2021. 6. 13. 22:54ㆍKotlin
목차
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 자원을 어떻게 사용하는지를 보려고 한다.
- 정말로 suspend 함수에서는 thread 가 blocked 되지 않고, suspend된 상태에서 다른 작업을 수행하는지?
- 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 를 제대로 공부하면서 이제야 막힌 부분이 뻥 뚫렸다. 너무 시원해!!!!
혹시 잘못된 내용이나 보완되어야 할 내용이 있다면 자유롭게 코멘트 부탁드립니다 🧤🧤🧤🧤🧤
'Kotlin' 카테고리의 다른 글
[Kotlin in Action] 8장. 고차함수와 inline function (inline 함수의 장단점, 사용 이유 등) (0) | 2022.01.09 |
---|---|
[Kotlin in Action] 4장. 클래스, 객체, 인터페이스 (0) | 2021.11.03 |
[Kotlin in Action] 3장. 함수 정의와 호출 (0) | 2021.10.09 |
[Kotlin] static, object, companion object 차이 (1) | 2021.06.27 |
[Kotlin] Scope Functions - let, run, with, apply, and also 에 대하여 (0) | 2020.02.17 |