
이번 게시글에서는 코루틴의 구조적 동시성에 대해 알아보고자 합니다.
1. 구조적 동시성이란?
1.1. 시작하기에 앞서
구조적 동시성(Structured Concurrency)이란 동시성 작업의 흐름을 순차적으로 제어할 수 있도록 하는 방식입니다.
일반적으로 코드는 순차적으로 실행되지만 동시성 작업이 추가되면, 제어의 흐름을 벗어나게 됩니다. 아래의 그림의 `myfunc`와 같이 말이죠.

동시성 작업이 완전히 통제되기를 기대할 수는 없습니다. 일정 부분은 그대로 흘러가게 두어야 합니다. 그러나 이러한 흐름을 적절히 제어해야만, 전체 작업이 예측 가능한 순서로 실행되도록 보장할 수 있습니다.
1.2. Nurseries
동시성 작업을 제어하기 위한 개념으로 `nurseries`가 등장했습니다.
`nurseries`의 핵심 아이디어는 실행 흐름이 여러 개의 동시 경로로 분기될 때마다, 반드시 다시 하나로 합쳐지도록 해야한다는 것입니다.
예를 들어, 동시에 세 가지 작업을 수행하고자 할 때, 전체 제어 흐름은 다음과 같은 형태를 갖춰야 합니다.

위 그림에서 볼 수 있듯이, 위에서 들어오는 화살표는 하나이고 아래로 나가는 화살표도 하나뿐입니다. 이러한 구조를 이루기 위해서는 우리는 모든 동시성 작업이 종료되는 시점을 명확하게 제어해야 합니다.
이를 위해 먼저 동시에 실행되는 작업들을 하나의 단위 `Block`으로 묶습니다.

그리고 각 동시성 작업이 종료되면 `Block`에게 자신이 종료되었다는 것을 알리고, `Block`은 모든 동시성 작업이 종료되었을 때, `Block` 자체도 종료되어 다음 작업 흐름으로 이어집니다.
2. 코루틴의 구조적 동시성
우리는 구조적 동시성의 개념에 대해 어느 정도 이해했습니다. 이제부터는 코루틴이 어떻게 구조적 동시성을 어떻게 구현하고 보장하는지 알아보겠습니다.
코루틴에서 동시에 실행되는 작업들은 CoroutineScope를 통해 하나의 실행 단위로 묶입니다.
<kotlin />
fun main() = runBlocking {
println("작업 시작")
coroutineScope {
launch {
delay(1000)
println("첫 번째 작업 완료")
}
launch {
delay(1500)
println("두 번째 작업 완료")
}
}
println("모든 작업 완료")
}
위 예제에서 각각의 작업(코루틴)은 자신의 실행이 종료되었음을 CoroutineScope에 알리고, CoroutineScope는 모든 자식 작업이 완료될 때까지 대기한 뒤, 스스로 종료되어 다음 실행 흐름으로 자연스럽게 이어지게 됩니다.
2.1. 부모-자식 코루틴의 개념
그럼 어떻게 각 작업은 자신의 실행이 종료되었다는 것을 CoroutineScope에 알릴까요?
바로 스코프(Context)를 통해 부모-자식 관계를 형성하여 트리 구조를 만듭니다.
부모 코루틴은 CoroutineScope을 가집니다. 그리고 자식 코루틴은 부모의 `Job`을 계층적으로 이어받습니다.
<kotlin />
fun main() = runBlocking { // 부모 코루틴
launch { // 자식 코루틴 1
delay(1000)
println("1번 작업")
}
launch { // 자식 코루틴 2
delay(2000)
println("2번 작업")
}
}
구조를 통해 부모 코루틴은 자식 코루틴의 생명주기를 추적하고 관리할 수 있게 됩니다.
자식 코루틴이 종료되면, 내부적으로 자신의 상태를 나타내는 Job이 완료되고, 이 상태는 부모의 Job에도 반영됩니다.
즉, 부모 코루틴은 자식들이 모두 정상적으로 완료되었는지, 중간에 예외가 발생했는지, 혹은 아직 실행 중인 작업이 있는지를 파악할 수 있으며, 이러한 관계 덕분에 부모는 자식들이 모두 종료될 때까지 대기하거나, 필요시 전파된 예외나 취소 신호에 대응할 수 있습니다.
이러한 구조적 연결 덕분에, 코루틴의 실행 흐름은 더 이상 예측할 수 없는 비동기 콜백의 모음이 아니라, 명확한 시작과 끝을 가진 일관된 구조로 표현될 수 있습니다.
3. 마무리하며
코루틴의 구조적 동시성에 대해 설명한 게시글은 많이 있습니다. 하지만 저는 그런 글들을 읽고도 쉽게 이해하기 어려웠습니다.
그 이유는 어떤 글들이 “코루틴에서 부모-자식 관계를 생성하는 것이 구조적 동시성이다”라고 설명하고 있었기 때문입니다.
이 설명이 완전히 틀린 것은 아니지만, 설명 순서나 관점이 잘못되었다고 생각합니다. 정확하게는, 구조적 동시성을 구현하기 위해 코루틴이 부모-자식 관계를 활용하고 있는 것입니다.
즉, 구조적 동시성이라는 개념이 먼저 존재하고, 이를 실현하기 위한 수단으로 코루틴의 컨텍스트, CoroutineScope, Job과 같은 메커니즘들이 동원되고 있는 것입니다.
4. Reference
Notes on structured concurrency, or: Go statement considered harmful — njs blog
<!-- gross hack to trick pelican into including the .woff file in the output dir it's referenced from the .svg files, thanks to running python ~/bin/svg-add-font-face.py "DejaVu Sans Mono" deja-vu-sans-mono.woff *.svg I also did: python ~/bin/svg-add-style
vorpus.org
'Android > Kotlin' 카테고리의 다른 글
[Flow 연산자] 생산자 총정리 (0) | 2025.03.31 |
---|---|
[Flow 연산자] 중간 연산자 총정리 (0) | 2025.02.12 |
Flow의 collect은 언제 suspend 될까? (0) | 2025.01.21 |
왜 SharedFlow의 emit()은 suspend 함수일까? (2) | 2025.01.01 |
얕은 복사, 깊은 복사 그리고 Data class의 copy()에 대한 고찰 (1) | 2024.12.17 |