Dispatcher란?
- dispatch와 -er의 합성어
- dispatch의 `보내다`라는 뜻에 -er이 붙어 `무언가를 보내는 주체`라는 뜻
CoroutineDispatcher
- `코루틴을 보내는 주체`
- 코루틴은 일시 중단이 가능한 `작업`이기 때문에 쓰레드가 있어야 실행될 수 있음
- CoroutineDispatcher는 코루틴을 쓰레드로 보내 실행시키는 역할
- 코루틴을 쓰레드로 보내는 데 사용할 수 있는 쓰레드나 쓰레드 풀 가짐
- 코루틴을 실행 요청한 쓰레드에서 코루틴이 실행되도록 만들 수 있음
CoroutineDispatcher 동작 과정
- CoroutineDispatcher 객체에 코루틴의 실행이 요청됨
- CoroutineDispatcher 객체는 실행 요청받은 코루틴을 작업 대기열에 적재시킴
- CoroutineDispatcher 객체는 자신이 사용 가능한 쓰레드가 있는지 확인
- 작업 대기열에 적재된 코루틴을 사용 가능한 쓰레드로 보내 실행
* 사용 가능한 쓰레드가 없을 경우 작업 대기열에서 대기
제한된 디스패처와 무제한 디스패처
- CoroutineDispatcher에는 두 가지 종류가 존재
- 제한된 디스패처 (Confined Dispatcher)
- 무제한 디스패처 (Unconfined Dispatcher)
1. 제한된 디스패처
- 사용할 수 있는 쓰레드나 쓰레드 풀이 제한된 디스패처
- 앞서 `CoroutineDispatcher 동작 과정`에서 설명한 대로 동작함
- 일반적으로 CoroutineDispatcher 객체별로 어떤 작업을 처리할지 미리 역할을 부여하고 역할에 맞춰 실행을 요청하는 것이 효율적이기 때문에 대부분의 CoroutineDispatcher 객체는 제한된 디스패처
2. 무제한 디스패처
- 사용할 수 있는 쓰레드나 쓰레드 풀이 제한되지 않은 디스패처
- 실행할 수 있는 쓰레드가 제한되지 않았다고 해서 실행 요청된 코루틴이 아무 쓰레드에서나 실행되는 것은 아님
- 무제한 디스패처는 실행 요청된 코루틴이 이전 코드가 실행되던 쓰레드에서 계속해서 실행되도록 처리
- 이 때문에 실행되는 쓰레드가 매번 달라질 수 있고 특정 쓰레드로 제한되어 있지 않음
제한된 디스패처 생성하는 방법
- 코루틴 라이브러리는 사용자가 직접 제한된 디스패처를 만들 수 있도록 몇 가지 함수를 제공
1. 단일 쓰레드 디스패처 생성
- 사용할 수 있는 쓰레드가 하나인 CoroutineDispatcher 객체
- 코루틴 라이브러리에서 제공하는 `newSingleThreadContext` 함수를 사용해 생성 가능
- `newFixedThreadPoolContext` 함수에 쓰레드 개수 인자로 1을 전달하면 동일하게 생성 가능
2. 멀티 쓰레드 디스패처 생성
- 두 개 이상의 쓰레드를 사용할 수 있는 CoroutineDispatcher 객체
- 코루틴 라이브러리의 newFixedThreadPoolContext 함수를 사용해 생성 가능
- 만들어지는 쓰레드들은 인자로 받은 name 값 뒤에 `-1`부터 시작해 숫자가 하나씩 증가하는 형식으로 명명
CoroutineDispatcher 사용해 코루틴 실행하는 방법
1. launch의 파라미터로 CoroutineDispatcher 사용
- launch 함수를 호출해 만든 코루틴을 특정 CoroutineDispatcher 객체에 실행 요청하기 위해서는 launch 함수의 context 인자로 CoroutineDispatcher 객체를 넘기면 됨
2. 부모 코루틴의 CoroutineDispatcher을 사용해 자식 코루틴 실행
- 코루틴은 구조화를 제공해 코루틴 내부에서 새로운 코루틴 실행 가능
- 바깥쪽의 코루틴을 부모 코루틴(Parent Coroutine)이라고 지칭
- 내부에서 생성되는 새로운 코루틴을 자식 코루틴(Child Coroutine)이라고 지칭
- 자식 코루틴에 CoroutineDispatcher 객체가 설정되지 않았을 경우 부모 코루틴의 CoroutineDispatcher 객체를 사용
부연 설명
- 자식 코루틴에는 별도 CoroutineDispatcher 객체가 설정돼 있지 않으므로 부모 코루틴에 설정된 CoroutineDispatcher 객체를 사용
- 부모 코루틴과 자식 코루틴이 모두 같은 CoroutineDispatcher 객체를 사용하므로 MultiThread-1과 MultiThread-2를 공용으로 사용하는 것을 볼 수 있음
미리 정의된 CoroutineDispatcher
- newFixedThreadPoolContext 함수를 사용해 직접 CoroutineDispatcher 객체를 생성할 경우 특정 CoroutineDispatcher 객체에서만 사용되는 쓰레드들이 생성되며, 쓰레드 풀에 속한 쓰레드의 수가 너무 적거나 많이 생성되어 비효율적으로 동작할 확률 존재
- 코루틴 라이브러리에서 제공되는 CoroutineDispatcher 객체들은 멀티 쓰레드 프로그래밍이 필요한 일반적인 상황에 맞춰 만들어졌기 때문에 사용자들이 매번 새로운 CoroutineDispatcher 객체를 만들 필요 없이 제공되는 CoroutineDispatcher 객체를 사용해 코루틴을 실행할 수 있도록 미리 정의된 (제한된) CoroutineDispatcher들의 목록을 다음과 같이 제공함
- Dispatchers.IO
- Dispatchers.Default
- Dispatcher.Main
1. Dispatchers.IO
- 멀티 쓰레드 프로그래밍이 가장 많이 사용되는 작업은 I/O 작업
- ex) 애플리케이션 내 네트워크 통신을 위해 HTTP 요청을 하거나 DB 작업 같은 입출력 작업 여러 개를 동시에 수행하기 위해서는 많은 쓰레드 필요
- 코루틴 라이브러리에서는 입출력 작업을 위해 미리 정의된 Dispatchers.IO를 제공
- 코루틴 라이브러리 1.7.2 버전을 기준으로 Dispatchers.IO가 최대로 사용할 수 있는 쓰레드의 수는 {JVM에서 사용이 가능한 프로세서의 수}와 64 중 더 큰 값으로 설정 가능
- Dispatchers.IO는 싱글톤 인스턴스이므로 launch 함수의 인자로 곧바로 넘겨 사용 가능
부연 설명
- DefaultDispatcher-worker가 쓰레드명의 접두사로 붙은 쓰레드는 코루틴 라이브러리에서 제공하는 공유 쓰레드 풀에 속한 쓰레드
- Dispatchers.IO와 Dispatchers.Default는 공유 쓰레드 풀의 쓰레드를 사용할 수 있도록 구현되어 있음
2. Dispatchers.Default
- 대용량 데이터를 처리해야 하는 작업처럼 CPU 연산이 필요한 작업도 존재하며 이러한 작업을 CPU 바운드 작업이라고 지칭
- CPU 바운드 작업은 작업을 하는 동안 쓰레드를 지속적으로 사용하므로 쓰레드 기반 작업을 사용해 실행했을 때와 코루틴을 사용해 실행했을 때와 처리 속도면에 큰 차이 없음
- 반면, IO 작업의 경우 작업을 실행한 후 결과를 반환받을 때까지 쓰레드 점유를 하지 않기 때문에 코루틴 사용할 경우 처리 속도면에서 유의미하게 차이가 나는 것을 확인 가능
- Dispatchers.Default는 CPU 바운드 작업이 필요할 때 사용하는 CoroutineDispatcher
- Dispatchers.Default도 그 자체로 싱글톤 인스턴스이므로 Dispatchers.IO와 마찬가지로 launch 함수의 인자로 곧바로 넘겨 사용 가능
2.1 limitedParallelism을 통해 Dispatchers.Default 쓰레드 사용 제한
- Dispatchers.Default를 사용해 무겁고 오래 걸리는 연산을 처리할 경우 특정 연산을 위해 Dispatchers.Default의 모든 쓰레드가 사용될 수도 있으며 이 경우 해당 연산이 모든 쓰레드를 사용하는 동안 Dispatchers.Default를 사용하는 다른 연산이 실행되지 못함
- 이를 방지하기 위해 코루틴 라이브러리는 Dispatchers.Default의 일부 쓰레드만 사용해 특정 연산을 실행할 수 있도록 limitedParallelism 함수 지원
부연 설명
- Dispatchers.Default의 여러 쓰레드 중 두 개의 쓰레드만 사용해 10개의 코루틴을 실행시키도록 limitedParallelism 적용
3. Dispatchers.Main
- 일반적으로 UI가 있는 애플리케이션에서 메인 쓰레드의 사용을 위해 사용되는 특별한 CoroutineDispatcher 객체
- Dispatchers.IO나 Dispatchers.Default와는 성격이 다름
- 코루틴 라이브러리에 대한 의존성만 추가한다고 해서 Dispatchers.Main을 사용할 수 없고 별도 라이브러리인 `kotlinx-coroutines-android` 등을 추가해야 사용 가능
비고
1. 공유 쓰레드 풀
- 앞서 언급했다시피 Dispatchers.IO와 Dispatchers.Default가 같은 쓰레드 풀인 공유 쓰레드 풀을 사용하므로 코루틴을 실행시킨 쓰레드의 접두사가 DefaultDispatcher-worker인 것을 확인 가능
- 코루틴 라이브러리는 쓰레드의 생성과 관리를 효율적으로 할 수 있도록 애플리케이션 레벨의 공유 쓰레드 풀 제공
- 쓰레드를 무제한으로 생성 가능
- 코루틴 라이브러리는 공유 쓰레드 풀에 쓰레드를 생성하고 사용할 수 있도록 API 제공
- newFixedThreadPoolContext 함수로 만들어진 디스패처가 자신만 사용할 수 있는 전용 쓰레드 풀을 생성하는 것과 다르게 Dispatchers.IO와 Dispatchers.Default는 공유 쓰레드 풀의 쓰레드를 사용
2. Dispatchers.IO의 limitedParallelism
- Dispatchers.Default에서 limitedParllelism 함수를 사용하면 위 사진처럼 Dispatchers.Default가 사용할 수 있는 쓰레드 중 일부만을 사용할 수 있음
- Dispatchers.IO의 limitedParallelism 함수는 공유 쓰레드 풀의 쓰레드로 구성된 새로운 쓰레드 풀을 생성하며, 만들어낼 수 있는 쓰레드에 제한이 있는 Dispatchers.IO나 Dispatchers.Default와 달리 쓰레드의 수를 제한 없이 만들 수 있음
- 특정한 작업이 다른 작업에 영향을 받지 않도록 별도 쓰레드 풀에서 실행되는 것이 필요할 때 사용
- 별도 쓰레드 풀을 생성하는 것은 비싼 작업이므로 남용 X
참고
코틀린 코루틴의 정석 (조세영 저)
반응형