첫 Coroutine 작성하기
이 섹션에서는 Coroutine의 기본 개념에 대해 다룹니다.
당신의 첫 번째 coroutine
Coroutine은 일시정지 연산을 위한 인스턴스이다. 이것은 코드의 나머지 부분들과 동시에 실행되는 코드 블록을 가진다는 점에서 스레드와 개념적으로 비슷하다. 하지만, 코루틴은 특정한 스레드에 종속되어 실행되지 않으며, 하나의 스레드에서 일시정지(suspend) 되었다가 다른 스레드에서 재개(resume)될 수 있다.
Coroutines는 경량 스레드로 생각될 수 있지만, 실제 사용을 스레드와 다르게 만드는 여러 다른 점들이 있다.
다음 코드를 실행하여 동작하는 첫 Coroutine을 만들어 보자.
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { // 새로운 coroutine을 실행하고 계속한다.
delay(1000L) // 블로킹 하지 않고 1초를 지연시킨다. (기본 시간 단위 : ms)
println("World!") // 지연 이후에 프린트한다.
}
println("Hello") // 이전 coroutine이 지연된 동안 main coroutine이 실행된다.
}
📌 전체 코드는 이곳에서 볼 수 있다.
위 코드를 실행하면 다음의 결과를 볼 수 있다.
Hello
World!
이 코드가 무엇을 하는지 분석해보자.
launch는 Coroutine Builder이다. launch는 독립적으로 동작하는 새로운 Coroutine을 나머지 코드와 동시에 실행 되도록 한다. 이로 인해 Hello가 처음에 출력된다.
delay는 특별한 일시중단 함수이다. delay는 Coroutine을 특정한 시간 동안 일시중단한다. Coroutine을 일시중단 하는 것은 Coroutine이 실행 중인 스레드를 블록하지 않으며, 다른 Coroutine 이 해당 스레드에서 자신들의 코드를 실행할 수 있도록 한다.
runBlocking 또한 Coroutine Builder이다. runBlocking은 Coroutine 세계에 속하지 않은 일반적인 fun main() 과 runBlocking { ... } 중괄호 내부의 Coroutine 코드를 연결시켜주는 역할을 한다. 이는 IDE 내에서 runBlocking 시작 중괄호 '{' 바로 다음에 오는 this: CoroutineScope 힌트로 강조표시된다.
만약 이 코드에서 runBlocking을 제거하거나 작성하는 것을 잊는다면, launch 호출 시에 애러가 발생할 것이다. launch는 Coroutine Scope 내에서만 선언될 수 있기 때문이다.
Unresolved reference: launch
runBlocking이라는 이름은 runBlocking { ... } 내부의 모든 Coroutine의 실행이 완료될 때까지 이를 실행하는 스레드(위의 상황에서는 main thread)가 호출 시간 동안 블록된다는 뜻이다. 스레드는 비싼 자원이고 스레드를 블록하는 것은 비효율적이고 바람직하지 않기 때문에, 어플리케이션의 최상위 수준에서 runBlocking이 이런식으로 사용되는 것을 종종 볼 수 있지만 실제 코드에서는 거의 보지 못한다.
구조화된 동시성
Coroutine은 새로운 Coroutine은 Coroutine의 수명을 제한하는 특정한 CoroutineScope 내에서만 실행될 수 있다는 구조화된 동시성의 원칙을 따른다. 위의 예시에서는 runBlocking이 해당 Scope을 설정하며, 그것이 이전 예시가 World! 가 프린트 될 때까지 1초를 기다린 후 종료되는 이유이다.
실제 어플리케이션에서는 당신은 많은 수의 Coroutine들을 실행할 것이다. 구조화된 동시성은 Coroutine들이 손실되거나 누수를 일으키지 않도록 한다. 바깥 Scope은 자식 Coroutine들이 모두 완료될 때까지 완료되지 못한다. 또한 구조화된 동시성은 코드 상의 애러들이 적절히 보고되고 손실되지 않도록 보장한다.
*그림1 은 공식 문서에 포함되지 않은 그림이며, 이해를 위해 추가하였습니다.
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutines Basics - Your first coroutine
원문 최종 수정 : 2022년 6월 27일
함수 추출해 Coroutines 코드 리펙토링하기
launch { ... } 내부의 코드*1를 분리된 함수로 추출해보자. 이 코드에서 "함수 추출" 을 수행한다면, suspend modifier을 가진 새로운 함수를 가지게 된다. 이것은 당신의 첫 일시중단 함수(suspending function)이 된다. 일시중단 함수는 다른 보통의 함수들과 마찬가지로 Coroutines 내부에서 사용될 수 있지만, 추가 기능은 다른 일시 중단 함수(이 예시에서 delay와 같은)를 사용하여 코루틴 실행을 일시 중단 할 수 있다는 것이다.
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { doWorld() }
println("Hello")
}
// 이것은 당신의 첫 일시 중단 함수이다.
suspend fun doWorld() {
delay(1000L)
println("World!")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 이전 코드는 다음과 같다. 아래 코드의 launch { ... } 내부의 코드들이 doWorld로 추출되는 것이다.
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { // 새로운 coroutine을 실행하고 계속한다.
delay(1000L) // 블로킹 하지 않고 1초를 지연시킨다. (기본 시간 단위 : ms)
println("World!") // 지연 이후에 프린트한다.
}
println("Hello") // 이전 coroutine이 지연된 동안 main coroutine이 실행된다.
}
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutines Basics - Extract function refactoring
원문 최종 수정 : 2022년 6월 27일
Coroutines Scope Builder 사용하기
다른 builder들에서 제공하는 Coroutine Scope 외에도, coroutineScope builder를 사용하여 고유한 scope을 선언할 수 있다.
coroutineScope는 자식 Coroutine 들의 실행이 모두 완료될 때까지 종료되지 않는 Coroutine Scope을 생성한다.
runBlocking과 coroutineScope builder는 그들의 body*1 와 children*2들이 모두 완료될 때까지 대기한다는 점에서 비슷해보일지 모른다. 이 둘의 주요한 차이는 runBlocking은 대기를 위해 현재 Thread를 block시키는 반면, coroutineScope는 다른 작업이 수행될 수 있도록 작업 중이던 Thread의 자원 사용을 해제(release)한다는 점이다. 이러한 차이점 때문에 runBlocking은 일반 함수이고, coroutineScope은 일시 중단 함수이다.
coroutineScope은 어떠한 일시 중단 함수 내부에서나 사용될 수 있다. 예를 들어, Hello와 World를 동시 출력을 suspend fun doWorld() 함수로 이동할 수 있다. *3. 이전코드
import kotlinx.coroutines.*
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
📌 전체 코드는 이곳에서 확인할 수 있습니다.
이 코드 또한 다음과 같이 출력된다.
Hello
World!
Scope builder과 동시성
coroutineScope builder은 일시 중단 함수 내부에서 복수의 동시 작업을 수행하기 위해 사용될 수 있다. doWorld 일시 중단 함수 내부에서 두 개의 동시에 수행되는 코루틴을 실행해보도록 하자.
import kotlinx.coroutines.*
// 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 { ... } 블록 내부의 두 코드들은 동시적으로 실행되어, 시작 후 1초가 지난 다음 "World 1"이 먼저 출력되고, 시작 후 2초가 지난 후에 "World 2"가 출력된다. doWorld의 coroutineScope은 이 둘 모두가 완료된 후에 종료되며, 그 후에야 doWorld가 반환되고 "Done" 문자열이 출력된다.
Hello
World 1
World 2
Done
📖 아래 내용은 독자의 이해를 위해 번역자가 추가한 글입니다.
*1. 중괄호 { ... } 내부의 코드를 뜻한다.
*2. 자식 Coroutine 을 뜻한다.
*3. 이전 코드는 다음과 같다.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
이 글은 Coroutines 공식 문서를 번역한 글입니다.
원문 : Coroutines Basics - Scope builder , Coroutines Basics - Scope builder and concurrency
원문 최종 수정 : 2022년 6월 27일
목차로 돌아가기