본문 바로가기
Android/Kotlin

[Kotlin/Coroutine] 4. Coroutine context and dispatchers

by 겸 2023. 10. 24.

코루틴은 항상 Kotlin표준 라이브러리에 정의된 CoroutineContext 타입의 값으로 표시되는 컨텍스트에서 실행된다.

코루틴 컨텍스트는 다양한 요소의 집합이다. 주요 요소로 코루틴의 JobDispatcher가 있다.

Dispatchers and threads

Coroutine Context의 구성

  1. 코루틴의 실행에 사용되는 Thread
  2. 스레드를 결정하는 Coroutine Dispatcher

Coroutine Dispatcher의 동작

  1. 코루틴의 실행을 특정 스레드로 제한
  2. 코루틴을 Thread pool에 보냄
  3. 코루틴을 무제한으로 실행할 수 있도록 함

launchasync같은 모든 코루틴 빌더는 새로운 코루틴과 다른 컨텍스트 요소에 특정 Dispatcher를 지정할 수 있는 CoroutineContext 파라미터가 있다.

 

코드

fun main() = runBlocking<Unit> {
    launch { // context of the parent, main runBlocking coroutine
        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher 
        println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
    }    
}

실행 결과

Unconfined            : I'm working in thread main @coroutine#3
Default               : I'm working in thread DefaultDispatcher-worker-1 @coroutine#4
main runBlocking      : I'm working in thread main @coroutine#2
newSingleThreadContext: I'm working in thread MyOwnThread @coroutine#5

파라미터 없이 launch { … } 사용

시작되는 CoroutineScope의 context와 dispatcher를 상속한다.

위의 코드에서는 runBlocking을 상속해 main 스레드에서 실행된다.

Dispatchers.Unconfined

main스레드에서 실행되는 것처럼 보이지만, 실제로는 다른 매커니즘 (아래에서 설명)

Dispatchers.Default

다른 디스패처가 명시적으로 지정되지 않은 경우 공유된 백그라운드 스레드 풀을 사용

newSingleThreadContext

코루틴이 실행될 스레드를 생성. 전용 스레드는 매우 비싼 리소스이다.

실제 애플리케이션에서는 더 이상 필요하지 않을 때 close를 사용해서 해제하거나 최상위 변수에 저장해 애플리케이션 전체에서 재사용해야한다.


Unconfined vs confined dispatcher

Dispatchers.Unconfined

  1. 호출한 스레드의 첫번째 중단 지점까지 코루틴을 시작한다.
  2. 중단 후에는 실행된 중단함수에 의해 결정된 스레드에서 코루틴을 다시 시작한다.
  3. unconfined dispatcher는 CPU시간을 소비하지 않고 특정 스레드에 제한된 공유 데이터를 업데이트하지 않는 코루틴에 적합하다.
  4. 특히, 데이터를 업데이트하지 않는 UI를 그릴 때 효과적이다.

일반 Dispatcher

  1. 외부 CoroutineScope로부터 상속된다.
  2. runBlocking 코루틴의 default dispatcher는 호출한 스레드로 제한되고, 이를 상속하면 predictable FIFO 스케줄링을 사용하여 실행을 이 스레드로 제한할 수 있다.

코드

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
        println("Unconfined      : I'm working in thread ${Thread.currentThread().name}")
        delay(500)
        println("Unconfined      : After delay in thread ${Thread.currentThread().name}")
    }
    launch { // context of the parent, main runBlocking coroutine
        println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
        delay(1000)
        println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
    }    
}

실행 결과

Unconfined      : I'm working in thread main @coroutine#2
main runBlocking: I'm working in thread main @coroutine#3
Unconfined      : After delay in thread kotlinx.coroutines.DefaultExecutor @coroutine#2
main runBlocking: After delay in thread main @coroutine#3

위 코드에서 launch{ … }runBlocking { … } 코루틴으로부터 상속된 context를 가져서 main 스레드에서 실행된다.

launch(Dispatchers.Unconfined){ … }delay 함수 실행 후에는 default executor 스레드에서 다시 시작된다.

반응형