Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

디지안의 개발일지

[TIL] Kotlin Coroutines v1.5.30 Document - 2. Coroutine basics 본문

Kotlin

[TIL] Kotlin Coroutines v1.5.30 Document - 2. Coroutine basics

안덕기 2021. 9. 6. 05:36

들어가기 전에

이 글은 기본적인 coroutine의 개념을 설명한다.

 

Your first coroutine

coroutine은 일시중지하는 계산이 가능한 인스턴스다. 개념적으로는 스레드와 유사한다. 이 의미는 코루틴에 해당하는 코드 블록이 다른 코드와 동시적으로 일을 수행한다는 의미다. 그러나 코루틴이 어떤 특별한 스레드를 의미하는 것은 아니다. 코루틴은 하나의 스레드 안에서 코루틴 자체의 실행을 지연하고 또 다른 스레드에서 실행을 재개한다.

 

코루틴은 경량의 스레드로 생각할 수 있으나 실제로 사용하는데 있어서 스레드와 중요한 차이가 있다.

 

다음 코드를 실행해보자.

fun main() = runBlocking {
  launch {
    delay(1_000L)
    println("World!")
  }
  println("Hello")
}

결과는 다음과 같다.

Hello
World!

이 코드를 분석해보자.

 

launch는 코루틴 빌더다. launch는 새로운 코루틴을 만드는데 다른 코드와 동시성을 보장하면서 수행되고 코루틴의 코드와 다른 코드는 각자 독립적으로 수행된다. Hello가 먼저 출력되는 이유가 여기에 있다. 

 

delay는 특별한 suspending function이다. delay는 특별한 시간을 위해 코루틴을 지연한다. 지연된 코루틴은 스레드 아래에서 블럭킹 당하지 않고 코드 상에 스레드 아래에 있는 다른 코루틴이 수행하도록 한다.

 

runBlocking도 코루틴 빌더다. fun main()에 있는 일반적인 코드 runBlocking { . . . } 블럭의 코드를 연결시켜주는 역할을 한다. runBlocking은 IDE에서 runBlocking 오른쪽에 this: CoroutineScope 라는 것으로 강조된다.

 

코드안에서 runBlocking를 제거하는 것이나 넣는 것을 잊으면 launch 호출에 대한 에러를 얻을 수 있다. 그 에러는 CoroutineScope 내에서 launch가 선언된 부분에 다음과 같은 표현된다.

Unresolved reference: launch

runBlocking의 이름은 runBlocking 내에 있는 모든 코루틴의 실행이 완료될 때까지 스레드(지금 상황에는 main 스레드)가 블럭킹 된다라는 의미를 가진다. 스레드는 값 비싼 리소스이기 때문에 스레드를 차단은 비효율적이고 바람직하지 않는 상황 자주 이루어지기 때문에 대부분 응용 프로그램의 최상위에서 사용되는 것을 볼 수 있다.

 

Structured concurrency

코루틴은 구조화된 동시성을 따른다. 이는 새로운 코루틴이 명세된 CoroutineScope에서 발행할 수 있는 것을 의미한다. CoroutineScope는 코루틴의 생명주기를 제한한다. 위에 예제에서 runBlocking이 범위를 설정하기 때문에 World!가 1초 지연된 뒤에 출력되고 애플리케이션이 종료되는 것을 볼 수 있다.

 

실제 애플리케이션에서, 많은 코루틴을 만들 수 있다. 구조화된 동시성은 이 많은 코루틴들을 손실되거나 누출되지 않도록 보장한다. 외부의 scope는 안의 코루틴이 완료될 때까지 완료할 수 없다. 구조화된 동시성은 코드에서 발생하는 에러를 적절하게 알려주고 손실되지 않는 것을 보장한다.

 

Extract function refactoring

launch { ... } 코드 블럭 안에 있는 코드를 다른 function으로 추출해보자. 이 코드에서 "Extract function"을 수행할 때, suspend 수정자와 함께 새로운 function을 얻을 수 있다. 이 함수는 suspending function 이다. 지연된 함수들은 코루틴 안에서 일반적인 함수처럼 사용할 수 있다. 이 함수들은 추가적인 기능을 가지고 있는데 delay 처럼 코루틴을 지연할 수 있는 다른 지연된 함수를 사용할 수 있다

fun main() = runBlocking {
    launch { doWorld() }
    println("Hello")
}

suspend fun doWorld() {
    delay(1_000L)
    println("World!")
}

 

Scope builder

다른 코루틴 빌더내에서 제공하는 코루틴외에도 coroutineScope를 통해서 자신만의 코루틴 스코프를 선언할 수 있다. 이는 코루틴 스코프를 만들고 그 코루틴 스코프 내에서 하위 코루틴들이 완료될 때까지 완료되지 않는다.

 

runBlockingcoroutineScope 빌더는 아마 유사하게 보일 것이다. 둘 다 하위 코루틴이 완료될 때까지 기다리기 때문이다. 

runBlocking과 coroutineScope는 주요 차이점은 코루틴을 만든 스레드 아레에서 runBlocking은 스레드를 멈추게하고 coroutineScope는 단지 지연되는 것 뿐이다. 이런 차이 때문에 runBlocking은 일반적인 함수고 coroutineScope은 지연된 함수다.

 

프로그래머는 지연된 함수로부터 coroutineScope를 사용할 수 있다. 예를 들어, 위 코드를 다음과 같이 수정할 수 있다.

 

이 코드의 결과는 다음과 같을 것이다.

Hello
World!

 

Scope builder and concurrency

coroutineScope 빌더는 일시 중단 기능 내에서 여러 동시 작업을 수행하는 데 사용할 수 있다. doWorld suspend function 내에서 두가지 동시 작업을 진행하는 예제를 보자.

// 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")
}

두 개의 launch { ... } 블럭은 동시에 실행한다. 애플리케이션이 실행되고 나서 1초 뒤에 Word 1이 출력되고 그리고 1초 뒤에 World 2가 출력된다. doWorld 함수의 coroutineScope는 둘 다 수행하는 것을 보장한다. 그래서 doWorld는 리턴되고 Done을 출력할 수 있게 허락한다. 결과는 다음과 같다.

Hello
World 1
World 2
Done

 

An explicit job

launch 코루틴 빌더는 Job을 반환한다. Job은 발행한 코루틴을 다루고 코루틴이 완료를 명시적으로 기다릴 때 사용할 수 있다. 예를 들어, 하위 코루틴의 완료를 다음과 같이 기다릴 수 있다.

fun main() = runBlocking {
    val job = launch {
        delay(1_000L)
        println("World!")
    }
    println("Hello")
    job.join()
    println("Done")
}

코드는 아래와 같이 수행된다.

Hello
World!
Done

 

Coroutines ARE light-weight

다음 코드를 실행해보자

import kotlinx.coroutines.*

//sampleStart
fun main() = runBlocking {
    repeat(100_000) { // launch a lot of coroutines
        launch {
            delay(5000L)
            print(".")
        }
    }
}
//sampleEnd

10만개의 코루틴을 생성하고 5초후에 점을 찍도록 하였다. 이를 스레드로 바꾸면 어떻게 될까? (아마 대부분은 OOM이 발생할 것이다.)

 

원문