κΉ¨μ•Œ κ°œλ… πŸ“‘/Kotlin

[Kotlin] Coroutines - 동기 μ½”λ“œ, 비동기 μ½”λ“œ

interfacer_han 2024. 2. 8. 12:26

λ³Έ κ²Œμ‹œκΈ€μ˜ #1 ~ #3은 동기성 및 비동기성을 μ„€λͺ…ν•˜κΈ° μœ„ν•΄μ„œ μŠ€λ ˆλ“œμ˜ κ°œλ…μ— λŒ€ν•΄ 고의적이고 논리적인 비약을 μ‚¬μš©ν–ˆλ‹€. 이 글을 λ³΄λŠ” 뢄은 κΌ­ #4의 μ£Όμ˜ν•  μ κΉŒμ§€ 봐주셔야 ν•œλ‹€.
 

#1 동기 μ½”λ“œ vs 비동기 μ½”λ“œ

#1-1 κ΅¬λΆ„ν•˜κΈ°

코루틴을 μ œλŒ€λ‘œ μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„  λ¨Όμ €, '동기 μ½”λ“œ'와 '비동기 μ½”λ“œ(= 코루틴 μ½”λ“œ)'λ₯Ό λͺ…ν™•ν•˜κ²Œ ꡬ뢄할 쀄 μ•Œμ•„μ•Ό ν•œλ‹€. λ‘˜μ„ κ΅¬λΆ„ν•˜λŠ” 기쀀은 μ‰½κ²Œ λ§ν•˜μžλ©΄ μž‘업이 μˆœμ°¨μ μœΌλ‘œ μ‹€ν–‰λ˜λŠ” μ§€μ˜ μ—¬λΆ€λ‹€. μˆœμ°¨μ μ΄λΌλŠ” 것은, 이전 μž‘μ—…μ΄ μ™„λ£Œλ  λ•ŒκΉŒμ§€ λ‹€μŒ μž‘μ—…μ΄ μ‹€ν–‰λ˜μ§€ μ•ŠμŒμ„ μ˜λ―Έν•œλ‹€. 비동기 μ½”λ“œλŠ” 동기 μ½”λ“œκ°€ μ•„λ‹Œ μ½”λ“œλ‹€.
 

#1-2 비동기 μ½”λ“œμ˜ μ˜ˆμ‹œ

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

suspend fun main() {
    println("Start")

    val myJob = CoroutineScope(Dispatchers.IO).launch {
        println("Coroutine code start")
        delay(2000)
        println("Coroutine code end")
    }

    var meaninglessNumber = 0
    for (i in 1..100000000) {
        meaninglessNumber += i
    }

    println("End")

    myJob.join()
}

/* 좜λ ₯ κ²°κ³Ό
Start
Coroutine code start
End
Coroutine code end
*/

μœ„ μ½”λ“œμ— '비동기 μ½”λ“œ'κ°€ μ‘΄μž¬ν•¨μ€ λΆ„λͺ…ν•˜λ‹€. μ½”λ“œκ°€ 써진 μˆœμ„œμ™€ 좜λ ₯ κ²°κ³Όκ°€ λΉ„λ‘€ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€. 즉, μˆœμ°¨μ μ΄μ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€.
 

#2 μŠ€λ ˆλ“œμ˜ 병렬 ꡬ쑰

#2-1 필연적 λΉ„(非)μˆœμ°¨μ„±

