[Kotlin] Coroutine suspend function 은 대체 뭐야?

2021. 6. 13. 22:54Kotlin

반응형

목차

     

    suspend 는 무엇인가. 

    사전을 찾아보면, '중지하다' 라는 뜻의 단어다.

    그렇다면, coroutine 에서의 suspend keyword 는 무엇을 의미할까?

    a function that could be started, paused, and resume.

    시작하고, 멈추고, 다시 시작할 수 있는 함수 라고 한다. (흠, 정의는 굉장히 짧은데 잘 와닿지는 않는다)

    • suspend란 비동기 실행을 위한 중단 지점의 의미 
    • 즉, 잠시 중단(suspend)한다는 의미이고, 잠시 중단한다면 언젠가는 다시 시작(resume)된다는 뜻.

    그래. 정의는 알겠어. 근데 그래서..? 뭐 뭐가 다른데 !!!!!  이 찝찝함을 이번 테스트를 통해 시원하게 해소했다. 나와 같이 이해가 잘 안 된 분들을 에게 조금이나마 도움이 되길 🙏

     

     

    suspend function 이 없다면?

    나는 항상 어떤 개념을 처음 접할 때, 이게 필요한 이유 또는 이것이 없다면? 을 생각하면 이해가 빨리 된다. 그래서 이번에도 suspend 가 없다면? 을 공부해 보았다. 

    하나의 thread 가 block 될 경우, 해당 thread 는 다른 작업을 할 수 없는 block 상태에 놓이게 된다. 즉, blocked 상태가 끝날 때까지 해당 thread 는 중지 상태인 것. (아무것도 못해!)

    BUT, suspend function 을 사용한다면 blocked 된 상태에 놓일 때, 그 작업을 suspend 하고 그 기간 동안 thread 에서 다른 작업을 수행할 수가 있다. 얼마나 자원 효율적인가.

    비유를 해보자면,

    • 내 하루 : 하나의 thread
    • 안드로이드 개발을 하다가 빌드 시간만 5분 걸리는 프로젝트를 빌드시킴 : 5분동안 blocked 당한 상태
    • suspend function 의 경우 : 빌드가 돌아가는 동안 서버 개발(다른 작업 수행)을 한다.
    • suspend 가 아닌 경우 : 안드 빌드가 끝날 때까지 가만히 기다린다.. OMG (빌드를 기다리는 일만큼 지루한 일이 있을까..)

     

    아무리 개념 설명을 하면 뭐하나. 직접 테스트 해보는게 답!

    suspend function 을 사용하는 경우와 하지 않을 때 thread 자원을 어떻게 사용하는지를 보려고 한다.

    1. 정말로 suspend 함수에서는 thread 가 blocked 되지 않고, suspend된 상태에서 다른 작업을 수행하는지?
    2. blocked 된 작업이 suspend 되고 다시 resume 되는지?

    이 테스트에서는delay() 와Thread.sleep() 을 사용해 작업을 block 시켰다.

    • delay() 자체는 suspend 함수로 coroutineScope 에서만 사용 가능
    • Thread.sleep() 은 suspend 함수가 아님.

    각 케이스에 따라 아래의 코드를 실행시켜 테스트를 진행했다.

     

     

    1️⃣ suspend function 사용하지 않은 테스트 코드

    fun main() {
        launch(Dispatchers.IO) {
          // 병렬로 2개 함수 실행 
          async { nonSuspendTask1() }
          async { nonSuspendTask2() }
        }
    }
    
    fun nonSuspendTask1() {
        Thread.sleep(3000)
        Log.d(TAG, "[nonSuspendTask1] After 3s in (${Thread.currentThread().name})")
        Thread.sleep(3000)
        Log.d(TAG, "[nonSuspendTask1] After 6s in (${Thread.currentThread().name})")
    
        Log.d(TAG, "[nonSuspendTask1] END in (${Thread.currentThread().name}) *****")
    }
    fun nonSuspendTask2() {
        Thread.sleep(1000)
        Log.d(TAG, "[nonSuspendTask2] After 1s in (${Thread.currentThread().name})")
        Thread.sleep(3000)
        Log.d(TAG, "[nonSuspendTask2] After 4s in (${Thread.currentThread().name})")
    
        Log.d(TAG, "[nonSuspendTask2] END in (${Thread.currentThread().name})*****")
    }

     

    2️⃣ suspend function 사용 테스트 코드 

    fun main() {
        launch(Dispatchers.IO) {
          // 병렬로 2개 함수 실행 
          async { suspendTask1() }
          async { suspendTask2() }
        }
    }
    
    suspend fun suspendTask1() {
        delay(3000)
        Log.d(TAG, "[suspendTask1] After 3s in (${Thread.currentThread().name})")
        delay(3000)
        Log.d(TAG, "[suspendTask1] After 6s in (${Thread.currentThread().name})")
    
        Log.d(TAG, "[suspendTask1] END in (${Thread.currentThread().name})*****")
    }
    suspend fun suspendTask2() {
        delay(1000)
        Log.d(TAG, "[suspendTask2] After 1s in (${Thread.currentThread().name})")
        delay(3000)
        Log.d(TAG, "[suspendTask2] After 4s in (${Thread.currentThread().name})")
    
        Log.d(TAG, "[suspendTask2] END in (${Thread.currentThread().name}) *****")
    }

     

    테스트 결과

    각 함수에 로그를 찍고, 로그에는 해당 함수가 어느 thread 에서 실행되는지 thread 의 이름을 같이 로깅했다.

     

    1️⃣ suspend function 을 사용하지 않은 경우 결과 

    각 함수 Task1() 와 Task2() 는 각기 다른 thread 에서 수행되었다.

    • Task1() : DefaultDispatcher-worker-2
    • Task2() : DefaultDispatcher-worker-1

     

    이유는, Thread.sleep() 동안 thread 가 blocked 되어 다른 작업을 수행할 수 없기 때문에, 각 함수는 서로 다른 스레드에서 실행된다.

    각 thread 의 작업 플로우는 아래와 같다.

     

     

    2️⃣ suspend function 을 사용한 경우

    각 함수 Task1() 와 Task2() 는 하나의 동일한 thread(DefaultDispatcher-worker-1) 에서 수행되었다.

    이유는, delay() 동안 thread 가 suspend 되어 다른 함수의 작업을 수행할 수 있기 때문이다. (단, suspend 를 사용한다고 무조건 하나의 스레드에서만 실행되는 것은 아니다. 여기서는 2개의 함수(task1, task2) 의 작업(Log.d를 실행하는 것)이 서로 다른 시간에 일어나기 때문에 가능한 것.)

    thread 의 작업 플로우는 아래와 같다.

     

     

    suspend function 이 suspend 된 후 resume 할 수 있는 이유

    • A 함수에 suspend 키워드를 추가하면 ⇒ 바이트코드를 보면 A 함수의 파라미터에 "Continuation"이 추가됨.
    • 이 Continuation이 프로그램의 현재 상태를 저장한다. Continuation wrapper 내에 나머지 코드를 전달하는 것처럼 생각할 수 있다.
    • 현재 작업이 완료되면 continuation block 이 실행된다.

    위의 suspendTask1() 함수의 디컴파일된 코드를 보면 아래와 같이 함수 파라미터로 Continuation 객체가 들어가 있는 것을 확인할 수 있다.

     

    Continuation 동작은 이 유튜브 영상을 참고하자.

     

     

    결론

    간단 요약.

    • coroutine suspend 함수는 thread 를 block 하지 않는다.
    • 따라서, 하나의 thread 에서 여러 개의 coroutine 을 실행할 수 있다. 특정 작업이 suspend 되고 resume 될 때까지 이 사이에 다른 작업을 수행할 수 있기 때문.
    • 그래서 suspend 는 많은 concurrent 작업을 지원하면서 blocking 에 대한 메모리를 절약할 수 있다.

    지금까지는 coroutine 이 왜 light-weight 한지 정확히 이해를 하지 못했었는데, suspend 를 제대로 공부하면서 이제야 막힌 부분이 뻥 뚫렸다. 너무 시원해!!!!

     

     

    혹시 잘못된 내용이나 보완되어야 할 내용이 있다면 자유롭게 코멘트 부탁드립니다 🧤🧤🧤🧤🧤

    반응형