Simple&Natural

Coroutine의 IO Dispatcher와 Default Dispatcher 의 사용 시 차이 본문

안드로이드(Android)/연구 및 프로젝트

Coroutine의 IO Dispatcher와 Default Dispatcher 의 사용 시 차이

Essense 2020. 9. 26. 15:02
728x90

 

Dispatcher

Dispatcher는 코루틴을 특정 스레드에서 실행할 수 있도록 도와주는 기능이다.
코루틴에서는 디스패처를 이용하여 다양하게 스코프를 지정할 수 있다.
Rx류의 라이브러리에서 쓰이는 스케쥴러가 유사한 기능을 한다.

출처- https://www.slideshare.net/BartomiejOsmaek/kotlin-coroutines-the-new-async



특히 비동기 백그라운드 작업을 수행할 때 가장 많이 쓰이는 것이 IO 와 Default Dispatcher이다.
Coroutine을 처음 접하면 이 두 디스패처 중 어떤 것을 써야 하는지 헷갈릴 수 있다.

정확히 이 두 디스패처의 차이가 무엇인지 알아보자.

Dispatchers.Default


다음은 Default Dispatcher에 대한 공식 설명이다.

* The default [CoroutineDispatcher] that is used by all standard builders like
* [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc
* if no dispatcher nor any other [ContinuationInterceptor] is specified in their context.
*
* It is backed by a shared pool of threads on JVM. By default, the maximal level of parallelism used
* by this dispatcher is equal to the number of CPU cores, but is at least two.
* Level of parallelism X guarantees that no more than X tasks can be executed in this dispatcher in parallel.


요약하자면, 해당 Dispatcher는 JVM의 공유 스레드풀을 사용하고 동시 작업 가능한 최대 갯수는 CPU의 코어 수와 같다고 한다. (최소 2개)

만약 4코어 CPU를 사용하는 경우 최대 4개의 병렬작업이 가능하다.

확인해보기 위해 약 50개의 Coroutine을 Default Dispatcher 위에서 실행시켜 보았다.

repeat(50) { CoroutineScope(Dispatchers.Default).launch { println(Thread.currentThread().name) } }




코드의 결과는 다음과 같다.

DefaultDispatcher-worker-1 DefaultDispatcher-worker-11 DefaultDispatcher-worker-10 DefaultDispatcher-worker-9 DefaultDispatcher-worker-7 ... DefaultDispatcher-worker-7 DefaultDispatcher-worker-9 DefaultDispatcher-worker-7 DefaultDispatcher-worker-8 DefaultDispatcher-worker-11 DefaultDispatcher-worker-5 DefaultDispatcher-worker-3 DefaultDispatcher-worker-4 DefaultDispatcher-worker-10 DefaultDispatcher-worker-12 DefaultDispatcher-worker-6 DefaultDispatcher-worker-6 DefaultDispatcher-worker-1 DefaultDispatcher-worker-2 DefaultDispatcher-worker-1 DefaultDispatcher-worker-6 DefaultDispatcher-worker-1 DefaultDispatcher-worker-12 ... Process finished with exit code 0


현재 사용하고 있는 CPU의 경우 6코어 12스레드이므로 약 12개의 스레드풀이 생성된다.
출력값을 보면 1~12번 스레드 위에서 작업이 실행되고 있는 것을 볼 수 있다.
따라서 작업들은 한번에 최대 12개씩만 실행된다.

Dispatchers.IO


그렇다면 IO Dispatcher는 어떨까?

다음은 IO Dispatcher에 대한 설명이다.

* The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads.
*
* Additional threads in this pool are created and are shutdown on demand.
* The number of threads used by this dispatcher is limited by the value of
* "`kotlinx.coroutines.io.parallelism`" ([IO_PARALLELISM_PROPERTY_NAME]) system property.
* It defaults to the limit of 64 threads or the number of cores (whichever is larger).
*
* This dispatcher shares threads with a [Default][Dispatchers.Default] dispatcher, so using
* `withContext(Dispatchers.IO) { ... }` does not lead to an actual switching to another thread —
* typically execution continues in the same thread.




설명에 의하면 IO Dispathcher는 필요에 따라 추가적으로 스레드를 더 생성하거나 줄일 수 있으며 최대 64개까지 생성이 가능하다.
또한 Default Dispatcher와 스레드를 공유하기 때문에 switching으로 인한 오버헤드를 일으키지 않는다고 되어 있다.

코드를 실행해보면 다음과 같은 결과가 나온다.

... DefaultDispatcher-worker-61 DefaultDispatcher-worker-62 DefaultDispatcher-worker-3 DefaultDispatcher-worker-11 DefaultDispatcher-worker-29 DefaultDispatcher-worker-64 DefaultDispatcher-worker-21 ... DefaultDispatcher-worker-67 ...



Default Dispatcher와는 다르게 12개 이상의 스레드를 사용하는 것을 볼 수 있다.
다른 글에서 다루었지만 스레드 번호가 64를 초과해서 찍히는 것은 Default Dispatcher와 스레드를 공유하기에 발생하는 현상이다 (참고 : sandn.tistory.com/112)

스레드 번호는 초과하더라도 최대 실행 스레드는 64개로 일정하다

 

정리


그렇다면 각각 어떤 목적으로 사용하는 것이 좋을까?

IO의 경우 대기시간이 있는 네트워크 입출력 등의 작업에 적합한 반면
Default의 경우 대기시간이 없고 지속적으로 CPU의 작업을 필요로 하는 무거운 작업에 적합하다.
후자의 경우 코어 수 만큼의 스레드만 생성하여 작업하기 때문에 CPU를 많이 점유하는 작업에서 최대의 효율을 내기 때문이다.

확인해보기 위해 대기시간이 있는 네트워크 작업을 통해 두 디스패처의 속도를 한번 비교해보자.
수십 개 정도의 간단한 크롤링 요청 작업이다.

// IO Dispatcher 2020-09-26 13:38:07.404 21550-21638/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 3620 ms 2020-09-26 13:38:17.391 21550-21720/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 3433 ms 2020-09-26 14:58:48.877 22750-22855/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 4024 ms 2020-09-26 14:59:15.670 22927-23000/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 4071 ms 2020-09-26 14:59:34.632 23069-23126/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 3694 ms

 

// Default Dispatcher 2020-09-26 14:58:05.095 22506-22552/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 4764 ms 2020-09-26 14:58:26.333 22620-22664/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 4489 ms 2020-09-26 14:59:59.606 23225-23292/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 4642 ms 2020-09-26 15:00:18.112 23353-23396/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 5155 ms 2020-09-26 15:00:34.161 23467-23511/com.ondarm.android.newsreaders D/MyTime: 걸린시간: 5019 ms


동일한 크롤링 작업을 5번 정도 반복한 결과값이다.
IO 의 경우 Default 에 비해 평균 1~2초 정도 빠른 처리 속도를 보여준다.
이는 대기시간이 있는 네트워크 작업의 경우 더 많은 스레드를 사용하여 병렬처리를 하는 것이 효율적이기 때문이다.

단, 위에서 언급했듯이 이는 작업의 성격에 따라 다른 것이고 무조건 스레드가 많다고 속도가 빠른 것은 아니다.
(네트워크 작업의 경우 실질적으로 CPU를 이용하는 시간보다 응답 대기하고 있는 시간이 길기 때문)

 

채굴이나 계산과 같이 연속적인 CPU 연산작업을 수행하는 경우 Default를 사용하는 것이 더 적합하다.


참고자료

1. Coroutine 공식 문서

2. stackoverflow.com/questions/59039991/difference-between-usage-of-dispatcher-io-and-default

 

Difference between usage of Dispatcher IO and Default

In this question: Kotlin Coroutines choosing Dispatcher we can understand to use Dispatcher.Default on CPU process, like an image/video conversion and Dispatcher.IO when writing/reading files or API

stackoverflow.com

 

728x90