πŸ“š Study

[kotlin/μ½”ν‹€λ¦° μ½”λ£¨ν‹΄μ˜ 정석] 3μž₯. CoroutineDispatcher

점이 2024. 5. 12. 19:21
λ°˜μ‘ν˜•

Coroutine Dispatcherλž€?

Coroutine을 μŠ€λ ˆλ“œλ‘œ 보내 μ‹€ν–‰μ‹œν‚€λŠ” μ—­ν• 

  • μŠ€λ ˆλ“œ / μŠ€λ ˆλ“œ 풀을 κ°€μ§€λ©° 코루틴을 μ‹€ν–‰ μš”μ²­ν•œ μŠ€λ ˆλ“œμ—μ„œ 코루틴이 μ‹€ν–‰λ˜λ„λ‘ 함

λ™μž‘ 방식

μžμ‹ μ—κ²Œ μ‹€ν–‰ μš”μ²­λœ 코루틴을 μž‘μ—… λŒ€κΈ°μ—΄μ— μ μž¬ν•œ ν›„, μ‚¬μš©ν•  수 μžˆλŠ” μŠ€λ ˆλ“œκ°€ 생기면 μŠ€λ ˆλ“œλ‘œ 보냄

  1. `CoroutineDispatcher` 객체에 μ½”λ£¨ν‹΄μ˜ 싀행이 μš”μ²­ 됨
  2. [CoroutineDispatcher] μ‹€ν–‰ μš”μ²­ 받은 코루틴을 μž‘μ—… λŒ€κΈ°μ—΄μ— 적재
  3. [CoroutineDispatcher] μ‚¬μš©ν•  수 μžˆλŠ” μŠ€λ ˆλ“œκ°€ μžˆλŠ”μ§€ 확인
    1. μžˆλ‹€λ©΄, 코루틴을 ν•΄λ‹Ή μŠ€λ ˆλ“œλ‘œ 보냄
    2. μ—†λ‹€λ©΄(λͺ¨λ“  μŠ€λ ˆλ“œκ°€ 코루틴을 싀행쀑이라면), μž‘μ—… λŒ€κΈ°μ—΄μ—μ„œ λŒ€κΈ°ν•˜λ„λ‘ λ‘ 
      1. μŠ€λ ˆλ“œ 쀑 ν•˜λ‚˜κ°€ μžμœ λ‘œμ›Œ μ‘Œμ„ λ•Œ, λŒ€κΈ°μ—΄μ— 있던 코루틴을 μŠ€λ ˆλ“œλ‘œ 보냄

μ—­ν• 

  • μ½”λ£¨ν‹΄μ˜ 싀행을 κ΄€λ¦¬ν•˜λŠ” 주체

 

μ œν•œλœ λ””μŠ€νŒ¨μ²˜μ™€ λ¬΄μ œν•œ λ””μŠ€νŒ¨μ²˜

μ œν•œλœ λ””μŠ€νŒ¨μ²˜

  • μ‚¬μš©ν•  수 μžˆλŠ” μŠ€λ ˆλ“œκ°€ μ œν•œλœ λ””μŠ€νŒ¨μ²˜
  • 일반적으둜 `CoroutineDispatcher` κ°μ²΄λ³„λ‘œ μ–΄λ–€ μž‘μ—…μ„ μ²˜λ¦¬ν• μ§€ 미리 역할을 λΆ€μ—¬ν•˜κ³ , 역할에 맞좰 싀행을 μš”μ²­ν•˜λŠ” 것이 효율적 → 주둜 μ‚¬μš©λ¨

λ¬΄μ œν•œ λ””μŠ€νŒ¨μ²˜

  • μ‹€ν–‰ν•  수 μžˆλŠ” μŠ€λ ˆλ“œκ°€ μ œν•œλ˜μ§€ μ•ŠμŒ
  • μ‹€ν–‰ μš”μ²­λœ 코루틴이 아무 μŠ€λ ˆλ“œμ—μ„œλ‚˜ μ‹€ν–‰λ˜λŠ” 것이 μ•„λ‹Œ, μ‹€ν–‰ μš”μ²­λœ 코루틴이 이전 μ½”λ“œκ°€ μ‹€ν–‰λ˜λ˜ μŠ€λ ˆλ“œμ—μ„œ 계속 μ‹€ν–‰λ˜λ„λ‘ 함

 

