반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[코루틴의 정석] 3장. CoroutineDispatcher 본문

Kotlin/코루틴의 정석

[코루틴의 정석] 3장. CoroutineDispatcher

어리둥절범고래 2024. 8. 18. 16:33
반응형

Dispather는 '보내다'라는 뜻의 dispatch에 -er이 붙어 '무언가를 보내는 주체'라는 뜻이다.

따라서 여기에 코루틴이 더해진 CoroutineDispatcher는 코루틴을 보내는 주체가 된다.

 

 

CoroutineDispater는..

 

  • 코루틴을 스레드로 보내 실행시키는 역할을 한다.
  • 코루틴을 스레드로 보내는 데 사용할 수 있는 스레드나 스레드풀을 가진다.
  • 코루틴을 실행 요청한 스레드에서 코루틴이 실행되도록 만들 수 있다.
  • 코루틴의 실행을 관리하는 주체이다.

 

3.1.1 CoroutineDispatcher의 동작 살펴보기

 

 

위와 같이 2개의 스레드로 구성된 스레드풀을 사용할 수 있는 CoroutineDispater 객체가 있다고 가정했을때, 순서는 다음과 같다.

 

  1. CoroutineDispatcher 객체에 Coroutine2 코루틴의 실행을 요청한다.
  2. CoroutineDispatcher 객체는 실행 요청을 받은 코루틴을 작업 대기열에 적재한다.
  3. CoroutineDispatcher 객체는 자신이 사용할 수 있는 스레드가 있는지 확인한다.
  4. Thread-2 스레드를 사용할 수 있으므로 코루틴을 해당 스레드로 보내 실행시킨다.

 

 

모든 스레드를 코루틴이 점유하고 있는 상황에서 CoroutineDispatcher 객체의 동작 순서는 다음과 같다.

 

 

  1. CoroutineDispatcher 객체에 Coroutine2 코루틴의 실행을 요청한다.
  2. CoroutineDispatcher 객체는 실행 요청을 받은 코루틴을 작업 대기열에 적재한다.
  3. 현재 모든 스레드가 코루틴을 실행 중이어서 CoroutineDispatcher 객체는 사용할 수 있는 스레드가 없다. 따라서 Coroutine3 코루틴을 스레드로 보내지 못하고 작업 대기열에서 대기한다.
  4. 스레드풀의 스레드 중 하나가 자유로워졌을 때 Coroutine3 코루틴이 스레드로 보내진다.

 

 


3.2 제한된 디스패처와 무제한 디스패처

 

CoroutineDispatcher에는 두 가지 종류가 있다.

 

  • 제한된 디스패처(Confined Dispatcher): 사용할 수 있는 스레드나 스레드풀이 제한된 디스패처
  • 무제한 디스패처(Unconfined Dispatcher): 사용할 수 있는 스레드나 스레드풀이 제한되지 않은 디스패처

 

제한된 디스패처(Confined Dispatcher)

 

  • 일반적으로 CoroutineDispatcher 객체별로 어떤 작업을 처리할지 미리 역할을 부여하고 역할에 맞춰 실행을 요청한다.


무제한 디스패처(Unconfined
 Dispatcher)

 

  • 무제한 디스패처는 실행 요청된 코루틴이 이전 코드가 실행되던 스레드에서 계속해서 실행되도록 한다.
  • 이 때문에 실행되는 스레드가 매번 달라질 수 있고, 특정 스레드로 제한되어 있지 않는다.

 

 

 

3.3 제한된 디스패처 생성하기


멀티 스레드 디스패처 만들기

val multiThreadDispatcher: CoroutineDispatcher = newFixedThreadPoolContext(nThreads = 2, name = "MultiThread")

단일 스레드 디스패처 만들기

val dispatcher: CoroutineDispatcher = newSingleThreadContext(name = "SingleThread")

 

newSingleThreadContext 함수는 내부적으로 newFixedThreadPoolContext 함수를 사용한다.

pubilc fun newSingleThreadContext(name: String): CloseableCoroutineDispatcher = newFixedThreadPoolContext(1, name)

 

즉, 두 함수는 같은 함수라고 봐도 무방하다.

 

 

3.4 CoroutineDispatcher 사용해 코루틴 실행하기

 

3.4.1 launch의 파라미터로 CoroutineDispatcher 사용하기


