JVM/Kotlin

[Coroutines] Dispatchers 알아보기

Hyo Kim 2023. 10. 13. 19:01
728x90
반응형

예제

fun main(): Unit = runBlocking {
    launch { // #1
        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) { // #2
        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) { // #3
        println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) { // #4
        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
    }
}

#1

  • 상위 컨텍스트를 상속받는다. 예제에서는 runBlocking의 main 스레드를 실행한다.

#2

  • 메인스레드가 실행되는 것처럼 보이는 특수 디스패처로 일시중단 함수에서 다른 매커니즘으로 실행한다.

#3

  • 다른 디스패처가 명시적으로 지정되지 않은 경우에 사용되며, 백그라운드 스레드풀을 사용한다.

#4

  • 새로운 스레드를 생성한다. 매우 비싼 리소스로, 실제 애플리케이션에서는 필요하지 않을 때 close() 하거나 최상위 변수에 정의하여 애플리케이션 전체에서 재사용하거나 해야한다.

 

Unconfined vs confined dispatcher

Dispatchers.Unconfined 는 호출한 스레드에서 코루틴을 시작하지만, 첫 번째 일시중단(suspend) 지점까지만 시작된다.

일시중단 함수 후에는 호출된 일시 중단 함수에서 사용했던 스레드에서 코루틴을 재개한다.

unconfined dispatcher는

CPU 시간을 소비하지 않거나 특정 스레드에 국한된 공유 데이터(ex- UI)를 업데이트하지 않는 코루틴에 적합하다.

fun main(): Unit = runBlocking {
    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
main runBlocking : I'm working in thread main
Unconfined : After delay in thread kotlinx.coroutines.DefaultExecutor
main runBlocking. : After delay in thread main

일반적으로 사용하는 방식은 아니고, 나중에 실행하기 위해 코루틴을 디스패치할 필요가 없거나 원치 않는 사이드 이펙트가 발생하는 특정 케이스에 유용할 수 있는 고급 메커니즘.

 

Default vs IO

Default

  • 스레드풀의 스레드 수는 CPU 코어 수와 같지만, 최소 2개 이상이다.
  • CPU 코어 수에 의해 제한되기 때문에 특정 수의 작업만 병렬 처리가 가능하다.
  • Context에 Dispatcher나 ContinuationInterceptor가 지정되지 않은 경우 모든 표준 빌더(launch, async ..)에서 기본으로 사용된다.
  • 많은 계산이 필요하거나 병렬 처리의 이점을 활용하는 CPU 바운드 작업에 적합하다.
  • 단기 실행 계산 및 소규모 데이터 처리 작업은 기본 디스패처에서 실행하기에 적합하다.

IO

  • Default의 스레드풀과 별개의 스레드풀을 사용한다.
  • 즉, IO 디스패처에서 실행하는 코루틴은 기본 디스패처에서 실행중인 메인 스레드나 다른 코루틴을 Blocking 하지 않는다.
  • 스레드 수는 64개 또는 시스템에서 사용 가능한 CPU 코어 수 중 더 높은 값으로 설정한다.
  • 시스템 속성 kotlinx.coroutines.io.parallelism 을 통해서도 병렬작업 수를 설정할 수 있다.
  • Default에 비해 많은 스레드가 존재하기 때문에 병렬 Blocking 작업을 실행할 수 있다.
  • 파일 읽기 및 쓰기, 데이터베이스 쿼리 수행 또는 네트워크 요청과 같은 Block 작업이 포함된 IO 집약적인 작업에 적합하다.
  • 일반적으로 IO 작업은 CPU 리소스를 많이 소모하지 않지만 완료하는 데 시간이 오래걸릴 수 있다.
  • 따라서 IO 디스패처의 대규모 스레드 풀에 액세스하면 애플리케이션의 I/O 작업 완료 속도를 높일 수 있다.

참고

https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html#unconfined-vs-confined-dispatcher

728x90
반응형