runBlocking 함수는 호출부의 스레드를 차단한다.
이 말이 무슨 뜻일까?
runBlocking 함수가 호출되면 새로운 코루틴인 runBlocking 코루틴이 실행되는데 이 코루틴은 실행이 완료될 때까지 runBlocking 함수를 호출한 스레드를 점유하고 사용한다.
즉, runBlocking 함수를 호출한 스레드는 그로부터 생성되는 runBlocking 코루틴이 실행 완료될 때까지 runBlocking 코루틴에 의해 배타적으로 사용된다.
runBlocking 코루틴에 의해 호출부의 스레드가 배타적으로 사용된다는 것은 runBlocking 코루틴은 하위에 생성된 코루틴도 그 호출부의 스레드를 사용할 수 있다.
runBlocking 함수의 차단은 스레드 블로킹(Thread Blocking)에서의 차단과 다르다. 스레드 블로킹은 스레드가 어떤 작업에도 사용할 수 없도록 차단되는 것을 의미하고, runBlocking 함수의 차단은 runBlocking 코루틴과 그 자식 코루틴을 제외한 다른 작업이 스레드를 사용할 수 없음을 의미한다.
runBlocking 코루틴은 runBlocking 함수 호출부의 스레드를 차단하고 사용하지만, launch 함수를 사용해 생성되는 launch 코루틴은 실행될 때 호출부의 스레드를 차단하지 않는다.
fun main() = runBlocking<Unit> { // runBlocking 코루틴
val startTime = System.currentTimeMillis()
runBlocking { // 하위 runBlocking 코루틴
delay(1000L)
println("[$Thread.currentThread().name}] 하위 코루틴 생성")
}
println(getElapsedTime(startTime)) // 지난 시간 출력
}
/*
// 결과
[main @coroutine#2] 하위 코루틴 종료
지난 시간: 1021ms
*/
이 코드에서는 바깥쪽 runBlocking 코루틴 하위에 runBlocking 코루틴을 새로 실행한다. 하위에 생성된 runBlocking 코루틴은 실행되는 동안 메인 스레드를 차단한다. 따라서 바깥쪽 runBlocking 코루틴은 하위 runBlocking 코루틴이 모두 실행될 때까지 메인 스레드를 사용할 수 없으므로 하위 runBlocking 코루틴이 모두 실행되고 나서야 지난 시간을 출력할 수 있다. 따라서 코드의 실행 결과를 보면 출력된 지난 시간이 1초 정도(1021밀리초)인 것을 확인할 수 있다.
반면에 launch 코루틴은 코루틴 빌더 함수 호출부의 스레드를 차단하지 않는다. 따라서 launch 코루틴이 delay 같은 작업으로 인해 실제로 스레드를 사용하지 않는 동안 스레드는 다른 작업에 사용될 수 있다. 앞의 코드에서 runBlocking 코루틴 내에서 호출되는 runBlocking 함수를 launch 함수로 바꿔보자.
fun main() = runBlocking<Unit> { // runBlocking 코루틴
val startTime = System.currentTimeMillis()
launch { // 하위 launch 코루틴
delay(1000L)
println("[$Thread.currentThread().name}] 하위 코루틴 생성")
}
println(getElapsedTime(startTime)) // 지난 시간 출력
}
/*
// 결과
지난 시간: 4ms
[main @coroutine#2] 하위 코루틴 종료
*/
이 코드에서 launch 코루틴은 호출부의 스레드를 차단하고 실행되는 것이 아니기 때문에 launch 코루틴이 메인 스레드를 양보하고 나서야 지난 시간 4밀리초가 메인 스레드에 보내져 실행된다.
launch 코루틴이 delay(1000L)로 인해 1초간 대기하는 동안에 메인 스레드를 다른 작업이 자유롭게 사용할 수 있다.
runBlocking 코루틴 빌더는 생성된 코루틴이 완료될 때까지 호출 스레드를 차단하고 사용하는 코루틴을 만드는 반면에 launch 코루틴 빌더로 생성된 코루틴은 호출 스레드를 차단하지 않는다.