3.4.1.1 단일 스레드 디스패처 사용해 코루틴 실행하기

 

launch 함수의 context 인자로 CoroutineDispatcher 객체를 넘기면 된다.

fun main() = runBlocking<Unit> {
    val dispatcher = newSingleThreadContext(name = "SingleThread")
    launch(context = dispatcher) {
        println("[${Thread.currentThread().name}] 실행") // [SingleThread] 실행
    }
}


3.4.1.2 멀티 스레드 디스패처 사용해 코루틴 실행하기

fun main() = runBlocking<Unit> {
    val multiThreadDispatcher = newFixedThreadPoolContext(nThreads = 2, name = "MultiThread")
    launch(context = multiThreadDispatcher) {
        println("[${Thread.currentThread().name}] 실행") // [MultiThread-1] 실행
    }
    launch(context = multiThreadDispatcher) {
        println("[${Thread.currentThread().name}] 실행") // [MultiThread-2] 실행
    }
}




3.4.2 부모 코루틴의 CoroutineDispatcher 사용해 자식 코루틴 실행하기


코루틴은 구조화를 제공해 코루틴 내부에서 새로운 코루틴을 실행할 수 있으며, 이를 통해 부모-자식 관계가 성립된다.

구조화는 코루틴을 계층 관계로 만드는 것뿐만 아니라 부모 코루틴의 실행 환경을 자식 코루틴에게 전달하는 데도 사용된다.

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}] 자식 코루틴 실행")
            launch(context = multiThreadDispatcher) { // 자식 코루틴
                println("[${Thread.currentThread().name}] 자식 코루틴 실행")
            }
        }
    }
}

/*
[MultiThread-1] 부모 코루틴 실행
[MultiThread-2] 자식 코루틴 실행
[MultiThread-1] 자식 코루틴 실행
 */

 

 

 

3.5 미리 정의된 CoroutineDispatcher

 

사용자들은 매번 새로운 CoroutineDispatcher를 만들 필요 없이 미리 정의된 CoroutineDispatcher 객체를 사용해 코루틴을 실행하면 된다.

 

  • Dispatchers.IO: 네트워크 요청, 파일 입출력 작업을 위한 CoroutineDispatcher
  • Dispatchers.Default: CPU를 많이 사용하는 연산 작업을 위한 CoroutineDispatcher
  • Dispatchers.Main: 메인 스레드를 사용하기 위한 CoroutineDispatcher

 


3.5.1 Dispatchers.IO

 

멀티 스레드 프로그램이이 가장 많이 사용되는 작업은 입출력(IO) 작업이다.

Dispatchers.IO는 싱글톤 인스턴스이며, 최대 스레드의 수는 JVM에서 사용이 가능한 프로세서의 수와 64 중 큰 값으로 설정돼 있다.

fun main() = runBlocking<Unit> {
    launch(Dispatchers.IO) {
        println("[${Thread.currentThread().name}] 코루틴 실행")
    }
}

/*
[DefaultDispatcher-worker-1] 코루틴 실행
Dispatchers.IO는 공유 스레드풀의 스레드를 사용할 수 있도록 구현됐기 때문에
DefaultDispatcher-worker-1 스레드에 코루틴이 할당돼 실행된다.
 */

 

 


3.5.2 Dispatchers.Default

 

대용량 데이터를 처리하는 CPU 연산 작업을 CPU 바운드 작업이라고 한다.

Dispatchers.Default는 싱글톤 인스턴스이며, CPU 바운드 작업이 필요할 때 사용한다.

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Default) {
        println("[${Thread.currentThread().name}] 코루틴 실행")
    }
}

/*
[DefaultDispatcher-worker-1] 코루틴 실행
 */

 

 

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

 

입출력 작업과 CPU 바운드 작업은 스레드를 지속적으로 사용하는지에 대한 차이가 있다.

입출력 작업은 작업을 실행한 후 결과를 반환받을 때까지 스레드를 사용하지 않는다.

CPU 바운드 작업은 작업을 하는 동안 스레드를 지속적으로 사용한다.

이 차이로 인해 스레드 기반 작업을 사용해 실행됐을 때와 코루틴을 사용해 실행됐을 때 효율성에 차이가 생긴다.

 



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

 

