본 글은 조세영님의 <코틀린 코루틴의 정석>을 읽고 이를 바탕으로 작성되었습니다.
CoroutineDispatcher
💡 코루틴의 실행을 관리하는 주체로, 자신에게 실행 요청된 코루틴들을 작업 대기열에 적재하고, 자신이 사용할 수 있는 스레드가 새로운 작업을 실행할 수 있는 상태라면 스레드로 코루틴을 보내 실행될 수 있게 만든다.
디스패처의 종류
제한된 디스패처 (본 게시물이 다루는)
- 사용할 수 있는 스레드나 스레드풀이 제한된 디스패처
무제한 디스패처
- 사용할 수 있는 스레드나 스레드풀이 제한되지 않은 디스패처
- 실행요청된 코루틴이 이전 코드가 실행되던 스레드에서 계속해서 실행되도록 한다
코루틴 생성하고 사용하기
생성 : 제한된 디스패처
단일 스레드 디스패처
val dispatcher: CoroutineDispatcher = newSingleThreadContext(name = "SingleThread")
멀티 스레드 디스패처
val multiThreadDispatcher: CoroutineDispatcher = newFixedThreadPoolContext(
nThreads = 5,
name = "MultiThread"
)
위에서 dispatcher를 이용해 코루틴을 생성하는 법을 알아봤으니, 이제 사용해 실행하는 방법을 알아보자
실행 : launch의 파라미터로 Dispatcher를 사용
fun main() = runBlocking<Unit> {
val multiThreadDispatcher = newFixedThreadPoolContext(
nThreads = 2,
name = "MultiThread"
)
launch(context = multiThreadDispatcher) {
println("[${Thread.currentThread().name}] 실행")
}
launch(context = multiThreadDispatcher) {
println("[${Thread.currentThread().name}] 실행")
}
실행 : 부모 코루틴의 Dispatcher를 사용해 자식 코루틴 실행
코루틴은 구조화를 제공해 내부에서 새로운 코루틴을 실행할 수 있다. 이때 바깥이 부모 코루틴, 내부가 자식 코루틴이다
- 부모 코루틴에서 launch해 자식 코루틴을 추가로 생성하면, 자식 코루틴에 별도 디스패처가 설정되어있지 않으면 부모 코루틴의 Coroutinedispatcher 객체를 사용한다.
fun main() = runBlocking<Unit> {
val multiThreadDispatcher = newFixedThreadPoolContext(
nThreads = 2,
name = "MultiThread"
)
launch(multiThreadDispatcher) { // 부모 코루틴
println("[${Thread.currentThread().name}] 부모 코루틴")
launch { // 자식 코루틴 실행
println("[${Thread.currentThread().name}] 자식 코루틴")
}
launch { // 자식 코루틴 실행
println("[${Thread.currentThread().name}] 자식 코루틴")
}
}
}
/*
// 결과:
[MultiThread-1 @coroutine#2] 부모 코루틴
[MultiThread-2 @coroutine#3] 자식 코루틴
[MultiThread-1 @coroutine#4] 자식 코루틴
*/
부모 코루틴에서 2개의 코루틴이 생성되는데, 이들이 자식 코루틴이 되고 이때 별도의 디스패처가 없으므로 부모를 따른다
하지만 이때 위 예제처럼 newFixedThreadPoolContext를 사용해 dispatcher를 직접 생성하면 협업할 시 이미 디스패처가 메모리 상에 있는데 모르고 또 생성한다던지, 특정 디스패처 안에서만 사용되는 스레드 풀이 생성될 수 있다던지 등의 비용적, 효율적 문제가 남아있다.
이렇게 개발자가 직접 생성하지 않게 하도록 코루틴은 3가지의 제한된 디스패처를 미리 제공한다.
미리 정의된 CoroutineDispatcher
코루틴은 아래와 같이 IO, Default, Main 세가지의 미리 정의된 제한된 디스패처를 제공한다.
Dispatchers.IO
네트워크 요청이나 파일 입출력 등의 입출력 작업을 위한 코루틴 디스패처
- JVM에서 사용이 가능한 프로세서 수와 64 중 큰 값으로 설정되어있음 → 여러 입출력 작업이 동시에 가능하다
- 싱글톤이므로 launch의 인자로 바로 사용할 수 있다
Dispatchers.Default
CPU를 많이 사용하는 연산 작업(CPU 바운드 작업)을 위한 코루틴 디스패처
- CPU 바운드 작업은 작업을 하는 동안 스레드를 지속적으로 사용한다
- (사실 그래서 이건 스레드 기반 작업을 사용해 실행해도 속도가 큰 차이없다)
limitedParallelism
- 무겁고 오래걸리는 특정 연산이 모든 스레드를 사용하는 동안 Dispatchers.Default를 사용하는 다른 연산 실행 불가하다
- 이를 방지 하기 위해 limitedParallelism(5) 이렇게 특정 개수를 지정해 스레드를 사용할 수 있도록 할 수 있다
- 이때 사용되는 5개의 스레드는 Dispatchers.Default의 스레드 안의 스레드들이다.
Dispatchers.Main
메인 스레드를 사용하기 위한 코루틴 디스패처
- UI가 있는 어플리케이션에서 UI를 업데이트하기 위해 메인 스레드의 사용을 위해 사용되는 특별한 CoroutineDispatcher 객체이다
- 참조만 가능하고 사용하면 ISE 오류가 난다
공유 스레드 풀을 사용하는 Dispatchers.IO, Dispatchers.Default
코루틴 라이브러리는 스레드의 생성과 관리를 효율적으로 할 수 있도록 어플리케이션 레벨의 공유 스레드풀을 제공한다.
이 공유 스레드풀에서는 스레드를 무한으로 생성할 수 있고, 코루틴 라이브러리는 공유 스레드풀에 스레드를 생성하고 사용할 수 있는 API를 제공
→ Dispatchers.IO와 Dispatchers.Default는 모두 이 API를 통해 구현됐기 때문에 같은 스레드풀을 사용한다
→ 따라서 newFixedThreadPoolContext로 생성되는 디스패처가 사용하는 스레드풀은 그 전용이고, 위 둘은 같은 스레드풀을 사용한다
따라서 위의 공유 스레드풀을 그림으로 그려보면 다음과 같다
- Dispatchers.IO.limitedParalleism은 Default와 다르게 공유 스레드 풀안에서 IO, Default와 모두 관련없는 스레드로 구성된다.
'코틀린 > Kotlin Coroutine' 카테고리의 다른 글
[Kotlin][Coroutine] Flow의 이해 (0) | 2024.07.06 |
---|---|
[Kotlin][Coroutine] CoroutineContext (0) | 2024.07.02 |
[Kotlin][Coroutine] Async, Await, Deferred, withContext (0) | 2024.07.02 |
[Kotlin][Coroutine] CoroutineBuilder와 Job (0) | 2024.06.25 |
[Kotlin][Coroutine] 멀티스레드와 코루틴 (0) | 2024.05.18 |