Coroutines(코루틴)은 코틀린의 강력한 기능중의 하나로 많은 앱들에서 사용되고 있는 핵심 기술중 하나다.

얼마나 멋진 기능이길래 많은 사랑을 받고 있는지 한번 깊게 알아보자.

 

🤷‍♂️ 왜 이름이 코루틴일까?

나 또한 그랬지만 종종 코틀린의 '코' 자를 따서 코루틴인줄 착각하는 경우가 있다.

스펠링을 보면 알겠지만 'Ko'가 아닌 'Co'다.

흔히 협동 모드를 'Co-op'이라고 하듯 협동, 협업의 의미인 'Co'를 사용한다.

직역하면 협동하는 루틴들인데, 아직은 와닿지 않으니 조금씩 알아가보자.

여담으로 코루틴은 1958년 멜빈 콘웨이가 용어를 만들었고 어셈블리 프로그래밍에 적용했다고 한다. (위키백과)

(굉장히 오래된 개념이다..)

 

공식문서에 소개된 코루틴의 특징들에 대해 알아보자.

 

🐱‍🏍 가볍다

공식 문서에는 lightweight라고 설명하고 있는데, 메모리와 속도 측면에서 살펴보자.

 

메모리 측면

먼저 코루틴과 비슷한 역할을 하는 스레드의 구조는 아래와 같다.

https://mooneegee.blogspot.com/2015/01/os-thread.html

스레드는 메모리 상에 자신의 1MB정도의 스택을 따로 할당해서 사용한다.

하지만 코틀린의 코루틴은 하나의 스레드 위에서 여러개의 코루틴이 동작하는 방식이며, stackless로 구현되어 있기 때문에, 하나의 코루틴은 수십 바이트의 공간만 차지한다.

 

속도 측면

import kotlin.system.*

fun creating_10k_Thread() {
        val time = measureTimeMillis {
            for(i in 1..5000) {
                Thread(Runnable {
                    Thread.sleep(1)
                }).run()
            }
        }
		println("(The operation took $time ms)")
 }


fun main(args: Array<String>) {
	creating_10k_Thread()
}

https://pl.kotl.in/nPpDEILaB 여기서 직접 실행해 볼 수 있다.

스레드를 5000개 생성하고 각 스레드를 1ms씩 Sleep 시키는 코드이다.

(The operation took 5693 ms)

약 5.7초정도 소요되는 것을 알 수 있다.

스레드가 생성되고 sleep하는 동안 해당 스레드는 다른 일을 하지 못하고 sleep이 종료되기를 기다리기 때문에 1 * 5000 + a  ms이 소요되는 것이다.

 

import kotlin.system.*
import kotlinx.coroutines.*

fun creatingCoroutines(){
        val time = measureTimeMillis {
            runBlocking {
                for(i in 1..5000) {
                    launch {
                        delay(1)
                    }
                }
            }
        }
		println("(The operation took $time ms)")
    }


fun main(args: Array<String>) {
	creatingCoroutines()
}

https://pl.kotl.in/QizhuIkuE 여기서 직접 실행해 볼 수 있다

위와 동일하게 5000개의 1ms씩 sleep(delay)하는 코루틴을 생성하는 코드이다.

(The operation took 400 ms)

실행결과 0.4초로 스레드보다 훨씬 빠른 속도를 보여준다.

 

앞서 말했듯이 스레드는 sleep을 만나면 sleep이 끝날때까지 해당 스레드는 아무것도 하지 못하는 상태(block)가 된다.

하지만 코루틴은 delay를 만나면 해당 코드의 아래 부분은 delay가 끝나면 수행하도록 연기(suspend)시키고, 할 수 있는 다른일을 하러 간다. 

그 뒤, delay가 끝나면 다시 돌아와서 나머지 작업을 수행한다.

즉 스레드처럼 delay를 기다리는 동안 아무일도 못하는 것이 아닌 계속해서 할 수 있는 일을 찾아 수행한다.

참고 : https://medium.com/microsoft-mobile-engineering/kotlin-coroutines-1c8e009cb711

 

✔ 메모리 누수가 거의 없다

Use structured concurrency to run operations within a scope.

공식문서에는 위와 같이 설명하고 있다.

얼마나 불친절한가... 

 

🤷‍♂️ 그래서 structured concurrency가 뭔데?

구조화된 동시성(structured concurrency)은 부모의 operation이 끝나기 전에 자식의 operation이 끝남을 보장하는 것을 말합니다.

 

import kotlinx.coroutines.*


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
}

https://pl.kotl.in/G6fND4ghW 여기서 직접 실행해 볼 수 있다

runBlocking이니 launch이니 이런 것들은 둘 다 코루틴을 생성하고 실행하는 역할을 한다고만 알아두고 나중에 자세히 알아보자.

현재 runBlocking {...} 이 부모이고 launch {...}가 자식인 상황이다.

runBlocking에 의해서 코루틴이 하나 생성되고 launch를 만나 자식 코루틴을 생성한다. 

이때 delay를 만났으므로 println("World!")는 잠시 미뤄두고 지금 할 수 있는 println("Hello")를 수행한다.

1초뒤 미뤄뒀던 println("World!")를 수행한다.

Hello
World!

예제 사이트에서 구동할 경우 출력이 동시에 되지만 실제로는 1초뒤 World!가 출력되는것이 정상입니다.

부모인 runBlocking {...} 입장에서는 Hello를 출력한 시점에서 모든 operation을 수행한 것이지만, 자식인 launch {...}가 완료되지 않았기 때문에 바로 종료되지 않고 자식이 모든 operation을 수행할때까지 기다린다.

해당 scope에 있는 모든 코드의 수행을 보장하기 때문에, 데이터는 유실이나 누수가 일어나지 않는다.

 

참고 :
https://kotlinlang.org/docs/coroutines-basics.html#structured-concurrency
https://en.wikipedia.org/wiki/Structured_concurrency

 

⛔ 작업 취소를 지원한다

코루틴을 실행하다가 다른 액티비티로 넘어가는 등 코루틴 작업이 더이상 필요 없어 실행을 중지시켜야할 경우가 있다.

이런 경우 job.cancel()로 실행중인 작업을 취소할 수 있다.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion 
    println("main: Now I can quit.")    
}

https://pl.kotl.in/Dj-x0iH2W 여기서 직접 실행해 볼 수 있다

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

0, 1, 2 까지 출력한 뒤 작업이 cancel되면서 출력이 멈추는 것을 확인할 수 있다.

참고 : https://kotlinlang.org/docs/cancellation-and-timeouts.html

 

 

🚀 Jetpack과의 호환성

ViewmodelScope와 같이 자체 Scope를 지원하거나, Fragment, SharedPreference 등 여러 Jetpack 라이브러리에서 코틀린을 완전히 지원한다.

참고 : https://developer.android.com/kotlin/ktx

 

 

복사했습니다!