[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 를 제대로 공부하면서 이제야 막힌 부분이 뻥 뚫렸다. 너무 시원해!!!!

 

 

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

반응형