본문 바로가기

SW 공부

[Kotlin] 코루틴 기초

kotlinlang.org/docs/coroutines-overview.html

 

Coroutines - Help | Kotlin

 

kotlinlang.org

코루틴에 앞서 suspend 함수

- suspend fun 은 중단점(다른 suspend fun 호출)을 가지는 함수, 중단점이 없다면 suspend fun 의 의미가 없을듯

- 보통의 함수는 시작하고 1번만 반환한다.

- suspend fun 은 코루틴 안에서 동작할 때 중단점에서 반환되고 다시 중단점 이후부터 실행되고를 반복한다.

- suspend fun 은 컴파일에 의해 Continuation passing style 로 변환된다. 별다른건 아니고 일반적인 코드형태

- suspend fun 의 마지막에 continuation 이라는 인터페이스를 가진 객체를 전달되고 함수의 종료 지점에 continuation 객체의 resume 이 실행되어 다른 suspend fun이 재시작 될 수 있게 함

- suspend fun 의 각 중단점은 label 이 달리고 switch 문으로 전환되어 각 중단점부터 실행될 수 있게 한다. 즉, 모든 suspend fun 이 중단되고 

- continuation 객체는 state machine 이라고 불릴 수 있는데, 중단되는 시점의 상태를 저장하고 모든 suspend fun에 passing 된다.

- 결국 코루틴은 함수들을 서브루틴 대신에 콜백방식으로 전환되는 것이 됨

* 그렇다면 함수실행 영역만 분리된다면 모두 비동기로 실행될수 있을 것으로 생각됨

 

 

코루틴

suspend fun 들이 협동하며(co) 실행되는 루틴(routine)

코루틴은 코루틴 빌더에 의해 생성

코루틴 빌더는 여러종류의 코루틴을 반환한다. BlockingCoroutine(runBlocking), StandaloneCoroutine(launch), DeferredCoroutine(async) 등

코루틴 빌더(launch, async...) 는 CoroutineScope 의 확장함수이며, 코루틴 안에서 실행되는 코루틴 블록의 입력으로 CoroutineScope 을 입력해준다.

코루틴 = 코루틴빌더 { 코루틴스코프 ->
	실행 블록
    ...
    ...
    
    코루틴스코프.coroutineContext[Job] // 현재 코루틴을 가져올 수 있음
}

 

 

코루틴은 코루틴 컨텍스트 안에서 실행됨

코루틴 컨텍스트는 코루틴 디스페처를 포함하는데 디스페처는 코루틴 실행시 사용할 스레드 또는 스레드풀을 결정

그래서 모든 코루틴 빌더는 코루틴 디스페처를 파라미터로 입력받을 수 있게 함

코루틴은 디스페처에 따라서 main, defualt, 새로운 스레드에서 동작될 수 있음

withContext 특정 컨텍스트에서 코루틴 블록을 실행하고 실행 결과를 반환함

 

코루틴 빌더 테스트

fun main() = runBlocking<Unit> {
    log("runBlocking scope - ${coroutineContext[Job]}")

    launch {
        log("launch scope - ${coroutineContext[Job]}")
    }

    async {
        log("async scope - ${coroutineContext[Job]}")
    }

    withContext(coroutineContext) {
        log("withContext scope - ${coroutineContext[Job]}")
    }
}
[main] runBlocking scope - BlockingCoroutine{Active}@41906a77
[main] withContext scope - UndispatchedCoroutine{Active}@1cf4f579
[main] launch scope - StandaloneCoroutine{Active}@5b80350b
[main] async scope - DeferredCoroutine{Active}@5d6f64b1

 

CoroutineScope 테스트

