중단 함수는 컨티뉴에이션 객체를 다른 중단 함수로 전달해야 한다. 따라서 중단 함수가 일반 함수를 호출하는 것은 가능하지만, 일반 함수가 중단 함수를 호출하는 것은 불가능하다.

Untitled

중단 함수를 연속으로 호출하면 시작되는 지점이 반드시 있다. 코루틴 빌더(coroutine builder)가 그 역할을 하며, 일반 함수와 중단 가능한 세계를 연결시키는 다리가 된다.

kotlinx.coroutines 라이브러리가 제공하는 세 가지 필수적인 코루틴 빌더는 다음과 같다. 각 코루틴 빌더는 서로 다른 쓰임새가 있다.

launch 빌더

launch가 작동하는 방식은 thread 함수를 호출하여 새로운 스레드를 시작하는 것과 비슷하다. 코루틴을 시작하면 각각이 별개로 실행된다. 다음은 새로운 프로세스를 시작하기 위해 launch를 사용한 예제이다.

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

fun main() {
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello")
    Thread.sleep(2000L)
}

/*
Hello
(2초후)
World!
World!
World!
*/

launch 함수는 CoroutineScope 인터페이스의 확장 함수다. CoroutineScope 인터페이스는 부모 코루틴과 자식 코루틴 사이의 관계를 정립하기 위한 목적으로 사용되는 구조화된 동시성(structured concurrency)의 핵심이다. 이것이 무엇인지는 뒷 장에서 배운다.

Untitled

실제 현업에서는 GlobalScope의 사용을 지양해야 한다.

main 함수의 끝에 Thread.sleep을 호출해야 한다. 스레드를 잠들게 하지 않으면 메인 함수는 코루틴을 실행하자마자 끝나 버리게 되며, 코루틴이 일을 할 기최조차 주지 않는다. delay가 스레드를 블록시키지 않고 코루틴을 중단시키기 때문이다.

launch가 작동하는 방식은 데몬 스레드와 어느 정도 비슷하지만 훨씬 가볍다. 데몬 스레드는 백그라운드에서 돌아가며, 우선순위가 낮은 스레드다. 이런 비교 방식은 처음엔 유용할 수 있지만 나중엔 문제가 될 수 있다. 블로킹된 스레드를 유지하는 건 비용이 드는 일이지만 중단된 코루틴을 유지하는 건 공짜나 다름 없기 때문이다. 둘 다 별개의 작업을 시작하며 작업을 하는 동안 프로그램이 끝나는 걸 막을 수 없다는 점은 비슷하다.

import kotlin.concurrent.thread

fun main() {
    thread(isDaemon = true) {
        Thread.sleep(1000L)
        println("World!")
    }
    thread(isDaemon = true) {
        Thread.sleep(1000L)
        println("World!")
    }
    thread(isDaemon = true) {
        Thread.sleep(1000L)
        println("World!")
    }
    println("Hello")
    Thread.sleep(2000L)
}

runBlocking 빌더

코루틴이 스레드를 블로킹하지 않고 작업을 중단시키기만 하는 것이 일반적인 법칙이다. 하지만 블로킹이 필요한 경우도 있다. 메인 함수의 경우 프로그램을 너무 빨리 끝내지 않기 위해 스레드를 블로킹해야 한다. 이럴 때 runBlocking을 사용하면 된다.

Untitled

코루틴이 중단되었을 경우 runBlocking 빌더는 중단 메인 함수와 마찬가지로 시작한 스레드를 중단시킨다.(새로운 코루틴을 실행한 뒤 완료될 때까지) 따라서 runBlocking 내부에서 delay(1000L)을 호출하면 Thread.sleep(1000L)과 비슷하게 작동한다.(디스패치를 사용해 runBlocking이 다른 스레드에서 실행되게 할 수 있다. 하지만 이 경우에도 코루틴이 완료될 때까지 해당 빌더가 시작된 스레드가 블로킹된다.)