μ œν•œλœ λ””μŠ€νŒ¨μ²˜ μƒμ„±ν•˜κΈ°

단일 μŠ€λ ˆλ“œ λ””μŠ€νŒ¨μ²˜

  • `Single-Thread Dispatcher`
  • μ‚¬μš©ν•  수 μžˆλŠ” μŠ€λ ˆλ“œκ°€ ν•˜λ‚˜μΈ Coroutine Dispatcher → μŠ€λ ˆλ“œ ν’€: 1
val dispatcher: CoroutineDispatcher = newSingleThreadContext(name = "SingleThread")
  • `name`: λ””μŠ€νŒ¨μ²˜μ—μ„œ κ΄€λ¦¬ν•˜λŠ” μŠ€λ ˆλ“œμ˜ 이름

λ©€ν‹° μŠ€λ ˆλ“œ λ””μŠ€νŒ¨μ²˜

  • `Multi-Thread Dispatcher`
  • 2개 μ΄μƒμ˜ μŠ€λ ˆλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλŠ” CoroutineDispatcher
val multiThreadDispatcher: CoroutineDispatcher = newFixedThreadPoolContext(nThreads = 2, name = "MultiThread"))
  • `nThreads` : μŠ€λ ˆλ“œ 개수
  • `name` : μŠ€λ ˆλ“œμ˜ 이름
    • `MultiThread-1` , `MultiThread-2` 와 같이 ‘-1’ λΆ€ν„° μˆ«μžκ°€ ν•˜λ‚˜μ”© μ¦κ°€ν•˜λŠ” ν˜•μ‹μœΌλ‘œ 이름을 λΆ™μž„

πŸ”Ž λ‹¨μΌ μŠ€λ ˆλ“œ λ””μŠ€νŒ¨μ²˜μ™€ λ©€ν‹° μŠ€λ ˆλ“œ λ””μŠ€νŒ¨μ²˜μ˜ 관계

  • λ‚΄λΆ€μ μœΌλ‘œ `newFixedThreadPoolContext`λ₯Ό μ‚¬μš©ν•˜κ³  있음
  • `newSingleThreadContext` 와 `newFixedThreadPoolContext` λŠ” 같은 ν•¨μˆ˜λΌκ³  봐도 무방

 

CoroutineDispatcher μ‚¬μš©ν•˜μ—¬ 코루틴 μ‹€ν–‰ν•˜κΈ°

launch의 νŒŒλΌλ―Έν„°λ‘œ μ‚¬μš©ν•˜κΈ°

단일 μŠ€λ ˆλ“œ λ””μŠ€νŒ¨μ²˜ μ‚¬μš©

fun main() = runBlocking {
    val dispatcher = newSingleThreadContext(name = "SingleThread")
    launch(context = dispatcher) {
        println("[${Thread.currentThread().name}] μ‹€ν–‰")
    }
}

  • μž‘μ—… λŒ€κΈ°μ—΄μ— coroutine#2 코루틴을 μ μž¬ν•œ ν›„, 코루틴을 SingleThread둜 보내 μ‹€ν–‰μ‹œν‚΄

λ©€ν‹° μŠ€λ ˆλ“œ λ””μŠ€νŒ¨μ²˜ μ‚¬μš©

fun multiThreadDispatcher() = runBlocking<Unit> {
    val multiThreadDispatcher = newFixedThreadPoolContext(nThreads = 2, name = "MultiThread")
    launch(context = multiThreadDispatcher) {
        println("[${Thread.currentThread().name}] μ‹€ν–‰")
    }
    launch(context = multiThreadDispatcher) {
        println("[${Thread.currentThread().name}] μ‹€ν–‰")
    }
}

  1. CoroutineDispatcher 객체 생성
  2. coroutine#2 μ‹€ν–‰ μš”μ²­
  3. μž‘μ—… λŒ€κΈ°μ—΄μ— coroutine#2 적재 ν›„, MultiThread-1 에 ν• λ‹Ήν•˜μ—¬ μ‹€ν–‰
  4. coroutine#3 μ‹€ν–‰ μš”μ²­
  5. μž‘μ—… λŒ€κΈ°μ—΄μ— coroutine#3 적재 ν›„, MultiThread-2 에 ν• λ‹Ήν•˜μ—¬ μ‹€ν–‰
