Kotlin Coroutine의 κ°œλ…κ³Ό λ™μž‘μ›λ¦¬

chihacker
  • #kotlin
  • #coroutine
  • #jvm
  • #android

λ“€μ–΄κ°€λ©°

μ•ˆλ…•ν•˜μ„Έμš”. 원더월 λͺ¨λ°”μΌνŒ€μ—μ„œ μ•±κ°œλ°œμ„ 맑고 μžˆλŠ” μ΅œλŒ€μˆœμž…λ‹ˆλ‹€.

ν˜„μž¬ λ§Žμ€ ν”„λ‘œκ·Έλž¨λ“€μ΄ 비동기 ν˜•νƒœλ‘œ μ§œμ—¬μ§€κ±°λ‚˜ 비동기 ν˜•νƒœλ₯Ό 지ν–₯ν•˜λŠ” ν˜•νƒœ ( Asynchronous or non-blocking programming ) 둜 κ΅¬μ„±λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 이미 λ§Žμ€ μ–Έμ–΄ λ˜λŠ” λΌμ΄λΈŒλŸ¬λ¦¬λ“€μ΄ 비동기 ν˜•νƒœμ˜ ν”„λ‘œκ·Έλž˜λ°μ„ μ‰½κ²Œ ν•  수 μžˆλ„λ‘ 지원을 ν•˜κ³  μžˆλŠ”λ°μš”. μ½”ν‹€λ¦° λ˜ν•œ 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ μ§€μ›ν•˜κΈ° μœ„ν•œ 방법듀 쀑 ν•˜λ‚˜λ‘œμ„œ coroutine 을 μ œκ³΅ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

이번 ν¬μŠ€νŠΈμ—μ„œλŠ” μ½”ν‹€λ¦°μ—μ„œ μ§€μ›ν•˜κ³  μžˆλŠ” coroutine 이 μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€μ— λŒ€ν•΄μ„œ λ‹€λ£¨μ–΄λ³΄κ³ μž ν•©λ‹ˆλ‹€. λ¨Όμ € coroutine 의 κ°œλ…μ„ μ‚΄νŽ΄λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

Coroutine μ΄λž€?

coroutine은 μΌμ’…μ˜ ν”„λ‘œκ·Έλž˜λ° κ°œλ…μ΄λΌκ³  λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μœ„ν‚€ν”Όλ””μ•„μ—μ„œλŠ” coroutine 을 μ•„λž˜μ™€ 같이 μ •μ˜ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed

μœ„ μ •μ˜λ₯Ό 풀어보면 coroutine은 suspend 와 resume 을 톡해 subroutineλ“€ κ°„μ˜ λΉ„μ„ μ ν˜• λ©€ν‹°ν…ŒμŠ€ν‚Ήμ„ ν• μˆ˜ μžˆλ„λ‘ ν•˜λŠ” ν”„λ‘œκ·Έλž¨ κ΅¬μ„±μš”μ†ŒλΌλŠ” μ˜λ―ΈμΈλ°μš”. λ‹¨μˆœνžˆ 사전적 μ •μ˜λ§ŒμœΌλ‘œλŠ” coroutine이 μ–΄λ–€ 역할을 ν•˜λŠ”μ§€ μ΄ν•΄ν•˜κΈ° 쉽지 μ•ŠμŠ΅λ‹ˆλ‹€. ν•΄λ‹Ή μ˜λ―Έμ— λŒ€ν•΄μ„œ μ’€ 더 μƒμ„Ένžˆ μ•Œμ•„λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

일반적인 ν”„λ‘œκ·Έλž¨λ“€μ€ 메인 루틴과 μ„œλΈŒ λ£¨ν‹΄μœΌλ‘œ 이루어져 μžˆμŠ΅λ‹ˆλ‹€. 보톡 ν”„λ‘œκ·Έλž¨μ„ μ‹œμž‘ν•˜λŠ” 곳이 메인 루틴이 되고 메인 루틴은 λ‹€μ–‘ν•œ μ„œλΈŒ λ£¨ν‹΄μœΌλ‘œ 이루어져 μžˆμŠ΅λ‹ˆλ‹€. 많이 μ•Œλ €μ§„ λ²„μŠ€ ν‹°μΌ“νŒ… 예제λ₯Ό ν†΅ν•΄μ„œ ν•΄λ‹Ή λ‚΄μš©μ„ μ„€λͺ…ν•˜κ² μŠ΅λ‹ˆλ‹€.

