[Coroutine] 코루틴 컨텍스트
업데이트:
디스패치/디스패쳐
디스패치란 다중 프로그래밍 시스템에서 다음에 처리될 작업을 선택하여 실행시키는 것. 즉, 대기열에서 기다리고 있는 프로세스/스레드를 선택하여 중앙 처리 장치의 사용 권한을 부여하는 작업을 뜻합니다. 따라서 디스패쳐란 위 디스패치의 역할을 수행하는 주체라고 보시면 됩니다. 디스패치/디스패쳐라는 용어는 컴퓨터에 자주 쓰이기 때문에 꼭 기억을 합시다.
코루틴 디스패쳐
코틀린에서는 스레드와 스레드 풀을 쉽게 만들 수 있지만 직접 엑세스하거나 제어하지 않는다는 점을 알아야 한다. 코루틴 디스패쳐의 역할은 기본적으로 가용성, 부하, 설정을 기반으로 스레드 간에 코루틴을 분산하는 오케스트레이터
이다.
쉽게 말해 코루틴을 실행하고자 하는 쓰레드를 지정한다. 쓰레드는 단일 쓰레드가 될 수도 있고 멀티 쓰레드가 될 수도 있다.
코루틴 컨텍스트
코루틴은 항상 컨텍스트 안에서 실행된다. 컨텍스트는 코루틴이 어떻게 실행되고 동작해야 하는지를 정의할 수 있게 해주는 요소들의 그룹이다.
코루틴 컨텍스트를 구성하고 있는 대표적인 요소는 다음과 같다.
- 디스패쳐 (쓰레드)
- 예외 처리기(Exception Handler)
- 코루틴 네임
코루틴을 시작할 때 특정 디스패쳐 사용하기
아래 코드는 기본 디스패쳐를 사용하고 있다.
fun main() {
runBlocking {
val task = launch {
printCurrentThread()
}
task.join()
}
}
printCurrentThread() 는 현재 스레드의 이름을 출력한다.
fun printCurrentThread () {
println("Running in the thread [${Thread.currentThread().name}]")
}
위 코드를 실행하면 아래의 결과를 출력한다.
> Task :TestKt.main()
Running in the thread [main @coroutine#2]
단일 스레드 디스패쳐
항상 코루틴이 특정 스레드 안에서 실행된다는 것을 보장한다. 이 유형의 디스패쳐를 생성하려면 newSingleThreadContext() 를 사용해야 한다.
fun main() {
runBlocking {
val dispatcher = newSingleThreadContext(name = "ServiceCall")
val task = GlobalScope.launch(dispatcher) {
printCurrentThread()
}
task.join()
}
}
위 코드를 실행하면 아래의 결과를 출력하는 것을 확인할 수 있다.
> Task :TestKt.main()
Running in the thread [ServiceCall @coroutine#2]
스레드 풀 디스패쳐
스레드 풀을 갖고 있으며 해당 풀에서 가용한 스레드에서 코루틴을 시작하고 재개한다. 런타임이 가용한 스레드를 정하고 부하 분산을 알아서 결정하기 때문에, 따로 할 작업은 없다
fun main() {
val dispatcher = newFixedThreadPoolContext(4, "mypool")
runBlocking {
GlobalScope.launch(dispatcher){
println("start in ${Thread.currentThread().name}")
delay(1000)
println("resume in ${Thread.currentThread().name}")
}.join()
}
}
위 코드는 보통 다음과 같이 출력된다.
start in mypool-1
resume in mypool-2
예외 처리
코루틴 컨텍스트의 또 다른 중요한 용도는 예외를 처리할 수 있도록 핸들러를 정의하고 등록할 수 있다. 예외 처리 핸들러는 CoroutineExceptionHanlder 를 구현해 만들 수 있다.
runBlocking {
val handler = CoroutineExceptionHandler { context, throwable ->
println("Error captured in $context")
println("Message: ${throwable.message}")
}
GlobalScope.launch(handler) {
TODO("Not implement yet!")
}
delay(500)
}
위 코드를 실행하면 아래와 같은 결과가 출력이 된다.
runBlocking {
val handler = CoroutineExceptionHandler { context, throwable ->
println("Error captured in $context")
println("Message: ${throwable.message}")
}
GlobalScope.launch(handler) {
TODO("Not implement yet!")
}
delay(500)
}
컨텍스트 조합
특정 스레드에서 실행하는 코루틴을 실행하고 동시에 해당 스레드를 위한 예외처리를 설정하고자 할 때 더하기 연산자를 사용해 둘을 결합할 수 있다.
runBlocking {
val dispatcher = newSingleThreadContext("myDispatcher")
val handler = CoroutineExceptionHandler { _, t ->
println("Error captured")
println("Message : ${t.message}")
}
GlobalScope.launch(dispatcher + handler) {
println("Running in ${Thread.currentThread().name}")
TODO("Not Implement")
}.join()
}
위 코드는 단일 스레드 디스패쳐와 예외 처리를 결합하고 있으며, 코루틴은 그에 따라 동작하게 된다.
Running in myDispatcher
Error captured
Message : An operation is not implemented: Not Implement
물론 조합된 컨텍스트를 유지할 변수를 만들면, 한 번 이상 더하기 연산자를 사용하지 않아도 된다.
val context = dispatcher + handler
GlobalScope.launch(context) {
컨텍스트 분리
결합된 컨텍스트에서 컨텍스트 요서를 제거할 수도 있다. 앞에 예제를 수정해 결합된 컨텍스트를 분리해보자.
runBlocking {
val dispatcher = newSingleThreadContext("myDispatcher")
val handler = CoroutineExceptionHandler { _, t ->
println("Error captured")
println("Message : ${t.message}")
}
val context = dispatcher + handler
val tmpCtx = context.minusKey(dispatcher.key)
GlobalScope.launch(tmpCtx) {
println("Running in ${Thread.currentThread().name}")
TODO("Not Implement")
}.join()
}
여기서 스레드는 dispatcher의 스레드가 아닌 기본 디스패처에 해당한다.
Running in DefaultDispatcher-worker-1
Error captured
Message : An operation is not implemented: Not Implement
Reference
코틀린 동시성 프로그래밍(에이콘)
댓글남기기