이 μ˜ˆμ‹œ λ„μ‹λ„μ—μ„œ, μ›λž˜ μŠ€λ ˆλ“œμ— μžˆλŠ” A + B 좜λ ₯은 λΆˆκ°€λŠ₯ν•˜λ‹€. ν•΄λ‹Ή μ‹œμ μ—μ„  A의 κΈ΄ μž‘μ—… μ‹œκ°„ λ•Œλ¬Έμ— κ·Έ 값이 λ­”μ§€ λͺ¨λ₯΄κΈ° λ•Œλ¬Έμ΄λ‹€. 이와 같이 λ³‘λ ¬ ꡬ쑰λ₯Ό κ΅¬μ„±ν•˜λ©΄, μ‹œμ°¨κ°€ λ‚  수 밖에 μ—†λ‹€. λ¬Όλ‘  λ§ˆλ²•μ²˜λŸΌ λͺ¨λ“  타이밍이 λ”±λ”± λ§žμ•„ λ–¨μ–΄μ§ˆ μˆ˜λ„ μžˆκ² μ§€λ§Œ, 그건 동전 100개λ₯Ό λ˜μ Έμ„œ μ „λΆ€ μ•žλ©΄μ΄ λ‚˜μ˜¬ μˆ˜λ„ μžˆμ§€ μ•ŠλƒλŠ” 물음과 닀름없닀. 즉, μ‹œμ°¨λŠ” (ν•„μ—°μ μœΌλ‘œ) λ°œμƒν•  수 밖에 μ—†λ‹€. 그리고 μ‹œμ°¨λ₯Ό κ·Ήλ³΅ν•˜λ €λ©΄, 아직 μ™„λ£Œλ˜μ§€ μ•Šμ€ μŠ€λ ˆλ“œλ₯Ό κΈ°λ‹€λ €μ•Ό ν•œλ‹€. 멈좰(Suspend)μ•Ό ν•œλ‹€λŠ” 말이닀.
 

#2-2 Suspendλ₯Ό μΆ”κ°€ν•œ 도식도

#2-1을 μˆ˜μ •ν•΄ Suspendν•˜λŠ” μž‘μ—…μ„ μΆ”κ°€ν–ˆλ‹€.
 

#2-3 비동기 μ½”λ“œμ—μ„œ Suspendκ°€ μ—†λŠ” 경우

μŠ€λ ˆλ“œ 병렬 κ΅¬μ‘°μ—μ„œ SuspendλŠ” λΆ„λͺ… ν•„μ—°μ μ΄μ§€λ§Œ, ν•„μˆ˜λŠ” μ•„λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, A와 B (λ‘˜ λ‹€ ν•„μš”ν•œ 좜λ ₯ μž‘μ—… 등을 ν•˜λŠ” 게 μ•„λ‹ˆλΌ) 각각 κ³„μ‚°λ§Œ ν•˜κ³  λ§ˆλŠ” κ²½μš°κ°€ κ·Έλ ‡λ‹€. μ‹œμ°¨λ₯Ό λ§žμΆ°μ„œ ν•  μž‘μ—…μ΄ μ—†λ‹€. 이런 κ²½μš°μ—λŠ” ꡳ이 Suspendν•˜μ§€ μ•ŠλŠ”λ‹€. 즉, SuspendλŠ” 병렬 κ΅¬μ‘°μ—μ„œ 'ν•„μš”'ν•˜μ§€λ§Œ, 'ν•„μˆ˜'λŠ” μ•„λ‹ˆλΌλŠ” 이야기닀.
 

#3 동기 μ½”λ“œμ˜ 'μ˜μ—­' vs 비동기 μ½”λ“œμ˜ 'μ˜μ—­

동기 μ½”λ“œ μ˜μ—­μ€ ν•˜λ‚˜μ˜ μŠ€λ ˆλ“œμ—μ„œ μž‘μ—…μ΄ μ‹€ν–‰λ˜λ©°, 이전 μž‘μ—…μ΄ μ™„λ£Œλ  λ•ŒκΉŒμ§€ λ‹€μŒ μž‘μ—…μ΄ μ‹€ν–‰λ˜μ§€ μ•ŠλŠ” ν™˜κ²½μ΄λ‹€. μš°λ¦¬κ°€ μΌμƒμ μœΌλ‘œ μ ‘ν•΄μ˜¨ μ½”λ“œμ˜ 일반적인 μ˜μ—­μ΄ κ·Έλ ‡λ‹€. λ°˜λŒ€λ‘œ 비동기 μ½”λ“œ μ˜μ—­μ€ μž‘μ—…λ“€μ΄ μˆœμ°¨μ„± 없이 λ³‘λ ¬μ μœΌλ‘œ 싀행될 μˆ˜ μžˆλŠ” ν™˜κ²½μ„ λ§ν•œλ‹€. Coroutine은 비동기 μ½”λ“œλ₯Ό κ΅¬ν˜„ν•  λ•Œ μ“° λ„κ΅¬μ§€λ§Œ, Coroutine이라고 λ°˜λ“œμ‹œ 비동기 μ½”λ“œμΈ 것은 μ•„λ‹ˆλ‹€ (μ°Έμ‘°).
 
