코틀린 코루틴을 공부하며 정리한 글입니다.
혼자 공부하고 정리한 내용이며, 틀린 부분은 지적해주시면 감사드리겠습니다 😀
책에서 자세하게 다루지는 않지만, 이전 포스팅에서 본 Thread.sleep
과 중단 함수에서 사용 가능한 delay
는 과연 어떤 차이가 있길래 단일 스레드 환경에서 차이가 나는걸까?
🛠️ 개발 환경 및 테스트 환경
- kotlin : 1.9.24
- Spring Boot : 3.3.1
Thread
이 개념을 이해하기 위해서는 우선 Thread
란 무엇인지 알아야 한다. 간단하게 스레드는 프로세스 안에서 작업을 진행하는 작업자라고 생각하면 된다.
1시간에 손님이 100명 방문하는 카페가 있는데, 알바생이 1명이면 어떨까? 음료를 주문하고, 그 음료를 받기까지 시간이 오래 걸릴 것이고, 알바생도 힘들 것이다. 때문에 여러 명의 알바를 고용해서 업무를 진행한다. 여기서 카페가 Process
이며, 손님은 Request
, 알바생이 바로 Thread
라고 생각하면 편하다.
sleep()
이제 Thread
에 대해서 간단한 지식을 가졌으니, sleep
은 과연 뭐하는 함수인지 확인해보자.
Thread.sleep(millseconds)
함수는 현재 스레드를 지정한 시간 동안 블로킹을 한다. 즉, 해당 스레드는 지정된 시간이 지날 때까지 아무런 작업도 할 수 없는 수면 상태에 빠지는 것이다.
앞서 본 비유를 그대로 이용해서 보면, 알바생이 업무 시간에 지정한 시간동안 잠을 자는 것이다.
fun main() {
println("[${Thread.currentThread().name}] Before sleep")
Thread.sleep(1000)
println("[${Thread.currentThread().name}] After sleep")
}
[main] Before sleep (54.053s)
[main] After sleep (55.077s)
위 코드를 실행시켜보면 Before sleep이 출력되고 1초 뒤에 After sleep이 출력되는 것을 볼 수 있다. 즉, main
함수를 실행하는 main
스레드가 아예 잠에 들어서 이후 코드를 진행하지 않게 되는 것이다.
delay()
그렇다면 delay()
를 사용했을 때는 어떨까? 결과를 먼저 확인해보자.
suspend fun main() = coroutineScope {
println("[${Thread.currentThread().name}] Before delay")
launch {
delay(1000L)
}
println("[${Thread.currentThread().name}] After delay")
}
[main] Before delay (8.319s)
[main] After delay (8.348s)
시간을 보면 알 수 있듯, 모든 println
이 먼저 출력되고, 1초 뒤에 프로그램이 종료된다. delay(1000L)
의 앞, 뒤로 로그를 넣어서 보면 다음과 같은 결과가 나오게 된다.
[main] Before delay (8.319s)
[main] After delay (8.348s)
1
2
[main] Before delay (8.319s)
1
[main] After delay (8.348s)
2
위처럼 2가지 결과가 랜덤으로 나오는데, 이는 콜백 함수가 비동기적으로 처리되기 때문에 launch
블록이After delay가 출력이 되기 전에 실행이 될 수도 있고, After delay가 출력이 되고 실행이 될 수 있기 때문이다.
coroutineScope
,launch
는 이후 포스팅에서 다룰 예정입니다.
다시 본론으로 돌아와서, 이전 포스팅에서 본 것과 같이 delay
를 쓴 것 만으로도 단일 스레드에서 모든 요청을 바로바로 처리한 것을 확인할 수 있었는데, 어떻게 이런 일이 가능한 것일까? 이는 delay
함수의 구현을 살펴보면 알 수 있다.
public suspend fun delay(timeMillis: Long) {
if (timeMillis <= 0) return // don't delay
return suspendCancellableCoroutine { cont: CancellableContinuation<Unit> ->
// if timeMillis == Long.MAX_VALUE
// then just wait forever like awaitCancellation, don't schedule.
if (timeMillis < Long.MAX_VALUE) {
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
}
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T = suspendCoroutineUninterceptedOrReturn { uCont ->
val cancellable = CancellableContinuationImpl(
uCont.intercepted(), resumeMode = MODE_CANCELLABLE
)
/*
* For non-atomic cancellation we setup parent-child relationship immediately
* in case when `block` blocks the current thread
* (e.g. Rx2 with trampoline scheduler),
* but properly supports cancellation.
*/
cancellable.initCancellability()
block(cancellable)
cancellable.getResult()
}
delay
는 스레드를 재우는 것이 아닌, suspendCancellableCoroutine
내부에 있는 uCont.intercepted()
을 통해 현재 코루틴을 중단(suspend)시키고, 특정 작업이 완료될 때까지 다른 코루틴의 작업을 처리하면서 기다리게 된다.
앞서 본 비유를 그대로 이용해서 보면, 스무디 주문이 들어오면 블렌딩을 하는 동안 다음 커피 주문을 받고, 샷을 내리는 동안 블렌딩 완료된 음료를 준비해서 손님에게 전달하고, 샷을 다 내렸는지 확인하고, 아직 진행 중이라면, 다음 주문을 받고, 다 내려진 샷을 준비해서 손님에게 전달하는 형태이다.
즉, 중단 함수를 사용하는 것은 이 함수가 중단된 동안 다른 일을 처리하지만, 계속해서 앞선 작업을 확인하고, 처리하는 형태인 것이다.
정리
Thread.sleep
은 현재 작업을 처리하는 스레드를 재워 블로킹을 하는 것이고, delay
는 스레드를 블로킹하는 것이 아닌, 중단(suspend)를 시키고, 다른 작업을 처리하는 것이다. 다른 작업을 처리하는 중에 중단이 일어나면, 다시 기존 작업을 처리하러 돌아온다.
'Book Study > [Kotlin] 코틀린 코루틴' 카테고리의 다른 글
[Coroutine] 플로우 (0) | 2024.12.15 |
---|---|
[Coroutine] 핫 데이터 소스와 콜드 데이터 소스 (4) | 2024.11.13 |
[Coroutine] 코루틴 빌더(2) (0) | 2024.08.22 |
[Coroutine] 코루틴 빌더(1) (0) | 2024.08.14 |
[Coroutine] 중단 함수 (0) | 2024.08.06 |