βš™ μ‹€ν–‰ ν™˜κ²½μ— 따라 각 코루틴이 λ‹€λ₯Έ μ†λ„λ‘œ 처리될 수 있기 λ•Œλ¬Έμ—, μŠ€λ ˆλ“œμ˜ μˆœμ„œλ‚˜ μ‚¬μš©λ˜λŠ” μŠ€λ ˆλ“œμ˜ μˆ˜λŠ” 맀우 λΆˆκ·œμΉ™μ μ΄λ‹€.

CoroutineDispatcher ꡬ쑰화

  • μ½”λ£¨ν‹΄μ˜ ꡬ쑰화: 코루틴 λ‚΄λΆ€μ—μ„œ μƒˆλ‘œμš΄ 코루틴을 μ‹€ν–‰ν•  수 μžˆλ‹€.
  • μžμ‹ 코루틴에 CoroutineDispatcher 객체가 μ„€μ •λ˜μ–΄μžˆμ§€ μ•ŠμœΌλ©΄, λΆ€λͺ¨ μ½”λ£¨ν‹΄μ˜ CoroutineDispatcher 객체λ₯Ό μ‚¬μš©
fun coroutineStructure() = runBlocking<Unit> {
    val multiThreadDispatcher = newFixedThreadPoolContext(nThreads = 2, name = "MultiThread")

    launch(multiThreadDispatcher) { // Parent Coroutine
        println("[${Thread.currentThread().name}] λΆ€λͺ¨ 코루틴 μ‹€ν–‰")
        launch {    // Child Coroutine
            println("[${Thread.currentThread().name}] μžμ‹ 코루틴 μ‹€ν–‰")
        }
        launch {  // Child Coroutine
            println("[${Thread.currentThread().name}] μžμ‹ 코루틴 μ‹€ν–‰")
        }
    }
}

λΆ€λͺ¨μ™€ μžμ‹ λͺ¨λ‘ 같은 CoroutineDispatcherλ₯Ό μ‚¬μš©ν•˜λ―€λ‘œ MultiThread-1κ³Ό MultiThread-2λ₯Ό 곡용으둜 μ‚¬μš©

πŸ’‘ νŠΉμ • CoroutineDispatcherμ—μ„œ μ—¬λŸ¬ μž‘μ—…μ„ μ‹€ν–‰ν•΄μ•Ό ν•œλ‹€λ©΄, λΆ€λͺ¨ 코루틴에 CoroutineDispatcherλ₯Ό μ„€μ •ν•˜κ³ , κ·Έ μ•„λž˜μ— μžμ‹ 코루틴을 μ—¬λŸ¬κ°œ μƒμ„±ν•˜μž.

 

미리 μ •μ˜λœ CoroutineDispatcher

문제점

  • `newFixedThreadPoolContext` ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ CoroutineDispatcherλ₯Ό λ§Œλ“€λ©΄, νŠΉμ • CoroutineDispatcher κ°μ²΄μ—μ„œλ§Œ μ‚¬μš©λ˜λŠ” μŠ€λ ˆλ“œ 풀이 생성 → μŠ€λ ˆλ“œ 풀에 μ†ν•œ μŠ€λ ˆλ“œμ˜ μˆ˜κ°€ λ„ˆλ¬΄ λ§Žκ±°λ‚˜ 적게 μƒμ„±λ˜μ–΄ λΉ„νš¨μœ¨μ μΈ λ™μž‘ 유발
  • μ—¬λŸ¬ κ°œλ°œμžκ°€ μž‘μ—…ν•  경우, 이미 λ©”λͺ¨λ¦¬ 상에 μžˆμŒμ—λ„ ν•΄λ‹Ή 객체의 쑴재λ₯Ό λͺ°λΌ λ‹€μ‹œ CoroutineDispatcher 객체λ₯Ό λ§Œλ“œλŠ” λ¦¬μ†ŒμŠ€ λ‚­λΉ„ λ°œμƒ κ°€λŠ₯

ν•΄κ²°

🌟 λ―Έλ¦¬ μ •μ˜λœ CoroutineDispatcher의 λͺ©λ‘μ„ 제곡

  • `Dispatchers.IO`
  • `Dispatchers.Default`
  • `Dispatchers.Main`

