CoroutineDispatcher

동키·2025년 4월 8일

Kotlin

목록 보기
5/10

CoroutineDispatcher

코틀린 코루틴의 정석 3장 내용을 공부하고 정리한 내용입니다.

코루틴의 실행을 관리하는 주체

  • 코루틴을 스레드로 보내 실행시키는 역활
  • 코루틴을 실행 요청한 스레드에서 코루틴이 실행되도록 만들 수 있음
  • 자신에게 실행 요청된 코루틴들을 작업 대기열에 적재 하고 자신이 사용할 수 있는 스레드가 새로운 작업을 실행할 수 있는 상태라면 스레드로 코루틴을 보내 실행될 수 있게 만드는 역활

CoroutineDispatcher는 추상 클래스이므로 다양하게 구현될 수 있으므로 내부 구현에 대해서 깊게 알 필요 X

개발자는 이미 추상화된 Dispatcher들을 잘 사용하면 됨


CoroutineDispatcher 사용해 코루틴 실행하기

launch의 파라미터로 CoroutineDispatcher를 사용할 수 있음

fun main() = runBlocking<Unit> {
    println(Thread.currentThread().name)
    launch(context = Dispatchers.IO + CoroutineName("Dispatcher.IO")) {
        println(Thread.currentThread().name)
    }
}
main
DefaultDispatcher-worker-1

context의 별도의 Dispatcher를 지정하지 않는경우 부모 코루틴의 Dispatcher 객체 사용

fun main() = runBlocking<Unit> {
    println(Thread.currentThread().name)
    launch(context = Dispatchers.IO + CoroutineName("Dispatcher.IO")) {
        println(Thread.currentThread().name)
        launch(context = Dispatchers.IO) {
            println(Thread.currentThread().name)
        }
    }
}
main
DefaultDispatcher-worker-1
DefaultDispatcher-worker-3

runBlocking은 Main 스레드에서 실행되지만 첫 번째 launch 에서 context에 Dispatchers.IO(I / O 관련 작업을 하는 스레드)로 지정했기 때문에 IO 스레드 풀인 DefaultDispatcher-worker 스레드 풀에서 실행되는 것을 확인할 수 있다.

두번째 자식 launch 은 별다른 지정이 없었기 때문에 부모인 첫 번째 launch의 Dispatcher를 그대로 사용.


미리 정의된 CoroutineDispatcher

위의 내용에서 개발자는 미리 정의된(추상화된) Dispatcher를 잘 사용하면 된다고 했다.

왜? 개발자가 직접 사용할 스레드를 생성하고 지정하면 안되는건가??

스레드풀을 직접 생성하는 newFixedThreadPoolContext 함수를 사용하면 경고 메시지가 뜹니다

This is a dlicate API and its use requires care. Make sure you fully
read and understand documentation of the declaration that is marked 
as a delicate API

대충 섬세하게 다뤄져야 하는 API이기 때문에 제대로 이해하고 사용해야 한다 = 니 자신있나?

즉, 비효율적으로 동작할 가능성이 높으며 특정 용도를 위해 만든 Dispatcher 객체가 이미 메모리상에 있음에도 까먹고 다시 만들어 리소스를 낭비하게 될 가능성이 있음.

스레드의 생성 비용은 매우 비쌉니다!!

이를 방지하기 위해 미리 정의된 CoroutineDispatcher의 목록을 제공한다.

Dispatchers.IO

네트워크 요청, 파일 입출력 등의 입출력 작업을 위한 CoroutineDispatcher

  • 네트워크 통신을 위해 HTTP 요청을 하는경우
  • DB 작업 같은 입출력
  • 파일 입출력

등 이런 요청을 동시에 수행하기 위해서는 많은 스레드가 필요하다.

이를 위해 코루틴 라이브러리에서는 입출력 작업을 위해 미리 정의된 Dispatchers.IO 를 제공한다.

즉, Dispatchers.IO를 사용 시 여러 입출력 작업을 동시에 수행 가능!

어떻게 사용할까?

fun main() = runBlocking<Unit> {
    launch(Dispatchers.IO) {
        println("Thread Name: ${Thread.currentThread().name}")
    }
}
DefaultDispatcher-worker-1

launch 함수의 context에 Dispatchers.IO를 전달하면 된다