fun main() {
    linedUp()
    ticketing()
    takeTheBus()
}

fun linedUp() {
    println("lined up")
    Thread.sleep(2000)
}

fun ticketing() {
    println("ticketing")
}

fun takeTheBus() {
    println("waiting the bus")
    Thread.sleep(2000)
    println("take the bus")
}


μœ„ μ½”λ“œμ—μ„œ ν”„λ‘œκ·Έλž¨μ˜ μ‹œμž‘μ μΈ main() 은 메인 루틴이 되고 κ·Έ μ•ˆμ— ν˜ΈμΆœλ˜λŠ” 각각의 ν•¨μˆ˜λŠ” μ„œλΈŒ 루틴이라고 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ„œλΈŒλ£¨ν‹΄μ€ μ§„μž…μ κ³Ό μ’…λ£Œμ‹œμ μ„ κ°–κ²Œ λ˜λŠ”λ° 일반적으둜 μ‚¬μš©ν•˜λŠ” ν•¨μˆ˜μ˜ 호좜 μ‹œμ κ³Ό return μ‹œμ μ΄ μ§„μž…κ³Ό μ’…λ£Œ μ‹œμ μ΄ λ©λ‹ˆλ‹€. μ„œλΈŒλ£¨ν‹΄μ€ μ§„μž…μ  λΆ€ν„° μ’…λ£Œμ‹œμ κΉŒμ§€ 쀑단없이 싀행이 되기 λ•Œλ¬Έμ— 각각의 μ„œλΈŒλ£¨ν‹΄λ“€ μ‚¬μ΄μ˜ κ΄€κ³„λŠ” 계측적, 직렬적 관계가 λ©λ‹ˆλ‹€.

계측적, 직렬적 routine λ“€μ˜ 관계


이제 μœ„ μ˜ˆμ œμ—μ„œ 비동기 처리λ₯Ό μ μš©ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. 쀄을 μ„œ μžˆλŠ” λ™μ•ˆ, 그리고 λ²„μŠ€λ₯Ό κΈ°λ‹€λ¦¬λŠ” λ™μ•ˆ μŒμ•…μ„ λ“£λŠ”λ‹€κ³  κ°€μ • ν•˜κ² μŠ΅λ‹ˆλ‹€.

fun main() {
    asyncLinedUp() {
        stopMusic()
        ticketing()
        asyncTakeTheBus {
            stopMusic()
        }
        asyncPlayMusic()
    }
    
    asyncPlayMusic()

}

fun asyncLinedUp(myTurn: () -> Unit) {
    Thread {
        println("lined up")
        Thread.sleep(2000)
        myTurn.invoke()
    }.start()
}

fun asyncTakeTheBus(onTime: () -> Unit) {
    Thread {
        println("waiting the bus")
        Thread.sleep(2000)
        onTime.invoke()
        println("take the bus")
    }.start()
}

var playingMusic = false

fun asyncPlayMusic() {
    Thread {
            println("play music")
            playingMusic = true
            while(playingMusic) {
                println("listening..")
                Thread.sleep(500)
            }
    }.start()
}

fun stopMusic() {
    playingMusic = false
    println("stop music")
}


ν”„λ‘œκ·Έλž¨μ€ λ‹€μŒκ³Ό 같은 μˆœμ„œλ‘œ λ™μž‘ν•˜κ²Œ λ©λ‹ˆλ‹€.

( ν‹°μΌ“νŒ…μ„ κΈ°λ‹€λ¦°λ‹€. -> κΈ°λ‹€λ¦¬λ©΄μ„œ μŒμ•…μ„ λ“£λŠ”λ‹€. -> λ‚΄ μ°¨λ‘€κ°€ 되면 μŒμ•…μ„ λ©ˆμΆ”κ³  ν‹°μΌ“νŒ…μ„ ν•œλ‹€. -> λ²„μŠ€λ₯Ό κΈ°λ‹€λ¦°λ‹€ -> λ²„μŠ€κ°€ λ„μ°©ν•œλ‹€. -> μŒμ•…μ„ λ©ˆμΆ”κ³  λ²„μŠ€λ₯Ό 탄닀. )



