코루틴 기초 (Coroutines basics)
이 섹션은 기본적인 코루틴 개념을 다룹니다.
첫 번째 코루틴
코루틴은 중단 가능한 계산의 인스턴스입니다. 개념적으로 스레드와 유사하며 코드 블록을 실행하며 나머지 코드와 동시에 작동한다는 점에서 스레드와 유사합니다. 그러나 코루틴은 특정 스레드에 바인딩되지 않습니다. 코루틴은 한 스레드에서 실행을 일시 중단하고 다른 스레드에서 다시 실행할 수 있습니다.
코루틴은 가벼운 스레드로 생각할 수 있지만, 실제 사용에서는 스레드와 매우 다른 중요한 차이점이 있습니다.
첫 번째 작동하는 코루틴에 도달하려면 다음 코드를 실행하세요:
fun main() = runBlocking { // 이것: CoroutineScope
launch { // 새로운 코루틴을 시작하고 계속 진행
delay(1000L) // 1초 동안의 비차단 지연 (기본 시간 단위는 밀리초)
println("World!") // 지연 후에 출력
}
println("Hello") // 메인 코루틴은 이전 코루틴이 지연되는 동안 계속 진행됩니다.
}
결과는 다음과 같이 나타납니다:
Hello
World!
이 코드가 무엇을 하는지 살펴보겠습니다.
launch는 코루틴 빌더입니다. 이것은 새로운 코루틴을 기존 코드와 동시에 실행하며 독립적으로 작동하게 합니다. 이것이 Hello가 먼저 출력된 이유입니다.
delay는 특수한 중단 함수입니다. 이것은 코루틴을 특정 시간 동안 중단시킵니다. 코루틴을 중단하는 것은 기본 스레드를 차단하지 않지만 다른 코루틴이 실행되고 기본 스레드를 사용하여 코드를 실행할 수 있게 합니다.
runBlocking 또한 코루틴 빌더로서, 일반적인 fun main()의 비코루틴 세계와 runBlocking { ... } 중괄호 안의 코루틴을 연결하는 역할을 합니다. 이것은 runBlocking 여는 중괄호 바로 뒤에 나오는 이것: CoroutineScope 힌트로 IDE에서 강조 표시됩니다.
이 코드에서 runBlocking을 제거하거나 잊어버리면 launch 호출에서 오류가 발생합니다. 왜냐하면 launch는 CoroutineScope에서만 선언되기 때문입니다:
Unresolved reference: launch
runBlocking의 이름은 해당 호출의 기간 동안 실행하는 스레드(이 경우 주 스레드)가 모든 코루틴이 실행을 완료할 때까지 차단된다는 것을 의미합니다. 애플리케이션의 맨 상위 수준에서 runBlocking이 이렇게 사용되는 것을 자주 볼 수 있으며 실제 코드 내부에서는 드물게 사용됩니다. 스레드는 비용이 많이 드는 자원이며 차단은 비효율적이며 종종 원치 않는 동작입니다.
구조화된 동시성
코루틴은 구조화된 동시성의 원칙을 따릅니다. 이것은 새로운 코루틴을 특정 CoroutineScope에서만 시작할 수 있고 코루틴의 수명을 제한하는 것을 의미합니다. 위의 예제에서는 runBlocking이 해당 범위를 설정하고 있으며 그래서 이전 예제는 1초의 지연 후에 World!가 출력될 때까지 기다리고 나서 종료합니다.
실제 애플리케이션에서는 많은 코루틴을 시작할 것입니다. 구조화된 동시성은 그들이 소실되거나 유출되지 않도록 보장합니다. 바깥 범위는 모든 하위 코루틴이 실행을 완료할 때까지 완료될 수 없습니다. 구조화된 동시성은 또한 코드 내의 모든 오류가 올바르게 보고되고 손실되지 않도록 보장합니다.
함수 추출 리팩토링
launch { ... } 블록 내부의 코드를 별도의 함수로 추출해 보겠습니다. 이 코드에 대해 "함수 추출" 리팩토링을 수행하면 suspend 수식자가 있는 새로운 함수가 생성됩니다. 이것이 여러분의 첫 번째 중단 함수입니다. 중단 함수는 일반 함수처럼 코루틴 내에서 사용할 수 있지만, 추가 기능으로 이 예제에서 사용된 것처럼 다른 중단 함수 (예: delay)를 사용하여 코루틴의 실행을 중단시킬 수 있습니다.
fun main() = runBlocking { // this: CoroutineScope
launch { doWorld() }
println("Hello")
}
// 이것이 여러분의 첫 번째 중단 함수입니다.
suspend fun doWorld() {
delay(1000L)
println("World!")
}
스코프 빌더
다양한 빌더에서 제공되는 코루틴 스코프 외에도, coroutineScope 빌더를 사용하여 자체 스코프를 선언할 수 있습니다. 이 빌더는 코루틴 스코프를 생성하고, 모든 시작된 자식 코루틴이 완료될 때까지 완료되지 않습니다.
runBlocking과 coroutineScope 빌더는 유사해 보일 수 있지만, 둘 다 본문과 그 하위 자식들의 완료를 기다립니다. 주요 차이점은 runBlocking 메서드가 현재 스레드를 대기하기 위해 블록하는 반면, coroutineScope는 중단되어 기본 스레드를 다른 용도로 사용할 수 있게 해제한다는 것입니다. 이 차이 때문에 runBlocking은 일반 함수이고, coroutineScope는 중단 함수입니다.
중단 함수에서 coroutineScope를 사용할 수 있습니다. 예를 들어, "Hello"와 "World"를 동시에 출력하는 코드를 suspend fun doWorld() 함수로 이동할 수 있습니다:
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
결과:
Hello
World!
스코프 빌더와 병행성
coroutineScope 빌더는 중단 함수 내에서 여러 병행 작업을 수행하기 위해 사용할 수 있습니다. doWorld 중단 함수 내에서 두 개의 병행 코루틴을 시작해 보겠습니다:
// doWorld 다음에 "Done"을 순차적으로 실행합니다.
fun main() = runBlocking {
doWorld()
println("Done")
}
// 두 섹션을 병행하여 실행합니다.
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(2000L)
println("World 2")
}
launch {
delay(1000L)
println("World 1")
}
println("Hello")
}
launch { ... } 블록 내의 코드는 두 개의 코루틴이 동시에 실행되며, World 1이 먼저 출력되고 시작 후 1초 후에 World 2가 다음으로 출력됩니다. doWorld의 coroutineScope는 두 개의 코루틴이 완료될 때까지 완료되지 않으므로 doWorld가 반환되고 "Done" 문자열이 출력되는 것은 그 후에 일어납니다:
Hello
World 1
World 2
Done
명시적인 Job
launch 코루틴 빌더는 시작된 코루틴의 핸들인 Job 객체를 반환하며, 이를 사용하여 명시적으로 해당 코루틴의 완료를 기다릴 수 있습니다. 예를 들어, 자식 코루틴의 완료를 기다린 다음 "Done" 문자열을 출력할 수 있습니다:
val job = launch { // 새로운 코루틴을 시작하고 해당 Job에 대한 참조를 유지합니다.
delay(1000L)
println("World!")
}
println("Hello")
job.join() // 자식 코루틴이 완료될 때까지 기다립니다.
println("Done")
이 코드는 다음과 같은 결과를 출력합니다:
Hello
World!
Done
코루틴은 가볍다
코루틴은 JVM 스레드보다 자원을 덜 소비합니다. 스레드를 사용할 때 JVM의 사용 가능한 메모리를 고갈시키는 코드도 리소스 제한에 도달하지 않고 코루틴을 사용하여 표현할 수 있습니다. 예를 들어, 다음 코드는 각각 5초 동안 대기한 다음 점 ('.')을 출력하는 50,000개의 서로 다른 코루틴을 시작합니다. 이 과정에서 매우 적은 메모리를 사용합니다:
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(50_000) { // 많은 코루틴을 시작합니다.
launch {
delay(5000L)
print(".")
}
}
}
만약 스레드를 사용하여 동일한 프로그램을 작성한다면 (runBlocking을 제거하고 launch를 thread로 바꾸고 delay를 Thread.sleep으로 바꿉니다), 많은 메모리를 소비할 것입니다. 운영 체제, JDK 버전 및 해당 설정에 따라 메모리 부족 오류를 발생시키거나 너무 많은 동시 실행 스레드가 없도록 스레드를 느리게 시작할 수 있습니다.
원문
'Kotlin' 카테고리의 다른 글
[Kotlin] Kotlin 공식 문서 번역 - 코루틴 취소와 타임아웃 (Coroutines Cancellation and timeouts) (0) | 2023.10.01 |
---|---|
[Kotlin] Kotlin 공식 문서 번역 - 코루틴과 채널 - 튜토리얼 (Coroutines and channels − tutorial) (1) | 2023.10.01 |
[Kotlin] Kotlin 공식 문서 번역 - 코루틴 가이드 (Coroutines guide) (1) | 2023.10.01 |
[Kotlin] Kotlin 공식 문서 번역 - 시퀀스 (Sequence) (73) | 2023.09.26 |
[Kotlin] data class가 애플리케이션 성능에 미치는 영향 (0) | 2023.08.27 |
댓글