들어가며
그 동안 우리는 Kotlin coroutine 을 동작하는 구성 요소들(dispatcher, scope) 에 대해 배워왔는데요
coroutine 을 실제 적용하기 위해서는 근본적으로 suspend 함수가 어떻게 동작 하는지에 대해 알아야 합니다.
suspend 함수란 무엇이고, 어떻게 동작하게 될까요? 차근차근 알아봅시다.
Suspend
자 그럼, Suspend 함수는 도대체 무엇일까요?
What is suspend function?
Suspend 함수는 한줄로 요약하면 중단가능한 함수 입니다.
중단가능한 함수는 함수가 가동하면서 언제든지 다른 요인에 의해 중단이 가능하다 라는 것을 의미합니다.
위 그림 예시를 보면, 빨간색 Box 의 function 이 특정 Thread 에서 일을 처리하다가,
어떤 요인(I/O blocking.. etc) 에 의해서 Suspended 라는 상태에 놓이고 함수 처리가 중단됩니다.
일반적인 동기 방식의 처리 프로세스는 blocking 상태가 되면, Thread 가 다른 일을 하지 못하고 대기하는 상황이 발생합니다.
하지만, coroutine 에서는 function 을 Suspended 라는 상태로 전이한 다음에 빨간색 function 을 중단하여, 갈색 / 회색 Box 의 function 을 실행시킬 수 있습니다.
다른 일들을 처리하다가 빨간색 Box 의 functioin 에 대하여 blocking 처리가 해지되면 Suspended 상태를 해지하여, 나머지 빨간색 box 의 function 로직을 처리하게 되는 것입니다.
정말 신기하죠?
이걸 어떻게 해주는 것인지 한번 파헤쳐봅시다
How suspend function works?
어떻게 동작하는지를 알아보기 위해 code 단으로 들어가보겠습니다
import kotlinx.coroutines.delay
class CoffeeMachine {
suspend fun makeCoffee(menu: String): String {
delay(1000)
return "$menu coffee"
}
}
위와 같이 suspend function 을 만들었습니다. Coffee 를 만드는 로직을 작성하고, 만드는 시간은 1초의 delay 를 주었습니다.
suspend function 의 원리에 따르면 delay 가 주는 시점에는 함수를 중단시키고, suspend 함수를 호출한 곳에서 다른일을 할 수 있도록 진행할 예정입니다.
정말 그렇게 되는지 코드단으로 가볼게요
suspend function 을 decompile 해보니.. suspend 키워드는 없어지고 일반 함수로 바뀐뒤에
function 의 마지막 인자로 Continuation 클래스가 들어가져 있는 것을 볼 수 있습니다.
Continuation 클래스 에 대한 설명은 아래에서 계속하겠습니다
여러분들은 중요한 것을 눈치채셨나요?
바로 kotlin 이 마법을 부리는게 아니라 단지, compile 레벨에서 suspend 키워드만 정의되면
나머지는 kotlin compiler 가 알아서 function 이 중단가능하게끔 만들어준다는 것입니다
즉 label20 에서는 Continuation 객체에 대한 선언부를 만들어주고 끝나는 것을 볼 수 있네요
그렇다면 실제 로직의 중단점을 어떻게 마킹해주게 될까요?
실제 로직은 label 에 대한 case 문 기반으로 분기처리하는 것을 볼 수 있네요. 위 코드를 해석하면 아래와 같이 해석됩니다.
Label | Logic |
0 | label 을 1로 marking 하고 delay 를 호출하고 함수를 리턴한다 |
1 | swich case 문을 탈출하여 coffee 를 만들어 함수를 리턴한다 |
즉 label 에 대한 상태 관리를 통하여 중단가능한 지점들을 상태 관리하는 것이 suspend 함수의 가장 중요한 포인트 입니다.
한마디로 FSM(Final state machine) 기반으로 상태를 관리하여 함수의 상태 관리를 하게 해주는 것이죠
위 예시는 조금 단순하다 보니 중단점들을 더 만들고 어떻게 되는지 한번 봐볼까요?
import kotlinx.coroutines.delay
class CoffeeMachine {
suspend fun makeCoffee(menu: String): String {
val sugar = getSugar()
val cream = getCream()
delay(1000)
return "$menu coffee $sugar $cream"
}
private suspend fun getCream(): String {
delay(1000)
return "cream"
}
private suspend fun getSugar(): String {
delay(1000)
return "sugar"
}
}
sugar 와 cream 을 얻어오는 중단함수들을 만들고, 제작하는데에 1초 delay 를 주어 만들도록 로직을 변경해보았는데요
실제 switch case 문이 어떻게 되는지 봐봅시다
조금 길죠? ㅎㅎ;;
각 상태에 대해 색깔별로 구분하여 표시를 해보았는데요.
label0 의 상태는 sugar 를 얻어오는 것에 집중하고,
label1 의 상태는 cream 을 얻어오는 것에 집중하고,
label2 의 상태는 delay 를 하는 것에 집중하고,
label3 의 상태는 coffee 를 만들어 리턴하는 것에 집중하고 있습니다.
즉 중단되는 포인트마다 상태관리가 들어가는 것을 볼 수 있네요.
그렇다면 우리가 지금까지 suspend 함수가 어떻게 중단되는지에 대해서는 이해했는데,
중단한 것을 어떻게 다시 실행하게 하는걸까요?
바로 그 중단점을 재개하는 역할은 바로 Continuation 클래스에 담겨져 있습니다.
Continuation
Continuation 객체가 중단된 지점에서 재개하는 역할을 담당하는데요. 이 클래스의 인터페이스를 한번 봐봅시다
/**
* Interface representing a continuation after a suspension point that returns a value of type `T`.
*/
@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
Continuation 객체에서는 2가지 중요한 포인트가 있습니다.
- CoroutineContext 를 필드 변수로 정의함으로써 CoroutineContext 에 등록된 Context 들을 유지할 수 있도록 합니다.
- resumeWith 함수를 통해 suspend 함수를 실행시키고, 이전에 중단되었던 값을 Result 객체로 주입받아 결과를 유지하여 실행시킬 수 있도록 합니다.
그럼에도 불구하고 잘 이해가 안되죠? 왜냐하면 resumeWith 가 실제로 어떻게 구현되는지 모르니 조금 막연한 느낌입니다.
실제 Continuation 의 구현체로 많이 쓰이는 BaseContinuationImpl 클래스로 가보겠습니다.
이 로직을 보니 깨닫게 되었습니다.
중단 함수에 대한 재실행은 큰 마법이 없다는 것을.. 단순하게 while loop 를 통해 invokeSuspend 함수를 호출해줌으로써 completion 될 때까지 반복하게 되는 것이죠.
invokeSuspend function 은 우리가 suspend 키워드를 붙인 function 을 의미합니다
이렇게하여 suspend function 이 어떻게 동작하게 되는지를 알게 되었네요.
추가적으로 함수의 suspended 여부를 판단하고, contiunation 의 resumeWith 를 호출하는 구성요소는 바로 이전 시간에 소개했던 Dispatcher 이니 참고해주세요 :)
https://huisam.tistory.com/entry/coroutine2
정리하며
머리가 지끈지끈 하시겠지만 간단하게 몇 줄 요약을 해볼게요
- suspend 함수는 중단 가능한 함수이다
- suspend 함수의 중단점 관리는 State Machine 기반으로 kotlin compiler 가 자동으로 관리한다
- Continuation 객체를 통해 중단된 함수를 재실행한다
참고
Coroutines: Suspending State Machines
'Developer > Kotlin & Java' 카테고리의 다른 글
Java21 Virtual thread vs Kotlin Coroutine - 가상쓰레드와 코루틴에 대해 고찰해보자 (2) | 2024.01.27 |
---|---|
[Kotlin] Kotlin Annotation 에 대해 톧아보기 (0) | 2023.04.01 |
[Kotlin] Coroutine - 5. 코루틴의 Channel 의 모든 것 (0) | 2023.03.05 |
[Kotlin] Coroutine - 4. 코루틴에서의 예외(exception) 핸들링 (0) | 2023.02.04 |
[Kotlin] Coroutine - 3. 코루틴의 Flow 활용 (2) | 2022.11.27 |