Thread 별 μ½”λ“œ μˆ˜ν–‰


비동기 처리λ₯Ό ν•΄μ£Όλ©΄μ„œ μ½”λ“œκ°€ 쑰금 더 λ³΅μž‘ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€. 이런 λ‹¨μˆœνžˆ thread 와 callback 을 μ΄μš©ν•œ λΉ„λ™κΈ°μ²˜λ¦¬λŠ” λͺ‡κ°€μ§€ λ¬Έμ œμ μ„ 가지고 μžˆμŠ΅λ‹ˆλ‹€.

μ²«λ²ˆμ§ΌλŠ” μ½”λ“œμ˜ λ³΅μž‘μ„±μž…λ‹ˆλ‹€. 각각의 루틴듀은 독립적인 thread μ•ˆμ—μ„œ λ™μž‘μ„ ν•©λ‹ˆλ‹€. λ”°λΌμ„œ 각 루틴듀이 μ„œλ‘œμ—κ²Œ 영ν–₯을 μ£ΌκΈ° μœ„ν•΄μ„œλŠ” thread 사이에 톡신이 ν•„μš”ν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λŠ” μ½”λ“œλ₯Ό λ³΅μž‘ν•˜κ²Œ ν•˜κ³  κ΄€λ¦¬ν•˜κΈ° νž˜λ“€κ²Œ ν•©λ‹ˆλ‹€. λ˜ν•œ thread/callback κ΅¬μ‘°λŠ” μ½”λ“œ μƒμœΌλ‘œ 흐름을 νŒŒμ•…ν•˜κΈ°κ°€ 쉽지 μ•ŠμŠ΅λ‹ˆλ‹€. ( μœ„ μ½”λ“œμ—μ„œλ„ μ‰½κ²Œ ν”„λ‘œκ·Έλž¨μ΄ μ–΄λ–€ μˆœμ„œλ‘œ λ™μž‘λ˜λŠ”μ§€ νŒŒμ•…ν•˜κΈ°κ°€ 쉽지 μ•ŠμŠ΅λ‹ˆλ‹€. )

λ‘λ²ˆμ§ΈλŠ” λΉ„μš©μž…λ‹ˆλ‹€. 기본적으둜 thread λŠ” OS μ—μ„œ ν• λ‹Ήν•˜κ³  관리λ₯Ό ν•˜κ²Œ λ©λ‹ˆλ‹€. OS μ—μ„œ thread λ“€μ˜ μž‘μ—…μ„ μ μ ˆν•˜κ²Œ λΆ„λ°°ν•˜κΈ° μœ„ν•΄ 코어에 각각의 νƒœμŠ€ν¬λ“€μ„ μ μ ˆν•˜κ²Œ ν• λ‹Ή, 회수 μž‘μ—…μ„ ν•˜κ²Œ λ©λ‹ˆλ‹€. 이처럼 OS 에 μ˜ν•΄μ„œ μž‘μ—…μ΄ ν• λ‹Ήλ˜λŠ” 것을 preemptive multitasking 이라고 ν•©λ‹ˆλ‹€. OS κ°€ 각 thread 의 μž‘μ—…μ„ μŠ€μΌ€μ€„λ§ ν•  λ•Œ context switching 이 ν•„μš”ν•˜κ²Œ λ©λ‹ˆλ‹€. μ΄λ•Œ switching λΉ„μš©μ΄ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€. λ¬΄λΆ„λ³„ν•œ thread 생성이 κ²°κ΅­ λ§Žμ€ λ¦¬μ†ŒμŠ€λ₯Ό μ†ŒλΉ„ν•˜κ²Œ ν•˜μ—¬ 전체적은 ν”„λ‘œκ·Έλž¨μ˜ μ„±λŠ₯을 μ €ν•˜μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

