KOTLIN

[KOTLIN] CONTEXT / DISPATCHER

집한구석 2022. 6. 15. 12:01
728x90

코루틴은 항상 Kotlin 표준 라이브러리에 정의된 CoroutineContext로 대표되는 어떤 context에서 실행되고 코루틴의 context는 여러 요소의 set으로 구성


Context

https://blog.csdn.net/qq_14876133/article/details/123849974 참고

  • 어떤 쓰레드에서 코루틴을 실행할지에 대한 Dispatcher의 정보를 담고 있는 그룹임, 스레드 풀을 전환하고, 지정하고, 예외를 잡는데 사용
  • Job, Deferred, Dispatcher, CoroutineName, CoroutineExceptionHandler는 모두 CorountineContext 인터페이스에 간접적으로 상속됨

Dispatcher

  • 코루틴을 생성하여 해당 코루틴을 Dispatcher에 전송시, Dispatcher는 자신이 관리하는 스레드풀 내의 스레드 부하 상황에 맞춰서 코루틴을 분배함
  • 결론적으로 Dispatcher는 코루틴을 특정스레드에서 실행할 수 있도록 도와주는 역할
  • 이러한 역할로 인하여 Dispatcher는 Context의 주요 요소 중 하나임

Dispatcher 과정 

  1. 유저가 코루틴을 생성 후 Dispatcher로 전송
  2. Dispatcher는 자신이 물고 있는 스레드풀에서 자원이 남는 스레드를 확인 후 해당 스레드로 코루틴 전송
  3. 코루틴 할당 받은 스레드는 수행함

Dispatcher 종류

종류 설명
Dispatchers.IO 코루틴이 공유된 스레드 풀에 있는 백그라운드 스레드에서  작동
로컬 DB와 작업 시, 네트워크 작업 or 파일과 관련된 작업할 때 사용
필요에 따라서 스레드를 더생성하거나 줄일 수있고 최대 64개까지 생성이 가능
Default Dispatcher와 스레드를 공유하기 때문에 스위칭으로 인한 오버헤드를 발생시키지 않음
Dispatchers.Main 코루틴이 메인스레드에서 실행
ui / suspending 함수 등 LiveData에서 수정사항을 가져오는 작고 가벼운 작업을 실행
Dispatchers.Default 많은 리스트를 정렬하는 작업과 같이 CPU부하가 많은 작업들을 할 때 사용
Dispatchers.Unconfined GlobalScope와 함께 사용
코루틴이 현재스레드에 작동하고 중단되고 다시 시작되면 중단된 스레드에서 시작
  • 비동기 백그라운드 작업을 수행 시 가장 많이 사용되는 것은 IO하고 Default임
  • Default의 경우 코어수 만큼의 스레드만 생성하여 작업하기 때문에 CPU를 많이 점유하는 작업에서 최대효율을 냄

Dispatcher and threads

fun main() = runBlocking<Unit> {
    launch { // 부모의 context. main runblocking context입니다.
        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) { // not confined -- 메인 스레드에서 동작합니다.
        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) { // DefaultDispatcher로 작업이 보내집니다.
        println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) { // 새로운 스레드로 작업이 전달됩니다.
        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
    }
}
  • 메인스레드에서 실행되는 main의 runBlocking 코루틴의 context를받음
  • Unconfined는 메인 스레드에서 실행되는 것 처럼 보이지만 아님
  • GlobalScope에서는 코루틴이 실행될때 기본적으로 Dispatcher는 default나 혹은 백그라운드 스레드풀에서 공유해서 사용
  • newSingleThreadContext는 새로운 스레드를 생성

Unconfined vs confined dispatcher

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Unconfined) {
        println("Unconfined      : I'm working in thread ${Thread.currentThread().name}")
        delay(500)
        println("Unconfined      : After delay in thread ${Thread.currentThread().name}")
    }
    launch { 
        println("main runBlocking: I'm working in thread ${Thread.currentThread().name}")
        delay(1000)
        println("main runBlocking: After delay in thread ${Thread.currentThread().name}")
    }  
}
  • Unconfined dispatcher는 호출한 스레드에서 코루틴을 시작하지만 첫번째 suspension까지만 시작함
  • suspension이 끝나면 호출된 suspension 함수에 의해 다른 스레드로 코루틴을 재개 
  • CPU시간을 소비하지 않고 특정스레드에서 공유데이터를 업데이트하지도 않는 코루틴에서는 unconfined가 적합ㅎ마
  • confined dispatcher는 기본적으로 외부 코루틴스코프에 dispatcher를 상속받음

Jumping between threads

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")

fun main() {
    newSingleThreadContext("Ctx1").use { ctx1 ->
        newSingleThreadContext("Ctx2").use { ctx2 ->
            runBlocking(ctx1) {
                log("Started in ctx1")
                withContext(ctx2) {
                    log("Working in ctx2")
                }
                log("Back to ctx1")
            }
        }
    }
}
  • 코루틴의 context를 변경해주는 withContext 함수를 사용