Dispatchers는 Kotlin Coroutines에서 CoroutineContext의 구성 요소로, 코루틴을 실행할 스레드를 결정합니다: Main(UI 스레드), IO(네트워크 및 디스크), Default(CPU 집약적 작업), Unconfined(현재 스레드). 각 디스패처는 특정 작업 유형에 최적화된 전문화된 스레드 풀을 관리합니다. JetBrains 가이드, 2024에 따르면 올바른 디스패처를 선택하는 것은 애플리케이션 성능과 안정성에 중요합니다.
핵심 요점
Dispatchers는 CoroutineDispatcher 인터페이스의 구현체이며 CoroutineContext의 요소입니다. 코루틴이 실행될 스레드 또는 스레드 풀을 결정합니다. launch 또는 async를 통해 코루틴을 생성할 때 디스패처를 첫 번째 매개변수로 전달할 수 있습니다: launch(Dispatchers.IO) { ... }. 디스패처가 지정되지 않으면 외부 CoroutineScope에서 상속됩니다.
Kotlin은 Main, IO, Default, Unconfined의 네 가지 내장 디스패처를 제공합니다. 각 디스패처는 특정 유형의 작업에 최적화된 자체 스레드 풀을 사용합니다. 올바른 디스패처 선택은 애플리케이션 성능을 결정합니다: 잘못된 선택은 UI 지연, 유휴 CPU 코어 또는 비효율적인 스레드 사용으로 이어집니다.
| 디스패처 | 스레드 풀 | 최대 스레드 | 용도 |
|---|---|---|---|
| Dispatchers.Main | 하나(UI) | 1 | UI 업데이트, LiveData, View |
| Dispatchers.IO | IO 풀 | 64(limitedParallelism) | 네트워크, 파일, DB |
| Dispatchers.Default | CPU 풀 | N코어 | 정렬, 파싱, 계산 |
| Dispatchers.Unconfined | 현재 스레드 | 해당 없음 | 중간 작업, 테스트 |
Dispatchers.Main은 Android 메인 스레드에서 코루틴을 실행하는 디스패처입니다. UI 관련 작업(TextView 업데이트, notifyDataSetChanged 호출, LiveData 및 StateFlow 작업)을 위해 설계되었습니다. Android에서 이 디스패처는 Handler(Looper.getMainLooper())를 통해 구현됩니다.
// Correct switch to Main for UI updates
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchData()
withContext(Dispatchers.Main) {
_uiState.value = data
}
}
코루틴이 이미 Main 디스패처에 있는 경우 추가 withContext(Dispatchers.Main)는 오버헤드를 발생시키지 않습니다 — 디스패처가 현재 스레드를 확인하고 전환을 건너뜁니다. withContext는 디스패처 간 전환에 선호되는 방법입니다.
Dispatchers.IO는 I/O 작업에 최적화된 디스패처입니다: HTTP 요청(Ktor, OkHttp), 파일 읽기 및 쓰기, Room 또는 SQLDelight 작업. 기본적으로 64스레드 풀을 사용하며 부하에 따라 확장 가능합니다. 각 새 I/O 요청은 제한에 도달할 때까지 추가 스레드를 만들 수 있습니다.
동시 I/O 작업 수를 제어하려면 limitedParallelism()을 사용하세요. 이 함수는 병렬 스레드 수에 제한이 있는 새 디스패처를 생성하여 대량 작업 중 풀 고갈을 방지합니다.
val limitedIo = Dispatchers.IO.limitedParallelism(4)
// Load 100 files with limit of 4 concurrent operations
coroutineScope {
val files = (1..100).map { index ->
async(limitedIo) {
downloadFile("file_$index")
}
}
files.awaitAll()
}
코루틴이 대기 시간을 보내는(I/O 바운드) 모든 작업에는 IO 디스패처를 사용하세요. IO 디스패처에서 CPU 집약적 작업은 비효율적입니다 — I/O용 스레드를 점유하여 시스템의 처리량을 감소시킵니다.
Dispatchers.Default는 프로세서에 부하를 주는 계산 작업을 위한 디스패처입니다: 정렬, 필터링, JSON 파싱(Moshi, Kotlinx Serialization), 이미지 처리, 계산. 풀 크기는 프로세서 코어 수와 같습니다(단 2 이상). 이는 컨텍스트 전환 없이 최대 CPU 사용률을 보장합니다.
suspend fun processData(input: List<RawRecord>): List<ProcessedRecord> {
return withContext(Dispatchers.Default) {
input
.parallelStream()
.map { transform(it) }
.toList()
}
}
I/O 작업에 Dispatchers.Default를 사용하지 마세요 — 계산 작업을 처리할 수 있는 CPU 풀의 스레드를 차단합니다. IO와 Default를 분리하면 시스템 리소스의 최적 활용이 가능합니다: IO 스레드는 I/O를 기다리고 CPU 스레드는 지속적으로 계산을 처리합니다.
Dispatchers.Unconfined는 코루틴을 어떤 풀에도 바인딩하지 않는 특수 디스패처입니다. 코루틴은 launch/async가 호출된 스레드에서 실행을 시작하고 일시 중단 후 resume을 호출한 스레드에서 재개됩니다. 이 동작은 고정 컨텍스트가 필요하지 않은 중간 작업에 적합합니다.
fun main() = runBlocking {
launch(Dispatchers.Unconfined) {
println("Before delay: ${Thread.currentThread().getName()}")
delay(500L)
println("After delay: ${Thread.currentThread().getName()}")
}
}
프로덕션 코드에서 Dispatchers.Unconfined는 거의 사용되지 않습니다. 주요 사용 사례: 다른 디스패처로 데이터를 전달하기 전의 가벼운 변환 및 테스트. 프로덕션 워크로드의 경우 명시적 디스패처를 사용하세요 — Unconfined는 실행 스레드가 resume 구현에 따라 달라지므로 예측할 수 없습니다.
디스패처 선택은 작업 유형에 따라 다릅니다: UI 작업 → Main, I/O 바운드 → IO, CPU 바운드 → Default, 중간 → 범위에서 상속. Android의 경우 메인 작업이 수행되는 디스패처에서 코루틴을 시작하고 UI를 업데이트하기 전에 withContext를 통해 Main으로 전환하는 것이 좋습니다.
복잡한 시나리오의 경우 + 연산자를 사용하여 디스패처를 결합하세요: Dispatchers.IO + SupervisorJob() + CoroutineExceptionHandler. 이렇게 하면 지정된 디스패처, 오류 처리 및 격리된 Job 계층 구조를 가진 CoroutineContext가 생성됩니다.
자주 묻는 질문
Dispatchers.IO는 I/O 바운드 작업(I/O 대기)에 최대 64스레드 풀을 사용하는 반면, Dispatchers.Default는 계산 작업에 CPU 코어 수에 기반한 풀을 사용합니다. 스레드가 부족하면 두 풀이 서로 스레드를 공유할 수 있습니다.
네, 단일 스레드용 newSingleThreadContext() 또는 고정 풀용 newFixedThreadPoolContext()를 사용하세요. 프로덕션에서는 기존 디스패처를 기반으로 limitedParallelism()을 사용하세요 — 새 풀을 만드는 것보다 효율적입니다.
Dispatchers.Main을 사용할 수 없는 경우(예: JUnit 테스트 또는 백그라운드 서비스) IllegalStateException이 발생합니다. 테스트에는 TestCoroutineDispatcher를, 백그라운드 서비스에는 Dispatchers.IO 또는 Default를 사용하세요.
Dispatchers.IO.limitedParallelism(N)을 사용하세요. 여기서 N은 최대 병렬 스레드 수입니다. 대량 요청 중 풀 고갈을 방지하고 제어된 병렬성을 제공합니다.
Dispatchers.Unconfined는 중간 작업에 적합합니다: 다른 디스패처로 전달하기 전의 가벼운 데이터 변환, 테스트 시나리오. 프로덕션 Android 코드에서는 일시 중단 후 실행 스레드가 정의되지 않아 권장되지 않습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.