이런 thread/callback 을 μ΄μš©ν•˜μ—¬ 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λŠ” κ³Όμ •μ—μ„œλŠ” μ½”λ“œμ˜ λ³΅μž‘λ„μ™€ λΉ„μš©μ˜ μ¦κ°€λ‘œ λ§Žμ€ 문제λ₯Ό μ•ΌκΈ° ν•  수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ 이런 점듀을 ν•΄κ²°ν•˜κΈ° μœ„ν•œ λ‹€μ–‘ν•œ 방법듀이 λ‚˜μ˜€κΈ° μ‹œμž‘ν•©λ‹ˆλ‹€. μ–Έμ–΄ μžμ²΄μ—μ„œ 이런 λ¬Έμ œλ“€μ„ ν•΄κ²°ν•˜κΈ° μœ„ν•œ λ‹€μ–‘ν•œ 방법듀이 λ“±μž₯ν•˜κΈ°λ„ ν•˜λ©° λ‹€μ–‘ν•œ 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ μœ„ν•œ λΌμ΄λΈŒλŸ¬λ¦¬λ“€λ„ λ“±μž₯ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이런 언어적 지원과 라이브러리 지원듀은 자체적으둜 thread λ₯Ό 관리해주고 callback 지μ˜₯에 빠지지 μ•Šκ³  λ‹¨μˆœν•˜κ³  νŒŒμ•…ν•˜κΈ° μ‰¬μš΄ 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λ„λ‘ 지원을 ν•΄μ€λ‹ˆλ‹€.

coroutine λ˜ν•œ 이런 단점듀을 ν•΄κ²°ν•΄μ£ΌλŠ” 언어적 κΈ°λŠ₯ 쀑 ν•˜λ‚˜μž…λ‹ˆλ‹€. μœ„μ—μ„œ λ³Έ coroutine 의 μ •μ˜μ—μ„œ non-preemptive multitasking λΌλŠ” 것을 확인 ν•  수 μžˆλŠ”λ°μš”, coroutine λŠ” thread 와 callback 을 ν†΅ν•œ 비동기 ν”„λ‘œκ·Έλž˜λ°μ—μ„œμ˜ 단점듀을 non-preemptive multitasking λ°©μ‹μœΌλ‘œ ν•΄κ²°ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

non-preemptive multitasking λž€ λ¬΄μ—‡μΌκΉŒμš”? coroutine 은 os κ°€ thread λ“€μ˜ μž‘μ—…μ„ μŠ€μΌ€μ€„λ§ ν•˜λ„λ‘ ν•˜μ§€ μ•Šκ³  subroutine κ°„μ˜ μƒν˜Έμž‘μš©μ„ ν†΅ν•΄μ„œ μ–Έμ–΄μ μœΌλ‘œ λ˜λŠ” μ½”λ“œ μž‘μ„±μžκ°€ 직접 μž‘μ—…μ„ μŠ€μΌ€μ€„λ§ ν•  수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€. ticketing 예제λ₯Ό coroutine 으둜 κ΅¬ν˜„ν•˜λ©΄μ„œ μ’€ 더 μžμ„Ένžˆ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.



fun main() {
    runBlocking {
        val lineUp = launch {
            coroutineLinedUp()
        }
            
        val playMusicWithLinedUp = launch {
            coroutinePlayMusic()
        }
            
        lineUp.join()
        playMusicWithLinedUp.cancel()
        coroutineTicketing()
            
        val waitingBus = launch {
            coroutineWaitingTheBus()
         }
            
        val playMusicWithWaitingBus = launch {
            coroutinePlayMusic()
        }
        
        waitingBus.join()
        playMusicWithWaitingBus.cancel()
        coroutineTakeTheBus()
    }
}

suspend fun coroutineLinedUp() {
    println("lined up")
    delay(2000)
}

fun coroutineTicketing() {
    println("ticketing")
}

suspend fun coroutineWaitingTheBus() {
    println("waiting the bus")
    delay(2000)
}

fun coroutineTakeTheBus() {
    println("take the bus")
}

suspend fun coroutinePlayMusic() {
    println("play music")
    while(true) {
        println("listening..")
        delay(500)
    }
}