Dispatchers.Default를 사용해 무겁고 오래 걸리는 연산을 처리할 경우, 해당 연산을 처리하기 위해 모든 스레드를 사용하는 동안 다른 연산이 실행되지 못한다.

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

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Default.limitedParallelism(2)) {
        repeat(10) {
            launch {
                println("[${Thread.currentThread().name}] 코루틴 실행")
            }
        }
    }
}

/*
[DefaultDispatcher-worker-2] 코루틴 실행
[DefaultDispatcher-worker-2] 코루틴 실행
[DefaultDispatcher-worker-1] 코루틴 실행
[DefaultDispatcher-worker-2] 코루틴 실행
[DefaultDispatcher-worker-1] 코루틴 실행
[DefaultDispatcher-worker-2] 코루틴 실행
[DefaultDispatcher-worker-1] 코루틴 실행
[DefaultDispatcher-worker-2] 코루틴 실행
[DefaultDispatcher-worker-2] 코루틴 실행
[DefaultDispatcher-worker-1] 코루틴 실행
 */

 



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

 

Dispatchers.IO와 Dispatchers.Default를 사용한 예제 실행 결과를 보면 DefaultDispatcher-worker-1인 것을 볼 수 있다.

이는 Dispatchers.IO와 Dispatchers.Default가 같은 스레드풀을 사용한다는 것을 의미한다.

코루틴 라이브러리는 스레드의 생성과 관리를 효율적으로 할 수 있도록 애플리케이션 레벨의 공유 스레드풀을 제공한다.

 

 

추가 자료, Dispatchers.IO의 limitedParallelism

 

  • Dispatchers.IO의 limitedParallelism 함수는 공유 스레드 풀의 스레드로 구성된 새로운 스레드 풀을 만들어내며, 스레드의 수 제한이 없다.
  • Dispatchers.IO의 limitedParallelism 함수는 특정한 작업이 다른 작업에 영향을 받지 않아야할 때 사용돼야 한다.



3.5.5 Dispatchers.Main

 

Dispatchers.Main은 UI가 있는 애플리케이션에서 메인 스레드의 사용을 위해 사용되며, 별도 라이브러리(kotlinx-coroutines-android 등)을 추가해야 이 객체를 사용할 수 있다.

 

 

 

 


 

요약

 

  • CoroutineDispatcher 객체는 코루틴을 스레드로 보내 실행하는 객체이다. 코루틴을 작업 대기열에 적재한 후 사용이 가능한 스레드로 보내 실행한다.
  • 제한된 디스패처는 코루틴을 실행하는 데 사용할 수 있는 스레드가 특정 스레드 또는 스레드풀로 제한되지만 무제한 디스패처는 코루틴을 실행하는 데 사용할 수 있는 스레드가 제한되지 않는다.
  • newSingleThreadContext 및 newFixedThreadPoolContext 함수를 사용해 제한된 디스패처 객체를 생성할 수 있다.  
  • launch 함수를 사용해 코루틴을 실행할 때 context 인자로 Coroutine Dispatcher 객체를 넘기면 해당 CoroutineDispatcher 객체를 사용해 코루틴이 실행된다.
  • 자식 코루틴은 기본적으로 부모 코루틴의 CoroutineDispatcher 객체를 상속받아 사용한다. 
  • 코루틴 라이브러리는 미리 정의된 CoroutineDispatcher 객체인 Dispatchers.IO, Dispatchers.Default, Dispatchers.Main을 제공한다.
  • Dispatchers.IO는 입출력 작업을 위한 CoroutineDispatcher 객체로 네트워크 요청이나 파일 I/0 등에 사용된다.
  • Dispatchers.Default는 CPU 바운드 작업을 위한 CoroutineDispatcher 객체로 대용량 데이터 처리 등을 하는 데 사용된다. 
  • limitedParallelism 함수를 사용해 특정 연산을 위해 사용되는 Dispatchers.Default의 스레드 수를 제한할 수 있다. 
  • Dispatchers.IO와 Dispatchers.Default는 코루틴 라이브러리에서 제공하는 공유 스레드풀을 사용한다. 
  • Dispatchers.Main은 메인 스레드에서 실행돼야 하는 작업에 사용되는  CoroutineDispatcher 객체로 Dispatchers.Main을 사용하기 위해서는 별도의 라이브러리를 추가해야 한다. 
  • Dispatchers.Main은 일반적으로 UI가 있는 애플리케이션에서 UI를 업데이트하는 데 사용된다.  
반응형