'동기 μ½”λ“œ'의 μ˜μ—­μ—” '비동기 μ½”λ“œ'λ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€. μ–΄μ§Έμ„œ? '비동기 μ½”λ“œ'λŠ” μ½”λ“œλ₯Ό μ‹€ν–‰(Run)ν•˜λŠ” 주체인 μ‹œμŠ€ν…œ(Kotlin의 κ²½μš°μ—” Kotlin λŸ°νƒ€μž„)을 'κΈ°λŒ€'ν•˜κ²Œ λ§Œλ“€κΈ° λ•Œλ¬Έμ΄λ‹€. μž‘μ—…μ΄ λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λœλ‹€λŠ” μ–˜κΈ°λŠ” μ‹œμŠ€ν…œμ΄ μ–Έμ œ κ·Έ μ½”λ“œμ˜ κ²°κ³Όλ₯Ό λ°›μ•„λ³Ό 수 μžˆλŠ” μ§€λ₯Ό μ•Œ 수 μ—†λ‹€λŠ” 이야기닀. 즉, '비동기 μ½”λ“œ'λŠ” μ‹œμŠ€ν…œμ—κ²Œ 값을 μ¦‰κ°μ μœΌλ‘œ λ‚΄μ£Όμ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ μ‹œμŠ€ν…œμ€ μ–Έμ  κ°„ μž‘μ—…μ΄ μ™„λ£Œ(launch)λ˜κ±°λ‚˜ 값을 전달(async, produce)해쀄 거라고 'κΈ°λŒ€'만 ν•  수 μžˆλ‹€. 'κΈ°λŒ€'λŠ” 'κΈ°λ‹€λ¦Ό'이닀. 기닀림은 '간헐적인 멈좀'이닀. κ°„ν—μ μœΌλ‘œ λ©ˆμΆ”λŠ” μ½”λ“œλ₯Ό μ–΄λ–»κ²Œ (μ—λŸ¬κ°€ λ‚˜μ§€ μ•ŠλŠ” ν•œ) μ ˆλŒ€ λ©ˆμΆ”μ§€ μ•ŠμŒμ„ 보μž₯ν•˜λŠ” '동기 μ½”λ“œ μ˜μ—­'에 λ‘˜ 수 μžˆκ² λŠ”κ°€? 
 
반면, '비동기 μ½”λ“œ'의 μ˜μ—­μ—” '동기 μ½”λ“œ'λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. λΉ„동기 μ½”λ“œμ˜ μ˜μ—­μ΄λΌκ³  λ°˜λ“œμ‹œ 비동기 μ½”λ“œλ§Œ μžˆμ„ ν•„μš”κ°€ μ—†λ‹€. '간헐적 멈좰짐이 ν—ˆμš©λ˜λŠ” κ³³'이지, 'λ°˜λ“œμ‹œ λ©ˆμΆ°μ•Όν•˜λŠ” κ³³'이 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ΄λ‹€.
 
#1-2의 μ˜ˆμ‹œ μ½”λ“œλ₯Ό 보면, 비동기 μ½”λ“œλŠ” CoroutineScopeλΌλŠ” 포μž₯지에 λ‘˜λŸ¬μ‹Έμ—¬ 밖에 μžˆλŠ” 동기 μ½”λ“œμ˜ μ˜μ—­κ³Ό κ·Έ μ˜μ—­μ΄ λΆ„λͺ…νžˆ ꡬ뢄됨을 λ³Ό 수 μžˆλ‹€.
 

#4 μ£Όμ˜ν•  점 (코루틴은 물리적 μŠ€λ ˆλ“œκ°€ μ•„λ‹ˆλ‹€)

