Beeeam

코루틴 공식 문서 읽기 (Coroutines basics) 본문

Kotlin

코루틴 공식 문서 읽기 (Coroutines basics)

Beamjun 2023. 3. 3. 17:47

안드로이드 면접 스터디를 진행하면서 코루틴에 대해서 공부하게 되었는데 이를 정리해보려고 한다. 참고로 코루틴 공식 문서를 참고하여 공부하였다.

https://kotlinlang.org/docs/coroutines-basics.html

 

Coroutines basics | Kotlin

 

kotlinlang.org

코루틴?

코루틴은 비동기적으로 실행되는 코드를 간소화 하기 위해 사용되는 설계 패턴이다.

스레드와 개념적으로는 비슷하지만 코루틴은 특정 스레드에 포함되지 않는다. 그리고 코루틴은 스레드보다 더 가벼울 수 있다.

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: 코루틴 빌더로 새로운 코루틴을 생성하고, 나머지 코드와 동시에 실행한다. 근데 보통 나머지 코드가 먼저 실행된다.

delay: delay는 일시정지를 하는 함수이다. delay에 인자로 특정 시간을 전달 해주면 전달된 특정 시간 만큼 코루틴이 정지하게 된다.

runBlocking: 얘도 코루틴 빌더이고, 코루틴 범위를 설정한다. 얘를 빼고 작성하면 오류가 발생한다.

 

Structured concurrency

코루틴은 구조화된 동시성의 원칙을 따른다. 구조화된 동시성은 새로운 코루틴은 코루틴의 수명을 구분하는 특정 코루틴 스코프에서만 실행이 될 수 있다는 것을 의미한다.

 

Extract function refactoring

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

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

launch 블럭 안에 doWorld 라는 함수가 있는데 이는 suspend 라는 식별자를 갖고 있다. suspend가 뭘까? suspend 함수는 코루틴 안에서는 일반적인 함수로 사용될 수 있는 함수로, 이 suspend 함수는 일반 함수과 달리 delay와 같은 다른 중단 함수들을 호출 할 수 있다.

이러한 이유는 suspend 함수 역시 중단 함수이기 때문에 특정 코루틴 컨텍스트 안에서 실행 되고 있고, 코루틴 컨텍스트 안에서는 모든 중단 함수를 호출 할 수 있기 때문이다.

 

Scope builder

CoroutineScope{ } 빌더를 사용하면 코루틴의 사용자가 지정한 범위를 선언하고, 생성할 수 있다. 이렇게 생성된 코루틴은 모든 자식 코루틴들이 끝날 때 까지 완료 되지 않는다.

runBlocking, coroutineScope 빌더들은 비슷해 보일 수 있다. 둘 다 본문과 모든 자식들이 완료 될 때 까지 기다리기 때문이다. 하지만 다르다.

runBlocking과 달리 coroutineScope는 자식들의 종료를 기다리는 동안 현재 스레드를 블록 하지 않는다.

 

Scope builder and concurrency

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

위의 코드를 실행하면 결과는 다음과 같이 나온다.

Hello
World 1
World 2
Done

doWorld 함수를 실행하게 되면, doWorld 함수 안에 첫 번째 launch를 실행하게 된다. delay가 걸려 있기 때문에 실행을 하면서 다음 launch로 이동한다. 두 번째 launch 안에도 delay가 있기 때문에 다음으로 이동한다. 그러면 마지막에 있는 “Hello”를 먼저 출력하게 되고, 다음에 delay가 더 짧은 두 번째 launch의 결과인 “World 1”이 출력이 된다. 그리고 첫 번째 launch의 결과인 “World 2”가 출력이 된 후에 doWorld를 빠져나와 “Done”이 출력이 된다.

 

An explicit job(명시적 작업)

launch 코루틴 빌더는 실행된 코루틴에 handle인 Job 객체를 반환하고, 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

“Hello”와 “Done”이 출력 되는 명령 사이에 job.join() 때문에 자식 코루틴이 끝날 때 까지 기다리게 된다. 그래서 “Hello” → “World” → “Done” 순서로 출력이 된다.

 

Coroutines are light-weight

코루틴은 JVM 스레드에 비해 리소스가 집약적이지 않다. 스레드를 사용할 때 JVM의 가용 메모리를 소진하는 코드는 코루틴을 사용하면 리소스 제한에 도달하지 않고 표현이 가능하다.