Dispatchers.IO 는 싱글톤 인스턴스이므로 바로 넘길 수 있음

출력 결과를 보면 DefaultDispatcher-worker-1 가 나오는데 IO 작업을 하는 공유 스레드풀에 속한 스레드의 스레드가 실행된다.


Dispatchers.Default

CPU를 많이 사용하는 연산 작업을 위한 Coroutine Dispatcher

CPU 집중적인 작업(CPU 바운드 작업)을 처리하기 위한 기본 디스패처

입출력 작업 VS CPU 바운드 작업

입출력 작업과 CPU바운드 작업의 중요한 차이는 작업이 실행되었을 때 스레드를 지속적으로 사용하는지의 여부임.

입출력 작업은 작업을 실행한 후 결과를 반환받을 때까지 스레드를 사용하지 않지만 CPU바운드 작업은 작업을 하는 동안 스레드를 지속적으로 사용한다.

즉 입출력 작업은 작업 실행 후 스레드가 대기하는 동안 다른 입출력 작업을 동시에 할 수 있지만 CPU바운드 작업은 스레드 기반 작업을 사용한다.

입출력 I/O 작업CPU 바운드 작업
스레드 기반 작업 사용느림비슷
코루틴 사용빠름비슷
fun main() = runBlocking<Unit> {
    launch(Dispatchers.Default) {
        println("Thread Name: ${Thread.currentThread().name}")
    }
}
DefaultDispatcher-worker-1

Dispatchers.Default 스레드 사용 제한하기

CPU 바운드 작업(대용량 데이터 처리)이 Dispatchers.Default의 스레드에서 실행될 때 Dispatchers.Default의 모든 스레드가 사용될 수 있다.

이는 해당 연산이 Default 스레드풀의 모든 스레드를 사용하는 동안 Dispatchers.Default를 사용하는 다른 연산이 실행되지 못합니다.

이를 방지하기 위해 Default의 일부 스레드만 사용해 특정 연산을 실행할 수 있도록 하는 limitedParallelism 함수를 지원합니다.

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Default.limitedParallelism(2)) {
        repeat(10) {
            launch {
                println("[${Thread.currentThread().name}] 코루틴 실행")
            }
        }
    }
}
[DefaultDispatcher-worker-2 @coroutine#3] 
[DefaultDispatcher-worker-1 @coroutine#4] 
[DefaultDispatcher-worker-2 @coroutine#5] 
[DefaultDispatcher-worker-1 @coroutine#6] 
[DefaultDispatcher-worker-2 @coroutine#7] 
[DefaultDispatcher-worker-1 @coroutine#8] 
[DefaultDispatcher-worker-2 @coroutine#9] 
[DefaultDispatcher-worker-1 @coroutine#10] 
[DefaultDispatcher-worker-2 @coroutine#11] 
[DefaultDispatcher-worker-1 @coroutine#12] 
  • 첫 번째 launch의 Dispatcher를 Default로 설정
  • repeat(10) 10번 반복하고 launch를 통해 자식 생성 (이 때 자식은 부모의 Dispatcher 사용)
  • 스레드의 사용을 2개로 제한했기 때문에 worker1과 worker2가 번갈아가며 작업을 실행
  • 왜 코루틴 이름이 3번부터 시작하나요? 1번 = runBlocking, 2번 = 첫 번째 launch

공유 스레드풀을 사용하는 IO 와 Default

Dispatchers.IO와 Dispatchers.Default의 실행결과에서 스레드 이름이 같음을 확인할 수 있습니다.

이 둘은 공유 스레드풀을 사용합니다.

이 공유 스레드풀에서는 스레드를 무제한으로 생성할 수 있으며, 코루틴 라이브러리는 공유 스레드풀에 스레드를 생성하고 사용할 수 있도록하는 API를 제공합니다.

Default, IO 모두 이 API를 사용해 구현됐기 때문에 같은 스레드풀을 사용하는 것(Default, IO 사용스레드는 구분됨)


Dispatchers.Main

메인 스레드를 사용하기 위한 CoroutineDispatcher

UI가 있는 애플리케이션에서 메인 스레드의 사용을 위해 사용되는 특별한 Coroutine Dispatcher 객체입니다.

메인 스레드의 사용을 위해서는 별도 라이브러리를 추가해야 사용할 수 있다.

profile
오늘 하루도 화이팅

0개의 댓글