#1 ~ #3μ—μ„œλŠ” 동기성 및 비동기성을 μ„€λͺ…ν•˜κΈ° μœ„ν•΄μ„œ 마치, ν•˜λ‚˜μ˜ 코루틴 = ν•˜λ‚˜μ˜ (물리적) μŠ€λ ˆλ“œμΈ κ²ƒμ²˜λŸΌ μ„€λͺ…ν–ˆλ‹€. ν•˜μ§€λ§Œ, μ΄λŠ” 이해λ₯Ό μœ„ν•œ 고의적이고 논리적인 비약이닀. 즉 ν‹€λ¦° μ„€λͺ…이닀. μŠ€λ ˆλ“œ(Thread)λŠ” μš΄μ˜μ²΄μ œκ°€ κ΄€λ¦¬ν•˜λŠ” 것이며 각 μŠ€λ ˆλ“œλŠ” 물리적인 CPU에 ν• λ‹Ήλœλ‹€. 반면, 코루틴은 μ½”ν‹€λ¦°μ΄λΌλŠ” ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄ λ‹¨μœ„μ—μ„œ κ΄€λ¦¬λ˜λŠ” μΌμ’…μ˜ 논리적 μŠ€λ ˆλ“œλ‹€.
 
코루틴은 논리적 μŠ€λ ˆλ“œμ΄κΈ°μ—, ν•˜λ‚˜μ˜ 물리적 μŠ€λ ˆλ“œ μœ„μ—μ„œ μ—¬λŸ¬ 개의 코루틴이 μˆ˜ν–‰λ  μˆ˜λ„ μžˆλ‹€. 예λ₯Ό λ“€μ–΄, AλΌλŠ” μŠ€λ ˆλ“œμ—μ„œ 코루틴 a와 코루틴 bκ°€ μˆ˜ν–‰λ˜λŠ” 상황이라고 가정해보겠닀. a와 bλŠ” μ„œλ‘œ μž μ‹œ λ©ˆμΆ”(Suspend)λ©΄μ„œ λ‹€λ₯Έ 코루틴이 싀행될 수 있게 μžμ›(A)을 μ–‘λ³΄ν•˜λŠ” 식이닀. 이 양보λ₯Ό 톡해, 같은 μŠ€λ ˆλ“œμ—μ„œ μ—¬λŸ¬ 코루틴이 λ²ˆκ°ˆμ•„ κ°€λ©° μ‹€ν–‰λ¨μœΌλ‘œμ¨ 병렬적인 λ™μ‹œ μ‹€ν–‰μ²˜λŸΌ λ³΄μ΄λŠ” 것을 μ½”λ£¨ν‹΄μ˜ λ™μ‹œμ„±(Concurrency)이라고 ν•œλ‹€. μ΄λŠ” 물리적 μŠ€λ ˆλ“œμ˜ μ§„μ§œ λ™μ‹œ 싀행인 병렬성(Parallelism)κ³Ό λŒ€λΉ„λœλ‹€.
 
ν•˜λ‚˜μ˜ 코루틴을 ν•˜λ‚˜μ˜ μŠ€λ ˆλ“œμ— ν•˜λ‚˜μ”© ν• λ‹Ήν•˜λŠ” 것도 ν•„μš”ν•˜λ‹€λ©΄ κ΅¬ν˜„ν•  수 μžˆλ‹€. μŠ€λ ˆλ“œκ°€ A, B, C 총 3κ°œκ°€ μžˆμ„ λ•Œ ν”„λ‘œκ·Έλž˜λ¨Έκ°€ CoroutineDispatcherλ₯Ό μ΄μš©ν•΄ A, B, C 각각에 ν•˜λ‚˜μ˜ 코루틴을 ν• λ‹Ήν•˜λ©΄ λœλ‹€ (이러면 코루틴은 Concurrencyκ°€ μ•„λ‹ˆλΌ Parallelism을 κ΅¬ν˜„ν•˜κ²Œ λœλ‹€). 코루틴은 κΈ°λ³Έμ μœΌλ‘œλŠ” 논리적 μŠ€λ ˆλ“œμ΄μ§€λ§Œ, ν•„μš”ν•œ 경우 μ‹€μ œ 물리적 μŠ€λ ˆλ“œκΉŒμ§€ λͺ…μ‹œν•΄ μ‚¬μš©ν•  수 μžˆλŠ” μœ μ—°ν•œ 도ꡬ인 μ…ˆμ΄λ‹€.
 

#5 μš”μ•½

병렬 κ΅¬μ‘°λŠ” ν•„μ—°μ μœΌλ‘œ 非순차적(ιžλ™κΈ°μ )이고, λ”°λΌμ„œ Suspendλ₯Ό μš”κ΅¬ν•  수 μžˆλ‹€.