약간의 내용 첨삭이 있습니다.
원문링크 : https://sandn.tistory.com/110
Dispatcher
Dispatcher는 코루틴을 특정 스레드에서 실행할 수 있도록 도와주는 기능이다.
코루틴에서는 디스패처를 이용하여 다양하게 스코프를 지정할 수 있다.
Rx류의 라이브러리에서 쓰이는 스케쥴러가 유사한 기능을 한다.
출처- https://www.slideshare.net/BartomiejOsmaek/kotlin-coroutines-the-new-async
특히 비동기 백그라운드 작업을 수행할 때 가장 많이 쓰이는 것이 IO 와 Default Dispatcher이다.
Coroutine을 처음 접하면 이 두 디스패처 중 어떤 것을 써야 하는지 헷갈릴 수 있다.
정확히 이 두 디스패처의 차이가 무엇인지 알아보자.
다음은 Default Dispatcher에 대한 공식 설명이다.
요약하자면, 해당 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개씩만 실행된다.
그렇다면 IO Dispatcher는 어떨까?
다음은 IO Dispatcher에 대한 설명이다.
kotlinx.coroutines.io.parallelism
" ([IO_PARALLELISM_PROPERTY_NAME]) system property.withContext(Dispatchers.IO) { ... }
does not lead to an actual switching to another 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개의 스레드를 풀에 생성하여 사용한다고 되어 있는데 어찌된 영문인지 스레드 번호는 64개 이상이 로그에 찍히는 것이 의아하였다.
구글링을 해보아도 아무런 단서가 나오지 않는 상황...
이해가 가지 않아 이에 대해 코틀린 공식 문서에 이슈를 남겨두었더니 공식문서가 수정되었다.
기존에는 아래 문구를 참고하여 스레드가 64개일 거라고 생각했었는데
It defaults to the limit of 64 threads or the number of cores (whichever is larger).
문서상 애매한 부분이 있었는지 아래의 설명을 문서에 추가해주었다.
- As a result of thread sharing, more than 64 (default parallelism) threads can be created (but not used)
- during operations over IO dispatcher.
즉, 스레드 번호는 초과하더라도 최대 실행 스레드는 64개로 일정하다.
공식 깃허브에 남겼던 Issue 링크를 남겨둔다.
https://github.com/Kotlin/kotlinx.coroutines/issues/2272
확인해보기 위해 대기시간이 있는 네트워크 작업을 통해 두 디스패처의 속도를 한번 비교해보자.
수십 개 정도의 간단한 크롤링 요청 작업이다.
// 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
이는 네트워크를 통한 데이터 호출의 경우 단순히 응답을 대기하고 있는 비중이 크기 때문에
스레드의 갯수가 늘어나는 만큼 자원을 더 효율적으로 사용할 수 있기 때문이다.
그렇다면 각각 어떤 목적으로 사용하는 것이 좋을까?
앞서 보았듯 IO Dispatcher 의 경우 네트워크 입출력과 같은 대기시간이 긴 작업에 적합하다.
실질적인 연산보다는 입출력을 위해 자원을 사용하지 않고 대기하는 시간이 길기 때문에 코어 수 보다 많은 스레드를 생성하여 작업을 처리하는 것이 효율적인 것이다.
반대로, 방대한 계산처럼 CPU 집약적인 연산을 수행하는 경우라면 멀티코어의 효율을 최대한 끌어낼 수 있도록 코어 수 만큼의 스레드만 사용하는 Default Dispatcher 를 사용하는 것이 적합할 것이다.