kotlin μ—μ„œ μ œκ³΅ν•˜λŠ” coroutine 을 톡해 μœ„ 예제λ₯Ό λ³€κ²½ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 이 ν¬μŠ€νŠΈμ—μ„œλŠ” coroutine 에 λŒ€ν•œ κ°œλ…λ§Œ μ„€λͺ…ν•˜κΈ° μœ„ν•΄ kotlin coroutine 에 λŒ€ν•œ μžμ„Έν•œ μ„€λͺ…은 μƒλž΅ν•˜κ³  μ½”λ“œλ₯Ό μ΄ν•΄ν•˜λŠ”λ° 무리가 없도둝 runBlocking κ³Ό launch ν•¨μˆ˜μ— λŒ€ν•΄μ„œλ§Œ κ°„λ‹¨νžˆ μ„€λͺ…ν•˜κ² μŠ΅λ‹ˆλ‹€.

runBlocking 은 ν˜„μž¬ thread λ₯Ό block ν•˜λŠ” coroutine 을 μƒμ„±ν•˜λŠ” ν•¨μˆ˜ μž…λ‹ˆλ‹€. 즉 runBlocking 이 호좜된 thread λŠ” runBlocking λ‚΄μ˜ μž‘μ—…μ΄ μ™„λ£Œλ˜κΈ° μ „κΉŒμ§€ λ‹€λ₯Έ μž‘μ—…μ„ ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€. 이 ν•¨μˆ˜λŠ” ν”„λ‘œκ·Έλž¨μ˜ main ν•¨μˆ˜ λ“± νŠΉμˆ˜ν•œ κ²½μš°μ— μ‚¬μš©λ˜λ„λ‘ 섀계가 λ˜μ—ˆλŠ”λ°μš”, μžμ„Έν•œ μ„€λͺ…은 일단 μƒλž΅ν•˜κ² μŠ΅λ‹ˆλ‹€.

launch ν•¨μˆ˜λŠ” ν˜„μž¬ thread 에 λŒ€ν•œ blocking 없이 μ‹€ν–‰λ˜λŠ” coroutine 을 μƒμ„±ν•©λ‹ˆλ‹€. 즉 ν˜„μž¬ thread 에 λ‹€λ₯Έ μž‘μ—…μ„ ν• λ‹Ή ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μœ„ μ½”λ“œμ—μ„œλŠ” 크게 runBlocking, lineUp, playMusic 3개의 coroutine 이 μƒν˜Έμž‘μš©μ„ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. runBlocking 은 main thread λ₯Ό 작고 있으며 μž‘μ—…μ΄ μ™„λ£Œ 될 λ•ŒκΉŒμ§€ ν”„λ‘œκ·Έλž¨μ΄ μ’…λ£Œ λ˜μ§€ μ•Šλ„λ‘ ν•©λ‹ˆλ‹€. 즉 main routine 이 λ©λ‹ˆλ‹€.

main routine 은 lineUp κ³Ό playMusic μ΄λΌλŠ” 2개의 coroutine 을 μƒμ„±ν•˜κ³  μ‹€ν–‰ν•©λ‹ˆλ‹€. lineUp κ³Ό playMusic 은 λ™μ‹œμ„±μ„ 보μž₯ν•˜λ©΄μ„œ 각각의 routine 을 μˆ˜ν–‰ν•©λ‹ˆλ‹€. κ·Έ ν›„ main routine μ—μ„œ lineUp.join() μ΄λΌλŠ” ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€. μ΄λŠ” lineUp coroutine 이 μ™„λ£Œ λ λ•ŒκΉŒμ§€ ν˜„μž¬ routine 을 μΌμ‹œμ •μ§€ μ‹œν‚€κ³  lineUp coroutine 이 μ™„λ£Œκ°€ 되면 κ·Έ λ•Œ routine 을 λ‹€μ‹œ 재개 ν•˜κ² λ‹€λΌλŠ” μ˜λ―Έμž…λ‹ˆλ‹€. lineUp coroutine 이 μ™„λ£Œκ°€ 되면 playMusic coroutine 을 cancel μ‹œν‚€κ³  ticketing κ³Ό takeTheBus λ₯Ό ν˜ΈμΆœν•˜λŠ” ꡬ쑰 μž…λ‹ˆλ‹€. 이λ₯Ό 도식화 해보면 μ•„λž˜ 그리과 κ°™μŠ΅λ‹ˆλ‹€.



routine 별 μ½”λ“œ μˆ˜ν–‰


