반응형

이 게시물은 다음 링크를 참조하여 학습했습니다

 

Under the hood 의미

가끔 영어 문서를 읽다 보면 under the hood라는 단어가 나온다. 이 뜻이 무엇일까? https://www.quora.com/What-does-under-the-hood-mean-in-programming What does "under the hood" mean in programming? Answ..

lucky516.tistory.com

 

[Kotlin] Coroutine - 코루틴의 내부 구현

코루틴은 디컴파일되면 일반 코드일 뿐이다. Continuation Passing Style(CPS, 연속 전달 방식) 이라는 형태로 동작하며, 결과를 호출자에게 직접 반환하는 대신 Continuation으로 결과를 전달한다. Continuation

june0122.github.io

 

1. under the hood?

이번 강의에서 Under the hood 라는 말이 나왔다.

under the hood는 자동차 "후드의 아래"를 생각하면 된다. 

운전을 할 때 자동차가 어떻게 굴러가는지에 대해 몰라도 운전을 할 수 있다.

under the hood는 우리(사용자)에게 안보이는 자동차 내부의 동작 원리를 생각하면 된다.

그래서 이번 게시글의 주제는 코루틴 내부의 동작 원리이다.

 

2. Coroutines under the hood

다음과 같은 코루틴 코드가 있다.

import kotlinx.coroutines.*

fun main(){
    GlobalScope.launch {
        val userData = fetchUserData()
        val userCache = cacheUserData(userData)
        updateTextView(userCache)
    }
}

suspend fun fetchUserData() = "user_name"

suspend fun cacheUserData(user: String) = user

fun updateTextView(user: String) = user

 

위 코드는 내부적으로 어떻게 동작할까?

2-1. Labels

먼저 Label을 찍는다. 

suspend fun이 컴파일 되는 시점을 기준으로 Label이 찍히게 된다.

아래처럼 말이다.

import kotlinx.coroutines.*

fun main(){
    GlobalScope.launch {
    		// LABEL 0
        val userData = fetchUserData()
        	// LABEL 1
        val userCache = cacheUserData(userData)
       		// LABEL 2
        updateTextView(userCache)
    }
}

suspend fun fetchUserData() = "user_name"

suspend fun cacheUserData(user: String) = user

fun updateTextView(user: String) = user

 

코루틴 코드를 자바로 디컴파일 해보면 switch case문으로 동작하는 것을 볼 수 있다.

Label로 나뉘어진 suspention point를 기준으로 switch-case문이 생성된다.

import kotlinx.coroutines.*

fun main(){
    switch(label){
    	case 0:
        	val userData = fetchUserData()
        	break
    	case 1:
        	val userCache = cacheUserData(userData)
        	break
    	case 2:
        	updateTextView(userCache)
        	break
    }
}

suspend fun fetchUserData() = "user_name"

suspend fun cacheUserData(user: String) = user

fun updateTextView(user: String) = user

이런식으로!

 

2-2. Continuation

Label이 찍힌 후 Continuation Passing Style로 변환하게 된다.

Continuation이라는 객체가 있고, 이를 넘겨준다.

Continuation에서 재귀 방식으로 resume을 하게 된다.

import kotlinx.coroutines.*

fun main(cont: Continuation){
	val sm = object:CoroutineImpl{ 
    	fun resume(...){
        	main(this)
    	}
        
        switch(sm.label){
            case 0:
                val userData = fetchUserData(sm)
                break
            case 1:
                val userCache = cacheUserData(userData, sm)
                break
            case 2:
                updateTextView(userCache)
                break
        }
    }
}

suspend fun fetchUserData() = "user_name"

suspend fun cacheUserData(user: String) = user

fun updateTextView(user: String) = user

 

 

3. Simulation Code

위 과정을 시뮬레이션 한 코드가 있어서 가져왔다.

위 내용과 비교해보면 이해가 잘 될것이다.

import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

fun main() {
    println("[in] main")
    myCoroutine(MyContinuation())
    println("\n[out] main")
}

fun myCoroutine(cont: MyContinuation){
    // suspend
    // switch - case 문을 when으로 구현
    when(cont.label){
        0 -> {
            println("\nmyCoroutine(), label: ${cont.label}")
            cont.label = 1
            fetchUserData(cont)
        }
        1 -> {
            println("\nmyCoroutine(), label: ${cont.label}")
            val userData = cont.result
            cont.label = 2
            cacheUserData(userData, cont)
        }
        2 -> {
            println("\nmyCoroutine(), label: ${cont.label}")
            val userCache = cont.result
            updateTextView(userCache)
        }
    }
}

fun fetchUserData(cont: MyContinuation){
    println("fetchUserData(), called")
    val result = "[서버에서 받은 사용자 정보]"
    println("fetchUserData(), 작업완료: $result")
    cont.resumeWith(Result.success((result)))
}

fun cacheUserData(user: String, cont: MyContinuation){
    println("cacheUserData(), called")
    val result = "[캐쉬함 $user]"
    println("cacheUserData(), 작업완료: $result")
    cont.resumeWith(Result.success(result))
}

fun updateTextView(user: String){
    println("updateTextView(), called")
    println("updateTextView(), 작업완료: [텍스트 뷰에 출력 $user]")
}

class MyContinuation(override val context:
CoroutineContext = EmptyCoroutineContext)
    : Continuation<String>{

    var label = 0
    var result = ""

    // resume
    // myCoroutine 재귀를 통해 재개
    override fun resumeWith(result: Result<String>) {
        this.result = result.getOrThrow()
        println("Continuation.resumeWith()")
        //여기서
        myCoroutine(this)
    }
}
반응형

+ Recent posts