Rlog

Kotlin Coroutines - Basic 본문

Kotlin

Kotlin Coroutines - Basic

dev_roach 2022. 5. 29. 20:32
728x90

Kotlin Coroutines 는 다른 언어에 있는 async / await 과 유사한 부분을 구현할 수 있게 만든 library 라고한다. Kotlin 의 suspend function 은 비동기 함수를 다루는데 Java 의 Future 보다도 안전하고 더 적은 버그가 있다고 한다. 일단 백문이 불여 일타 라고 코드로 먼져 보도록 하자

실습

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
    
    // result
    // Hello
    // World
}

위의 결과를 보면 Hello World 로 출력된다. 그 이유는 무엇일까? 일단 Kotlin 공식문서에 적혀있는 Coroutine instance 의 개념을 한번 알아보자.

A coroutine is an instance of suspendable computation. It is conceptually similar to a thread

코루틴은 중단될수 있는 계산(로직이라하겠음) 이며, Thread 와 매우 유사하다고 한다. 일단 간단하게 위의 코드를 설명해 보면 runBlocking 이란 것은 Coroutines 의 Block 을 의미한다. 만약 runBlocking 안의 코드가 Main Thread 와 다른 Thread 였다면 이 함수는 곧바로 종료되었을 것이다. 하지만 같은 Thread 이기에 이 함수가 종료되지 않는 것이다. 이 코드를 debug 를 찍어서 한번 살펴보자.

보면 알겠지만 World 를 감싸고 있는 Coroutines 의 Context 가 하나 생성되고, Main Coroutines Context 가 생성된다. 하지만 둘다 Main Thread 를 사용한다. 이게 Coroutine 을 Light-weight Thread 라고 부르는 이유이다. 나중에 공부하면 알게 되겠지만 Coroutine Scope 는 특정 Thread 에 종속되지 않는다. Suspend 됬다가 다시 실행될때는 다른 Thread 에서 실행될 수도 있다. 이제 공식문서의 설명에 따라 함수를 하나하나 파헤쳐보자.

 

launch

launch method 는 Coroutine Builder 이다. 즉, Coroutine 을 생성하는 함수이다. Block 안의 함수를 새로운 Coroutines 로 동시적으로 실행할 수 있도록 있게 해준다. 기본적으로 launch method 는 Job 객체를 반환한다.

delay

delay 는 조금 충격적이였는데, Kotlin 공식문서에서 sepcial suspending function 이라는 말을 쓰는데, 진짜 놀랍다. 왜냐면 보통 Thread.Sleep 의 경우 currentThread 자체를 wait 시켜버리는데, delay 함수는 현재 function 의 진행상태를 suspend 시키고, 현재 Thread 에게 너 다른 Coroutine 실행시켜도 되! 라고 말하는 것이기 때문이였다. 여기서 suspend 에 대한 생각이 Coroutine 의 sleep 의 느낌이다. 라는 생각이 들었다. 그래서 Coroutines 는 Thread 라기 보다는 Flow(흐름) 이다. 라는 느낌이 들었다.

runBlocking 

runBlocking 또한 Coroutine Builder 이다. 왜 runBlocking 이라는 이름을 지녔나면 Thread 에게 runBlocking 안의 모든 내용들에 대해 execution 이 끝나기 전까지 기다려(blocking) 라고 말하는 것이다. 공식문서에서는 보통 application code 의 top-level 에서 runBlocking 코드를 볼 수 ㅣㅇㅆ을 거라고 한다.

Structured Concurrency

코루틴이 따르는 중요한 원칙중 하나이다. 쉽게 얘기하면 Coroutine 의 life 는 Coroutine Scope 에 에서 규정되고 실행된다? 이런 느낌이다. 예시를 들기 위해, 위의 예시를 다시 가져오겠다. 아래 예시를 한번 보면 runBlocking 에서 CoroutineScope 를 만들었다. 여기서 만든 CoroutineScope 를 A Scope 라고 하겠다., launch 또한 Coroutine Scope 를 만드는데 이 새롭게 만들어진 CoroutineScope 는 runBlocking 에서 만든 scope 에 속하므로, 당연히 launch 가 끝나는걸 A Scope 가 기다려 주게 된다.

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
    
    // result
    // Hello
    // World
}

이렇게 설명하면 잘 이해가 안갈거 같아서 이해가 잘되기 위해 다른 CoroutineScope 를 쓰는 예시를 가져왔다.

fun main() = runBlocking {
    GlobalScope.launch {
        delay(1000)
        kotlin.io.println("World")
    }
    println("Hello, ")
}

위의 예시를 보면 GlobalScope 라는 것을 쓰는데 이건 Top-Level 의 Coroutine Scope 라고 생각하면 편하다. 즉 application 에서 GlobalScope 를 지니는 것이다. 즉, GlobalScope.launch 로 만들어진 CoroutineScope 는 runBlocking 으로 만들어진 CoroutineScope 에 포함되지 않는다. 이제 Structured Concurrency 의 개념대로면 GlobalScope 는 100% 확률로 실행되지 않을 것이다. 한번 함수를 실행시켜 보자.

위의 결과 처럼 실행되지 않는다. 오늘은 코루틴의 기본적인 개념을 공부해봤는데, 이거 조금만 더 공부해보면 회사코드에 어느정도 넣을 수 있겠다는 생각이 많이 들었다.