기본 디스패처
디스패처를 설정하지 않으면 기본적으로 설정되는 디스패처는 CPU 집약적인 연산을 수행하도록 설계된 `Dispatchers.Default`이다.
이 디스패처는 코드가 실행되는 컴퓨터의 CPU 개수와 동일한 수(최소 두 개 이상)의 스레드 풀을 가지고 있다. CPU 집약적인 연산을 수행하며 블로킹이 일어나지 않는 환경 같이 스레드를 효율적으로 사용하고 있다고 가정하면 이론적으로는 최적의 스레드 수라고 할 수 있다.
다음 코드를 통해 디스패처를 확인할 수 있다.
suspend fun main(): Unit = coroutineScope {
repeat(1_000) {
launch(Dispatchers.Default) {
List(1000) {
Random.nextLong()
}.maxOrNull()
val threadName = Thread.currentThread().name
println("나는 지금 : $threadName")
}
}
}
기본 디스패처를 제한하기
비용이 많이 드는 작업이 `Dispatchers.Default`의 스레드를 다 써버려서 같은 디스패처를 사용하는 다른 코루틴이 실행될 기회를 제한하고 있다고 의심하는 상황일 때, `limitedParallelism`을 사용해 제한할 수 있다. 다음과 같이 사용하면 디스패처가 같은 스레드 풀을 사용하지만 같은 시간에 특정 수 이상의 스레드를 사용하지 못하도록 제한할 수 있다.
val dispatcher = Dispatchers.Default.limitedParallelism(5)
메인 디스패처
안드로이드에서는 메인 스레드 (UI 스레드)가 가장 중요하다. 메인 스레드는 UI와 상호작용하는 데 사용하는 유일한 스레드이며, 메인 스레드가 블로킹되면 전체 애플리케이션이 멈춰버린다. 메인 스레드에서 코루틴을 실행하려면 `Dispatchers.Main`을 사용하면 된다.
안드로이드에서는 기본 디스패처로 메인 디스패처를 주로 사용한다.
IO 디스패처
`Dispatchers.IO`는 파일을 읽고 쓰는 경우, 안드로이드의 shared preference를 사용하는 경우, 블로킹 함수를 호출하는 경우처럼 I/O 연산으로 스레드를 블로킹할 때 사용하기 위해 설계되었다.
`Dispatchers.IO`는 같은 시간에 50개가 넘는 스레드(정확히는 `max(64, CPU 코어수)` )를 사용할 수 있도록 만들어졌기 때문에 다음 코드는 1초밖에 걸리지 않는다.
suspend fun main() {
val time = measureTimeMillis {
coroutineScope {
repeat(50) {
launch(Dispatchers.IO) {
Thread.sleep(1000)
}
}
}
}
println("걸린 시간 $time")
}
`Dispatchers.Default` 와 `Dispatchers.IO`는 같은 스레드 풀을 공유한다.
커스텀 스레드 풀을 사용하는 IO 디스패처
`limitedParallelism`함수는 독립적인 스레드 풀을 가진 새로운 디스패처를 만든다. 이렇게 만들어진 풀은 우리가 원하는 만큼 많은 수의 스레드 수를 설정할 수 있으므로 스레드 수가 64개로 제한되지 않는다.
스레드의 한도가 `Dispatchers.IO`를 비롯한 다른 디스패처와 무관하기 때문에 한 서비스가 다른 서비스를 블로킹하는 경우는 없다.
따라서 `limitedParallelism`을 적절히 활용하는 것이 좋겠다.
프로젝트 룸의 가상 스레드 사용하기
`JVM` 플랫폼은 프로젝트 룸이라는 새로운 기술을 발표했다.
일반적인 스레드보다 훨씬 가벼운 가상 스레드를 도입했다. 일반적인 스레드를 블로킹하는 것보다 가상 스레드를 블로킹하는 것이 비용이 훨씬 적게 든다.
스레드를 블로킹할 수밖에 없는 `Dispatchers.IO` 대신 가상 스레드를 사용할 수 있다.
디스패처 룸에서 작업을 수행한 결과 스레드에 대한 블로킹이 있을 때 기존의 스레드 보다 더 빠른 성능과 시간을 보인다.
val LoomDispatcher = Executors
.newVirtualThreadPerExecutor()
.asCoroutineDispatcher()
launch(LoomDispatcher) {
Thread.Sleep(1000)
}
제한받지 않는 디스패처
`Dispatchers.Unconfined`는 스레드를 바꾸지 않는다.
제한받지 않는 디스패처가 시작되면 시작한 스레드에서 실행이 되고, 재개되었을 때는 재개한 스레드에서 실행된다.
단위 테스트할 때 유용하며, 스레드 스위칭을 일으키지 않아 비용이 가장 저렴하다.
안드로이드의 메인 스레드에서 실행하면, 전체 애플리케이션이 블로킹된다고 한다.
메인 디스패처로 즉시 옮기기
코루틴을 배정하는 것에도 비용이 든다.
`withContext`가 호출되면 코루틴은 중단되고 큐에서 기다리다가 재개된다.
`Dispatchers.Main.immediate` 를 사용하면 메인 스레드에서 함수를 호출하면 스레드 배정 없이 즉시 실행된다.
호출하고자 하는 함수가 이미 메인 디스패처에서 호출이 되었다면 `withContext`의 인자로 `Dispatchers.Main.immediate`를 사용하는 것이 좋다.
컨티뉴에이션 인터셉터
`ContinuationInterceptor` 코루틴 컨텍스트는 코루틴이 중단되었을 때 `interceptContinuation` 메서드로 컨티뉴에이션 객체를 수정하고 포장한다. `releaseInterceptedContinuation`메서드는 컨티뉴에이션이 종료되었을 때 호출된다.
디스패처는 특정 스레드 풀에서 실행되는 `DispatchedContinuation`으로 컨티뉴에이션 객체를 래핑 하기 위해 `interceptContinuation`을 사용한다. `DispatchedContinuation`은 디스패처가 작동하는 핵심 요소이다.
작업의 종류에 따른 각 디스패처의 성능 비교
다음은 디스패처 및 스레드에 따라 평균 실행 시간을 나타낸다.
중단 | 블로킹 | CPU 집약적인 연산 | 메모리 집약적인 연산 | |
싱글스레드 | 1,002 | 100,003 | 39,103 | 94,358 |
디폴트 디스패처(스레드 8개) | 1,002 | 13,003 | 8,473 | 21,461 |
IO 디스패처(스레드 64개) | 1,002 | 2,003 | 9,893 | 20,776 |
스레드 100개 | 1,002 | 1,003 | 16,379 | 21,004 |
이로써
1. 중단에서는 스레드 수에 상관이 없다.
2. 블로킹을 할 경우 스레드 수가 많을수록 유리하다.
3. CPU 집약적인 연산에서는 `Dispatchers.Default`가 유리하다.
4. 메모리 집약적인 연산에서, 더 많은 스레드를 사용하는 것이 낫다.
요약
- `Dispatchers.Default` 는 CPU 집약적인 연산에 사용
- `Dispatchers.Main`은 Android, Swing JavaFX에서 메인 스레드에 접근할 때 사용
- `Dispatchers.Main.immediate`는 `Dispatchers.Main`이 사용하는 스레드에서 실행되지만 꼭 필요할 때만 재배정됨
- `Dispatchers.IO` 는 블로킹 연산을 할 때 사용
- 병렬 처리를 제한한 `Dispatchers.IO`나 특정 스레드 풀을 사용하는 커스텀 디스패처는 블로킹 호출이 아주 많을 때 사용
- 병렬처리를 1로 제한하면 경쟁 상태를 임시로 해결할 수 있다.
- `Dispatchers.Unconfined`는 코루틴이 실행될 스레드에 대해 신경 쓸 필요가 없을 때 사용
참고자료
마르친 모스카와(2023), 코틀린 코루틴 Kotlin Coroutines : Deep Dive - 안드로이드 및 백엔드 개발자를 위한 비동기 프로그래밍, 인사이트
https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html
'Kotlin Coroutines' 카테고리의 다른 글
[Kotlin Coroutines] 공유 상태로 인한 문제 (0) | 2024.06.03 |
---|---|
[Kotlin Coroutines] 코루틴 스코프 만들기 (0) | 2024.05.31 |
[Kotlin Coroutines] 코루틴 스코프 함수 (0) | 2024.05.20 |
[Kotlin Coroutines] 코루틴 예외 처리 (0) | 2024.05.17 |
[Kotlin Coroutines] 코루틴 취소 (0) | 2024.05.17 |