Kotlin: 코루틴 vs Suspending function 차이
Overview
Kotlin의 Coroutine과 suspending function을 이야기할 떄 두 용어는 거의 같은 의미로 사용된다. 하지만 두 용어는 약간의 차이를 가지고 있다.
launch, async라는 함수가 있는데 suspending function 안에서 호출될 수 있을까? 답은 아니다. 그 반면, delay라는 함수는 호출될 수 있다. 왜일까?
launch, async라는 함수는 coroutine을 생성하는 coroutine builder이다. 이들은 coroutine scope 내에서만 호출될 수 있다. coroutine scope, coroutine, suspending function. 이 포스트에서는 이 세 개념의 관계에 대해서 이야기해보려 한다.
Coroutine에 대하여
이를 설명하기 위해 공식 문서를 참고했다.
https://kotlinlang.org/docs/coroutines-basics.html
A coroutine is an instance of suspendable computation.
위 공식 문서의 바로 첫 줄에 coroutine에 대해 서술되어 있다. 아래는 위 문서의 첫 번째 예제이다.
fun main() = runBlocking { // this: CoroutineScope
launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello") // main coroutine continues while a previous one is delayed
}
위 코드에서 launch를 호출하는 부분이 coroutine을 생성하는 부분이다.
suspendable computation이란 무엇일까? Kotlin에서는 suspendable computation라는 개념을 suspending function이라는 이름으로 구현했다. 즉, coroutine은 suspending function의 인스턴스이다.
간단한 개념이지만 처음에는 이해가 잘 되지 않았던 것 같다. 클래스에서 클래스 인스턴스를 생성하듯, suspending function에서 coroutine을 생성하는 것이다.
// this is your first suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}
그래서, suspending function이 무엇인가?
Suspending function: coroutine을 중지하는 함수
아래 문장을 읽어보자.
Suspending functions can be used inside coroutines just like regular functions, but their additional feature is that they can, in turn, use other suspending functions (like
delay
in this example) to suspend execution of a coroutine.
- suspending function은 coroutine 내에서 호출될 수 있다 (일반 함수처럼)
- suspending function은 또다른 suspending function을 호출할 수 있다. (delay처럼)
- 호출된 suspending function은 coroutine의 실행을 중지(suspend)할 수 있다
suspending function은 이름에서 암시하듯 중지될 수 있는 함수이다. 위 설명에 따라, delay() 함수는 suspending function이므로 suspending function 내에서 호출될 수 있는 것이다. 그럼 launch는?
launch, async: coroutine builder
launch는 위 문서에서 설명하듯 coroutine builder, 즉 coroutine을 생성하는 함수이다. 함수 시그니처를 확인해보자.
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job
- launch는 suspending function이 아니다.
- 마지막 파라미터 block은 suspending function이다 (receiver로 CoroutineScope을 받고 있다)
포스트 처음에 언급한 coroutine을 떠올려보자. (coroutine이 suspending function의 인스턴스이다) launch는 coroutine을 생성해주는 함수이다. 즉, suspending function인 block 파라미터로부터 coroutine을 생성해주는 함수인 것이다.
그리고 여기서 특이한 점은, launch 함수의 block 파라미터는 suspending function임에도 receiver가 CoroutineScope이므로 launch, async와 같은 coroutine builder를 호출할 수 있다.
본 포스트 처음에 언급했듯, 위와 같이 receiver가 지정되지 않은 경우(suspendung function를 선언할 때)에는 coroutine builder를 호출할 수 없다.
Coroutine scope: structured concurrency
launch와 같은 코루틴 빌더 함수는 coroutine scope 내에서만 호출할 수 있다. 왜 그렇게 해놓았을까? 이 의문은 Scope builder and concurrency 부분을 읽어보면 해결할 수 있다.
Kotlin coroutine은 구조적 병렬성 (Structured concurrency)을 즤원한다. Node.js 등 다른 언어와 async/await 패턴보다 coroutine이 더 강력한 이유 중 하나이다.
Coroutines follow a principle of structured concurrency which means that new coroutines can be only launched in a specific CoroutineScope which delimits the lifetime of the coroutine.
번역하면,
Kotlin의 coroutine은 structured concurrency 원칙을 따른다. 즉, 새로운 코루틴은 CorotuineScope 내에서만 실행(launch)될 수 있고 이는 코루틴의 수명(lifetime)을 제한한다.
coroutine의 lifetime은 coroutine scope에 의해 정의되고, 이를 위해 coroutine은 coroutine scope 내에서만 실행 되어야 한다.
async/await style처럼 독립적으로 실행하고 싶다면 Globalscope을 사용하여 실행하면 된다.
Conclusion
본 포스트의 내용을 짧게 요약하면 아래와 같다.
- Suspending function은 중지될 수 있는 함수이다.
- Coroutine은 suspending function을 인스턴스화한 것이다.
- Coroutine은 coroutine scope 내에서만 호출될 수 있다. 이는 coroutine의 structured concurrency를 지원하기 위해서 필요하다.
따라서, 포스트 처음에 제기한 의문에 대한 답은 아래와 같다.
launch, async는 coroutine builder 함수로서 coroutine을 생성한다. 따라서 coroutine scope 내에서만 호출 가능하다.
suspending function은 기본적으로 coroutine scope을 정의하지 않기 때문에, suspending function 내에서 coroutine을(launch, async 함수를) 실행할 수 없다.
delay는 suspending function이므로, suspending function 내에서 실행될 수 있다.
Appendix
Suspend function의 의미, JVM 구현 (Continuation)
위 글에서 suspending function에 대하여 잘 설명해주고 있다. suspending function의 정의 자체는 말 그대로 중지(suspend)&재개(resume)될 수 있는 함수이다. 더 나아가 코루틴이 어떻게 구현되어 있는지도 설명하고 있다. 코루틴을 컴파일하면 Continuation이라는 파라미터를 하나 추가되어, 해당 함수(suspending function)을 다시 resume할 수 있도록 지원한다.