fun main(): Unit {
    log("main thread")

    /**
     * CoroutineScope 는 컨텍스트스코프 객체를 반환
     * 코루틴은 아니지만 CoroutineScope 만 구현한 구현체이 코루틴 컨텍스트를 가진다.
     * 실제 코루틴은 job, Continuation<T>, CoroutineScope 등의 구현체
     * 코루틴스코프의 확장함수 launch, async 등을 이용하여 코루틴을 생성할 수 있다.
     * 빌더로 생성된 코루틴은 모두 다른 코루틴컨텍스트를 가진다.며
     * 코루틴이 아니므로 withContext 을 호출할 수 없다. (suspend 함수를 호출할 수 없음)
     * 현재 실행중인 스레드를 중단하지 않는다.
     * 현재 실행중인 스레드의 종료와 상관없이 디스페처에 의해 할당된 스레드에서 동작을 계속한다.
     * 메인 스레드가 중단되면 코루틴도 모두 중단됨
     * Structured concurrency 에 의해 scope 안의 1개의 코루틴에서 exception이 발생하면 모두 중지됨
     * DefaultDispatcher-worker-N 의 이름으로 스레드 생성을 관리, 여러 코루틴이 같은 스레드에서 실행될 수도 있고 다른 스레드에서 실행될 수도 있음
     *
     */
    val t = thread {
        CoroutineScope(Dispatchers.Default).apply {
            log("CoroutineScope ${this}")
            log("CoroutineScope - ${coroutineContext[Job]}")

            val a = launch(Dispatchers.Default) {
                log("launch(Default) scope - ${coroutineContext[Job]}")
            }

//        withContext(Dispatchers.IO) {
//            log("CoroutineScope ${this}")
//            log("withContext(IO) scope - ${coroutineContext[Job]}")
//        }

            async {
                log("async(Unconfined) scope - ${coroutineContext[Job]}")
                delay(1000)
                log("async(Unconfined) scope - ${coroutineContext[Job]}")
                delay(1000)
                log("async(Unconfined) scope - ${coroutineContext[Job]}")
            }
        }
    }

    t.join()
    println("Thread end")

    Thread.sleep(10000)
}
[main] main thread
[Thread-0] CoroutineScope CoroutineScope(coroutineContext=[JobImpl{Active}@1d9788ac, Dispatchers.Default])
[Thread-0] CoroutineScope - JobImpl{Active}@1d9788ac
[DefaultDispatcher-worker-1 @coroutine#1] launch(Default) scope - "coroutine#1":StandaloneCoroutine{Active}@79c294da
[main] Thread end
[DefaultDispatcher-worker-1 @coroutine#2] async(Unconfined) scope - "coroutine#2":DeferredCoroutine{Active}@3b37929e
[DefaultDispatcher-worker-1 @coroutine#2] async(Unconfined) scope - "coroutine#2":DeferredCoroutine{Active}@3b37929e
[DefaultDispatcher-worker-1 @coroutine#2] async(Unconfined) scope - "coroutine#2":DeferredCoroutine{Active}@3b37929e

 

RunBlocking

현재 스레드를 중단하고 새로운 코루틴을 생성하고 실행한다.

메인 함수 또는 테스트 함수에서만 사용할 것 (일반적인 함수에서 suspend 함수를 호출하기 위한 용도로써)

runBlocking 을 사용하면 현재 스레드가 중단되므로, 다른 곳에서 timeout 이 발생할 수 있다. runBlocking 에서 생성한 코루틴이 끝날때까지 항상 기다리므로

runBlocking 을 사용하기 보단, 메인 스레드로부터 스레드를 할당받아 그곳에서 CoroutinScope 를 생성하고 코루틴을 실행하는 방식으로 해야될 것 같다.

그러면 할당 받은 스레드와 비동기로 각 코루틴 디스페처에서 할당한 스레드에서 코루틴을 동작할 것이며, 할당받은 스레드의 자원도 제대로 반환 될 것임

runBlocking 을 사용하면 현재 스레드를 모두 점유하게 되므로, 함수 내에서 일시중단을 하므로써 스레드를 여러 곳에서 사용하게 하려는 코루틴의 장점이 사라진다.

 

 

코루틴 디버깅시 현재 스레드와 현재 코루틴을 로그에 표시하면 편하다. intellij 디버깅 옵션으로 제공됨 (-Dkotlinx.coroutines.debug)