μœ„ 그림처럼 coroutine 을 μ΄μš©ν•˜μ—¬ routine κ³Ό routine κ°„μ˜ 관계 μ •μ˜λ§Œμ„ ν†΅ν•΄μ„œ λ™μ‹œμ„±μ΄ 보μž₯λ˜λŠ” 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 일반적인 subroutine κ³ΌλŠ” λ‹€λ₯΄κ²Œ coroutine μ—μ„œλŠ” λΉ„λ™κΈ°μ μœΌλ‘œ routine 을 μ‹€ν–‰ ν•  수 μžˆμ—ˆμœΌλ©° 각 λ£¨ν‹΄μ—μ„œ μ‹€ν–‰λ˜λŠ” μž‘μ—…λ“€μ„ 쀑간에 μΌμ‹œμ •μ§€( lineUp.join() ) ν•˜κ³  μž„μ˜μ˜ μ‹œμ μ— 재개 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 μ½”λ“œλŠ” 더 읽기 μ‰¬μ›Œμ‘ŒμœΌλ©° κ°œλ°œμžκ°€ μ •μ˜ν•œ λͺ¨λ“  subroutine 을 같은 context μ—μ„œ (ν•΄λ‹Ή ν”„λ‘œκ·Έλž¨μ—μ„œλŠ” main thread) μ‰½κ²Œ μ‹€ν–‰ ν•  수 있게 ν•©λ‹ˆλ‹€. ( μœ„ μ΄λ―Έμ§€λŠ” κ°œλ°œμžκ°€ μ •μ˜ν•œ μ½”λ“œ μ˜μ—­μ—μ„œμ˜ 관계λ₯Ό 도식화 ν•œ κ²ƒμœΌλ‘œ μ‹€μ œλ‘œλŠ” μœ„ ν”„λ‘œκ·Έλž¨μ΄ ν•œκ°œμ˜ thread μ—μ„œ λ™μž‘ν•˜λŠ” 것은 μ•„λ‹™λ‹ˆλ‹€. μ–Έμ–΄μ μœΌλ‘œ 각 λ£¨ν‹΄λ“€μ˜ μŠ€μΌ€μ€„λ§μ„ μœ„ν•œ thread λ₯Ό ν• λ‹Ή ν•˜μ—¬ μ‚¬μš©ν•˜κ³  μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. ) 이처럼 coroutine 은 routine κ³Ό routine κ°„μ˜ 관계λ₯Ό μ •μ˜ν•˜κ³  μ •μ˜λœ 관계에 따라 μŠ€μΌ€μ€„λ§μ„ μ–Έμ–΄λ ˆλ²¨μ—μ„œ ν•΄μ€ŒμœΌλ‘œμ„œ μ½”λ“œλ₯Ό μ’€ 더 λͺ…ν™•ν•˜κ²Œ ν•˜κ³  context switching λΉ„μš©μ„ 쀄일 수 있게 ν•©λ‹ˆλ‹€.

JVM μƒμ—μ„œμ˜ Kotlin Coroutine λ™μž‘κ΅¬μ‘°

kotlin의 μž₯점 쀑 ν•˜λ‚˜λŠ” JVM기반 μ–Έμ–΄λ‘œ JVM λ°”μ΄νŠΈμ½”λ“œλ‘œ λ³€ν™˜μ΄ κ°€λŠ₯ν•˜λ‹€λŠ” μ μž…λ‹ˆλ‹€. 그럼 kotlin coroutine 은 μ–΄λ–»κ²Œ μœ„μ™€ 같은 비동기 ν”„λ‘œκ·Έλž˜λ°μ΄ κ°€λŠ₯ν•˜κ²Œ ν•˜λŠ” κ²ƒμΌκΉŒμš”? kotlin coroutine 은 기본적으둜 CPS 와 state machine 을 μ΄μš©ν•˜μ—¬ 이와 같은 비동기 ν”„λ‘œκ·Έλž˜λ°μ΄ κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

