ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Coroutine] 코루틴 학습 - 1 (Coroutine?)
    Java/Kotlin 2022. 4. 16. 16:47
    반응형

    Coroutine?

     JVM에서 병렬 처리를 위해서 이미 RxJava, Reactor, 자바에서 지원하는 Multithreading 등 유용한 도구들이 많이 있습니다. 많은 사람들이 비동기 처리를 위해 콜백 패턴을 활용하기도 합니다. 효율적인 비동기 처리를 위한 옵션들이 이미 많이 있는 상황에서 코루틴은 어떤 차별점을 가지고 있을까요? 먼저 앞서 소개한 도구들에 대해서 정리해 보겠습니다.

     

    Multi-thread

    • 앞서 소개한 도구들은 병렬 처리를 위해 쓰레드를 생성하고, 연산을 위한 쓰레드 스위칭이 필요합니다.
    • 쓰레드를 생성하는 일은 많은 비용을 초래합니다.
    • 쓰레드를 취소하기 위한 메커니즘이 존재하지 않습니다. 이는 메모리 누수 문제로 이어질 수 있습니다.
    • 잦은 쓰레드 스위칭은 제어하기 어렵습니다.
    • 코드량이 많아지고 복잡해집니다.

     

    Callbacks

    • 콜백 패턴은 쓰레드를 취소할 방법이 없기 때문에 제대로 관리하지 못한다면 여전히 메모리 누수 문제가 발생할 수 있습니다.
    • 콜백 지옥으로 인하여 코드가 상당히 복잡해질 수 있습니다.

     

    RxJava, reactive streams

    • RxJava와 Reactor는 쓰레드 스위칭과 병렬 처리를 지원하고, 스트림 처리에 상당히 유용합니다.
    • 위에서 보았던 도구들과는 다르게 취소 연산을 지원합니다.
    • 다만 사용하기 복잡합니다.

    Kotlin coroutine

    • 코루틴은 suspend 연산자를 통해서 쓰레드를 블로킹 시키지 않고도 비동기 처리가 가능합니다. 비동기 처리가 끝난 후에, 일시중단된 코루틴은 처리가 완료되면 대기하다가 쓰레드 할당이 되면 일시중단된 지점에서 연산을 지속합니다.
    fun main() = runBlocking {
        launch {
            delay(2000L)
            println("Awake!")
        }
        println("코루틴의 일시중단 함수는 쓰레드를 블로킹하지 않는다.")
    }

    • 위 코드에서 확인이 가능하듯이 일시중단 함수는 쓰레드를 중단 시키지 않기 때문에 아래의 출력문이 먼저 실행되고, 2초간의 딜레이가 지나고서 "Awake!" 메세지가 출력됩니다.
    • runBlocking 은 주어진 블록이 완료될 때까지 일시중단 시키는 코루틴을 생성하여 실행시키는 코루틴 빌더입니다.
      • 코루틴 안에서 runBlocking 의 사용은 권장되지 않으며, 위 코드는 예시를 위해 사용하였습니다. 왜 권장되지 않는지는 추후에 다른 포스트에서 정리하려고 합니다.
    • 참고로 delay 또한 중단함수이며, 모든 중단 함수들은 코루틴 안에서만 호출될 수 있습니다.
    • 위와 같이 코루틴의 코드는 별다른 노력 없이 비동기 처리를 손쉽게 구현할 수 있습니다.
    • 간결하게 작성할 수 있는 코드에서 오는 가독성 또한 장점입니다.

    코루틴은 어떻게 동작하는가?

    • 코루틴을 일시중단하는 것은 연산을 중간에 중단하는 것을 의미합니다.
    • 코루틴이 일시중단된 동안 쓰레드는 블로킹되지 않고 다음 연산을 진행합니다.
    • 일시중단 되었던 함수는 다시 재개되어야 할텐데, 이를 위한 객체가 바로 Continuation 입니다.
    • 코루틴이 일시중단 되었을 때, 해당 suspend 함수는 Continuation 객체를 리턴합니다.
    • Continuation은 처음 실행중이던 쓰레드와 다른 쓰레드에서 실행될 수도 있습니다.
    suspend fun main() {
        println("Before")
        suspendCoroutine<Unit> {  }
        println("After")
    }
    
    // Before
    // 무한 대기..

     위 코드는 "Before" 까지만 출력하고 이후에는 진행하지 못하고 정지 상태에 머물러 있게 됩니다. 방금 설명한 Continuation 을 통해 일시중단 된 함수를 재개시키지 않았기 때문입니다.

    suspend fun main() {
        println("Before")
        suspendCoroutine { continuation: Continuation<Unit> ->
            println("Before too")
            continuation.resumeWith(Result.success(Unit))
        }
        println("After")
    }
    
    // Before
    // Before too
    // After
    // exit..

     위와 같이 continuation의 resume 함수를 호출하면 일시중단된 함수는 재개되어 연산을 수행하게 됩니다. resumeWith() 함수의 파라미터로 받는 Result는 suspendCoruotine 이 수행된 후에 반환하게 될 타입과 같습니다. 반대로, 실패한 경우엔 Throwable 타입을 인자로 넘겨주면 됩니다.

    suspend fun main() {
        println("Before")
        val result = suspendCoroutine { continuation: Continuation<Int> ->
            println("Before too")
            thread {
                Thread.sleep(1000)
                continuation.resumeWith(Result.success(1000))
            }
        }
        println(result)
        println("After")
    }
    
    // Before
    // Before too
    // 1000
    // After

    참고자료

    https://www.amazon.com/Kotlin-Coroutines-Deep-Marcin-Moskala/dp/8396395837

    반응형

    댓글

Designed by Tistory.