본문 바로가기
Android/Coroutine

[Kotlin/Coroutine] 1. Coroutines basics

by 겸 2023. 10. 19.

Coroutines guide


코루틴 공식 가이드 문서를 읽고 요약 정리해보려 한다.

목표는 Kotlin Coroutine을 완벽하게 이해하는 것!!

더불어, 영어로 된 가이드 문서를 읽는 연습도 할 것이다! : )

Coroutines basics


코루틴은 중단가능한 계산의 인스턴스이다.

스레드와 비슷한 개념이지만, 코루틴은 특정 스레드에 바인딩되지 않는다.

하나의 스레드에서 실행을 중지하고, 다른 스레드에서 다시 실행할 수 있다.

 

코드

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
}

출력

Hello
World!

위 코드를 분석해보자.

launch

코루틴 빌더이다. 나머지 코드와 새로운 코루틴을 동시에 시작하며 독립적으로 동작한다.

CoroutineScope에서만 선언될 수 있다.

Hello가 먼저 출력될 수 있었던 이유가 launch가 아래 println("Hello") 와 동시에 실행될 수 있었기 때문이다.

delay

suspend 함수. 중단함수이다. 특정 시간동안 코루틴을 중지시킬 수 있다.

코루틴을 중지시키면 기본 스레드는 차단되지 않는다. 그 동안 다른 코루틴이 기본 스레드를 사용할 수 있도록 한다.

runBlocking

lauch와 같이 코루틴 빌더이다. 코루틴이 아닌 fun main()runBlocking { ... } 내부의 코루틴 코드를 연결해준다.

만약 위 코드에 runBlocking이 없었다면, launch 호출 시 에러가 발생한다. launch는 CoroutineScope에서만 선언될 수 있기 때문이다.

호출 시 내부에 있는 코루틴의 실행이 끝날 때 까지 스레드가 차단된다.

비싼 리소스이고, 비효율적이므로 거의 사용되지 않는다.


Structured concurrency

코루틴은 구조화된 동시성의 원칙을 따른다.

새로운 코루틴은 코루틴의 수명을 제한하는 특정 CoroutineScope에서만 실행될 수 있다.

외부 영역은 자식 코루틴이 끝날 때까지 종료될 수 없다.

손실이나 누수를 방지한다.


Extract function refactoring

launch { ... }내부의 코드를 별도의 함수로 분리하려면 새로운 함수 앞에 suspend 수정자를 붙이면 된다.

코드

fun main() = runBlocking { // this: CoroutineScope
    launch { doWorld() }
    println("Hello")
}

// this is your first suspending function
suspend fun doWorld() {
    delay(1000L)
    println("World!")
}

Scope builder

CoroutineScope빌더를 사용해서 코루틴 범위를 지정할 수 있다.

CoroutineScope 범위를 생성하고, 실행된 자식 코루틴이 끝날 때까지 종료되지 않는다.

runBlocking vs coroutineScope builder

공통점

  • 범위 내 하위 코루틴이 모두 끝날 때까지 기다린다.

차이점

  • runBlocking
    • 기다리기 위해 현재 스레드를 차단한다.
    • 일반 함수
  • coroutineScope
    • 일시 중지한 뒤, 해당 스레드는 다른 용도로 사용한다.
    • 중단 함수

 

코드 1

fun main() = runBlocking {
    doWorld()
}

suspend fun doWorld() = coroutineScope {  // this: CoroutineScope
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello")
}

출력

Hello
World!

코루틴 스코프 빌더내에서 launch { … } 내부의 delay로 인해 1초 동안 중지되어 있는 동안 스레드는 Hello를 출력한다. 1초의 대기 후에 world를 출력하게 된다.

 

코드 2

fun main() {
    runBlocking {
        delay(1000L)
        println("World!")
    }
    println("Hello")
}

출력

World!
Hello

runBlocking은 스레드를 차단하므로 내부 코루틴이 모두 실행될 때까지 Hello가 먼저 출력될 수 없다.


Scope builder and concurrency

코루틴스코프 필더는 suspend 함수 내에서 여러개의 동시 작업을 할 때 사용할 수 있다.

 

코드

// Sequentially executes doWorld followed by "Done"
fun main() = runBlocking {
    doWorld()
    println("Done")
}

// Concurrently executes both sections
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
    launch {
        delay(2000L)
        println("World 2")
    }
    launch {
        delay(1000L)
        println("World 1")
    }
    println("Hello")
}

출력

Hello
World 1
World 2
Done

기다리는 시간동안 스레드가 다른 동작을 할 수 있으므로 여러개의 자식 코루틴이 동시에 실행될 수 있다.

코루틴스코프는 내부의 모든 하위 코루틴이 완료될 때까지 기다렸다가 종료한다.


An explicit job

launch 코루틴 빌더는 Job 객체를 반환한다.

실행된 코루틴이 완료될 때까지 명시적으로 기다리는데 사용할 수 있다.

 

코드

val job = launch { // launch a new coroutine and keep a reference to its Job
    delay(1000L)
    println("World!")
}
println("Hello")
job.join() // wait until child coroutine completes
println("Done")

출력

Hello
World!
Done

join을 사용하면 해당 코루틴이 완료될 때까지 기다린다.


Coroutines are light-weight

코루틴은 JVM스레드보다 리소스 집약적이지 않다.

코루틴 대신 스레드만을 사용해서 구현한다면 많은 JVM 메모리를 소비하게 된다.

코루틴을 사용하면 리소스 제한에 도달하지 않고 표현할 수 있다.

반응형