일반적인 ν•¨μˆ˜ ( subroutine ) 은 return 이 되면 졜초 호좜 뢀인 caller 둜 λŒμ•„μ˜€κ²Œ λ©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ kotlin coroutine μ—μ„œλŠ” caller 둜 λ¦¬ν„΄ν•˜μ§€ μ•Šκ³  각 subroutine 의 μƒνƒœλ₯Ό 컨트둀 ν•˜κΈ° μœ„ν•΄μ„œ λ”°λ‘œ 전달 받은 callback ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κ²Œ λ©λ‹ˆλ‹€. return λŒ€μ‹ μ— μ§€μ†μ μœΌλ‘œ μ΄μ–΄λ‚˜κ°ˆ μƒˆλ‘œμš΄ context λ₯Ό continuation 이라고 λΆ€λ¦…λ‹ˆλ‹€. CPS λž€ continuation passing style 의 μ•½μžλ‘œ subroutine μ—μ„œ caller 둜 return ν•˜λŠ” 것이 μ•„λ‹Œ continuation 을 λ°›μ•„μ„œ subroutine 의 μž‘μ—…κ°’μ„ continuation 으둜 passing ν•˜λŠ” ν”„λ‘œκ·Έλž˜λ° 방법을 μ˜λ―Έν•©λ‹ˆλ‹€.

kotlin coroutine μ—μ„œλŠ” 이런 CPS 둜 각 subroutine 듀을 μ œμ–΄ ν•©λ‹ˆλ‹€. subroutine 은 caller 의 context 둜 λŒμ•„κ°€λŠ” λŒ€μ‹  μ •μ˜λœ continuation 에 μ˜ν•΄ μ œμ–΄λ˜κ²Œ λ©λ‹ˆλ‹€.

kotlin coroutine μ—μ„œ 각 subroutine 듀은 continuation 에 μ˜ν•΄μ„œ μ œμ–΄λœλ‹€κ³  ν–ˆλŠ”λ° μ–΄λ–€ λ°©λ²•μœΌλ‘œ μ œμ–΄κ°€ λ˜λŠ” κ²ƒμΌκΉŒμš”? kotlin coroutine 은 state machine 을 ν†΅ν•΄μ„œ subroutine 의 λ™μž‘μ„ μ œμ–΄ν•˜κ²Œ λ©λ‹ˆλ‹€. λ‹€μŒμ€ KotlinConf 2017 β€” Deep Dive into Coroutines on JVM 에 μ†Œκ°œλœ μ˜ˆμ œμž…λ‹ˆλ‹€.



suspend fun postItem(item: Item) {
    val token = requestToken()
    val post = createPost(token, tiem)
    processPost(post)
}


μœ„ μ˜ˆμ œμ—μ„œ 각각의 routine 듀은 coroutine μœΌλ‘œμ„œ λ™μž‘μ„ ν•©λ‹ˆλ‹€. postItem 은 μ‹€μ œλ‘œλŠ” λ‹€μŒκ³Ό 같은 ν˜•νƒœλ‘œ λ™μž‘μ„ ν•˜κ²Œ λ©λ‹ˆλ‹€.