Dispatchers.IO

  • λ„€νŠΈμ›Œν¬ μš”μ²­μ΄λ‚˜ 파일 μž…μΆœλ ₯λ“±μ˜ IO μž‘μ—…μ„ μœ„ν•œ CoroutineDispatcher
  • μ΅œλŒ€λ‘œ μ‚¬μš©ν•  수 μžˆλŠ” μŠ€λ ˆλ“œ 수: JVMμ—μ„œ μ‚¬μš© κ°€λŠ₯ν•œ ν”„λ‘œμ„Έμ„œμ˜ μˆ˜μ™€ 64 쀑 큰 κ°’
  • πŸŒŸμ—¬λŸ¬ μž…μΆœλ ₯ μž‘μ—…μ„ λ™μ‹œμ— μˆ˜ν–‰ν•  수 있음🌟
fun dispatchersIO() = runBlocking<Unit> {
    launch(Dispatchers.IO) {
        println("[${Thread.currentThread().name}] 코루틴 μ‹€ν–‰")
    }
}

  • `DefaultDispatcher-worker : 코루틴 λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μ œκ³΅ν•˜λŠ” 곡유 μŠ€λ ˆλ“œν’€μ— μ†ν•œ μŠ€λ ˆλ“œ
    • Dispatchers.IOλŠ” 곡유 μŠ€λ ˆλ“œν’€μ˜ μŠ€λ ˆλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλ„λ‘ κ΅¬ν˜„λ˜μ–΄ 있음

Dispatchers.Default

  • CPUλ₯Ό 많이 μ‚¬μš©ν•˜λŠ” μ—°μ‚° μž‘μ—…μ„ μœ„ν•œ CoroutineDispatcher
fun dispatchersDefault() = runBlocking<Unit> {
    launch(Dispatchers.Default) {
        println("[${Thread.currentThread().name}] 코루틴 μ‹€ν–‰")
    }
}

πŸ”Ž μž…μΆœλ ₯ μž‘μ—…κ³Ό CPU λ°”μš΄λ“œ μž‘μ—…

  • μž…μΆœλ ₯ μž‘μ—…: κ²°κ³Όλ₯Ό λ°˜ν™˜λ°›μ„ λ•Œ κΉŒμ§€ μŠ€λ ˆλ“œλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŒ
  • CPU λ°”μš΄λ“œ μž‘μ—…: μž‘μ—…μ„ ν•˜λŠ” λ™μ•ˆ μŠ€λ ˆλ“œλ₯Ό μ§€μ†μ μœΌλ‘œ μ‚¬μš©
  I/O μž‘μ—…  CPU λ°”μš΄λ“œ μž‘μ—…
μŠ€λ ˆλ“œ 기반 μž‘μ—… μ‹œ 느림 λΉ„μŠ·

코루틴 μ‚¬μš© μ‹œ 빠름
(λŒ€κΈ°ν•˜λŠ” λ™μ•ˆ ν•΄λ‹Ή μŠ€λ ˆλ“œμ—μ„œ λ‹€λ₯Έ μž…μΆœλ ₯ μž‘μ—…μ„ λ™μ‹œμ— μ§„ν–‰)

limitedParallelism

문제점

μ‚¬μš©μ΄ 무겁고 μ˜€λž˜κ±Έλ¦¬λŠ” μ—°μ‚° 처리λ₯Ό μœ„ν•΄ `Dispatchers.Default` 의 λͺ¨λ“  μŠ€λ ˆλ“œκ°€ μ‚¬μš©λ  수 있음

`Dispatchers.Default` λ₯Ό μ‚¬μš©ν•˜λŠ” λ‹€λ₯Έ 연산이 μ‹€ν–‰λ˜μ§€ λͺ»ν•¨

ν•΄κ²°

`limitedParallelism` : 일뢀 μŠ€λ ˆλ“œλ§Œ μ‚¬μš©ν•˜μ—¬ νŠΉμ • 연산을 μ‹€ν–‰ν•  수 μžˆλ„λ‘ ν•˜λŠ” ν•¨μˆ˜

fun limitedParallelism() = runBlocking<Unit> {
    launch(Dispatchers.Default.limitedParallelism(2)) {
        repeat(10) {
            launch {
                println("[${Thread.currentThread().name}] 코루틴 μ‹€ν–‰")
            }
        }
    }
}

  • Dispatchers.Default 의 μ—¬λŸ¬ μŠ€λ ˆλ“œ 쀑 2개의 μŠ€λ ˆλ“œλ§Œμ„ μ‚¬μš©

곡유 μŠ€λ ˆλ“œν’€

  • 코루틴 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” μŠ€λ ˆλ“œμ˜ 생성과 관리λ₯Ό 효율적으둜 ν•  수 μžˆλ„λ‘ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 레벨의 곡유 μŠ€λ ˆλ“œν’€μ„ 제곡
  • 곡유 μŠ€λ ˆλ“œν’€μ—μ„œλŠ” μŠ€λ ˆλ“œλ₯Ό λ¬΄μ œν•œμœΌλ‘œ 생성 κ°€λŠ₯
  • 코루틴 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜κ³  μ‚¬μš©ν•˜λ„λ‘ ν•˜λŠ” API 제곡

  • `newFixedThreadPoolContext` ν•¨μˆ˜λ‘œ λ§Œλ“€μ–΄μ§€λŠ” λ””μŠ€νŒ¨μ²˜λŠ” μžμ‹ λ§Œ μ‚¬μš©ν•  수 μžˆλŠ” μ „μš© μŠ€λ ˆλ“œν’€ 생성
  • `Dispatchers.IO`와 `Dispatchers.Default` λŠ” 곡유 μŠ€λ ˆλ“œν’€μ˜ μŠ€λ ˆλ“œλ₯Ό μ‚¬μš©

Dispatchers.Main

  • UIκ°€ μžˆλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ 메인 μŠ€λ ˆλ“œμ˜ μ‚¬μš©μ„ μœ„ν•΄ μ‚¬μš©λ¨
  • λ³„λ„μ˜ 라이브러리(kotlinx-coroutines-android λ“±)을 μΆ”κ°€ν•΄μ•Ό μ‚¬μš© κ°€λŠ₯

http://www.acornpub.co.kr/book/kotlin-coroutines

 

μ½”ν‹€λ¦° μ½”λ£¨ν‹΄μ˜ 정석

λ§Žμ€ κ°œλ°œμžλ“€μ΄ μ–΄λ ΅κ²Œ λŠλΌλŠ” 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ λ‹€μ–‘ν•œ μ‹œκ°μ  μžλ£Œμ™€ μ„€λͺ…을 톡해 λˆ„κ΅¬λ‚˜ μ‰½κ²Œ 이해할 수 μžˆλ„λ‘ 쓰인 책이닀.

www.acornpub.co.kr

πŸŒŸν›„κΈ°πŸŒŸ

Spring Boot - Kotlin μ‚¬μš©ν•˜λŠ” ν™˜κ²½μ—μ„œ 코루틴에 λŒ€ν•œ 더 κΉŠμ€ 지식이 ν•„μš”ν•΄ μ°Ύλ‹€ λ°œκ²¬ν•œ μ±…. 
ν•œκ΅­μΈ μ €μž λΆ„μ΄μ–΄μ„œ κ·ΈλŸ°μ§€ μ „λ°˜μ μœΌλ‘œ μ΄ν•΄ν•˜κΈ° ꡉμž₯히 μ‰½κ²Œ λ˜μ–΄μžˆκ³ ,
κΈ°μ‘΄ μ½”ν‹€λ¦° μ±…μ—μ„œλŠ” μ‰½κ²Œ 닀뀄지지 μ•ŠλŠ” 더 λ”₯ν•œ 코루틴 κΈ°λŠ₯κΉŒμ§€ μ•Œ 수 있음.
(μ§€κΈˆκΉŒμ§€ 읽은 κΈ°μˆ μ„œ 쀑 μ‹€λ¬΄μ—μ„œ κ°€μž₯ 잘 μ“°κ³  μžˆλŠ” μ±…...κ°€μž₯ 술술 읽힌 μ±…...πŸ‘)
SpringBoot-Kotlin ν™˜κ²½μ—μ„œ 코루틴을 더 κ³΅λΆ€ν•˜κ³  μ‹Άλ‹€λ©΄ 정말 κ°•μΆ” ν•„μˆ˜ ν•„μˆ˜ πŸ‘πŸ”₯πŸ‘πŸ”₯πŸ‘πŸ”₯πŸ‘πŸ”₯πŸ‘πŸ”₯
λ°˜μ‘ν˜•