fun postItem(item: Item, cont: Continuation) {
    val sm = object : ContinuationImpl { 
        fun resume() {
            postItem(null,this)
        }
    }
    switch(sm.label) {
        case 0:
          sm.item = item
          sm.label = 1
          val token = requestToken(sm)
        case 1:
          val item = sm.item
          val token = sm.result
          val post = createPost(token, item, sm)
}
fun requestToken(cont: Continuation) {
  //...
}
fun createPost(token: Token, item: Item, cont: Continuation) {
  //...
}


μ‹€μ œ kotlin coroutine μ½”λ“œλ₯Ό decompile 해보면 μœ„μ™€ 같은 ν˜•νƒœλ‘œ λ˜μ–΄ μžˆλŠ” 것을 μ•Œμˆ˜ μžˆμŠ΅λ‹ˆλ‹€. (λ¬Όλ‘  μœ„μ˜ μ˜ˆμ œλŠ” 맀우 λ‹¨μˆœν™” μ‹œν‚¨ κ²ƒμž…λ‹ˆλ‹€. ) μ•žμ„œ λ§ν•œ 봐와 같이 coroutine 은 CPS ν˜•νƒœλ‘œ λ™μž‘ν•¨μœΌλ‘œ continuation 을 νŒŒλΌλ―Έν„°λ‘œ λ°›κ³  μžˆμŠ΅λ‹ˆλ‹€. postItem ν•¨μˆ˜μ—μ„œλŠ” reqeustToken() κ³Ό createPost() λ£¨ν‹΄κ°„μ˜ 관계 μ •μ˜λ₯Ό μœ„ν•΄μ„œ state machine 인 continuation 을 λ§Œλ“€κ²Œ λ©λ‹ˆλ‹€. 이제 reqeustToken() κ³Ό createPost() 루틴은 postItem μ—μ„œ μ •μ˜λœ state machine 에 μ˜ν•΄μ„œ λ™μž‘μ΄ κ²°μ •λ˜κ²Œ λ©λ‹ˆλ‹€. 초기 label 이 0 μž„μœΌλ‘œ μ΅œμ΄ˆμ—λŠ” switch 문의 reqeustToken() 이 μˆ˜ν–‰λ©λ‹ˆλ‹€. μ΄λ•Œ 이 ν›„μ˜ context λ₯Ό κ²°μ •ν•˜κΈ° μœ„ν•΄ continuation 으둜 state machine λ₯Ό λ„˜κ²¨μ€λ‹ˆλ‹€. 그리고 이 state machine μ—λŠ” ν˜„μž¬μ˜ μƒνƒœκ°’λ“€μ„ μ €μž₯ν•©λ‹ˆλ‹€. ( label 을 λ³€κ²½ν•˜κ³  νŒŒλΌλ―Έν„°λ‘œ λ“€μ–΄μ˜¨ item 을 μ €μž₯ ). requestToken() λ‚΄λΆ€μ—μ„œλŠ” λ‹€μ‹œ continuation 으둜 λ„˜κ²¨λ°›μ€ state machine 에 return 값을 μ €μž₯ν•΄μ£Όκ²Œ λ©λ‹ˆλ‹€. 그리고 resume 을 ν†΅ν•΄μ„œ 볡귀 ν•΄μ•Όν•  ν•  context λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€. μ΄λ•Œλ„ μ—­μ‹œ continuation 을 λ„˜κ²¨ μ£Όλ©΄μ„œ λ³€κ²½λœ μƒνƒœμ— λŒ€ν•œ 값듀이 μ €μž₯된 μƒνƒœλ‘œ μ‹€ν–‰ 될 수 있게 ν•©λ‹ˆλ‹€. 이λ₯Ό ν†΅ν•΄μ„œ 루틴듀 사이에 suspend, resume 등이 κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.

마치며

이번 ν¬μŠ€νŠΈμ—μ„œλŠ” coroutine에 λŒ€ν•œ κ°œλ…κ³Ό JVM μƒμ—μ„œμ˜ λ™μž‘μ›λ¦¬λ₯Ό κ°„λ‹¨ν•˜κ²Œ 정리해 λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

λ‹¨μˆœνžˆ μ‚¬μš©λ²• λŒ€ν•΄μ„œλ§Œ μ•Œκ³  μ½”λ“œλ₯Ό κ΅¬μ„±ν•˜κΈ°λ³΄λ‹€ 내뢀적인 λ™μž‘κ΅¬μ‘°μ™€ 원리λ₯Ό νŒŒμ•…ν•œ ν›„ μž‘μ„±ν•˜λŠ” μ½”λ“œλŠ” κ°œλ°œν•¨μ— μžˆμ–΄μ„œ 더 κΉŠμ€ 고민을 ν•  수 μžˆκ²Œν•˜κ³  이런 고민을 톡해 보닀 λ‚˜μ€ μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό 개발 ν•  수 μžˆλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

이번 ν¬μŠ€νŠΈκ°€ coroutine을 μ‚¬μš©ν•  λ•Œ 보닀 κΉŠμ€ 고민을 ν•  수 μžˆλŠ”λ° 도움이 λ˜μ—ˆμœΌλ©΄ ν•©λ‹ˆλ‹€.

← λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ°

Art Changes Life

λ…Έλ¨ΈμŠ€μ™€ ν•¨κ»˜ μ—”ν„°ν…Œν¬ 산업을 ν˜μ‹ ν•΄λ‚˜κ°ˆ 멀버λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.

μ±„μš© 쀑인 곡고 보기