코루틴은 왜 스레드 보다 가볍다고 할까?
코루틴 하나가 새로 생성되어 실행된다는 것이 그와 동시에 새로운 스레드 또한 생성되는 것을 의미하는 것은 아닙니다. (정확히 말하자면 이것은 코루틴 생성 시 스케쥴러 설정에 따라 다릅니다.)
사실, 코루틴은 스케쥴링 가능한 코드 블록 혹은 이러한 코드 블록들의 집합이라고 볼 수 있습니다.
이해를 돕기 위해서 코루틴이 실행되는 과정을 다음과 같이 그림으로 나타내 보았습니다.
제일 왼쪽에 보면 CoroutineScope 가있습니다. 우리가 어떤 코루틴을 실행하기 위해서는 어떤 코루틴 스코프에 속해 있어야 합니다.
현재 코루틴 스코프가 갖는 컨텍스트(CoroutineContext) 에서 Dispatcher 는 UI Dispatcher 라고 되어 있습니다. 이것은 현재 스코프에서 실행되는 중단 함수들은 UI Thread 에서 수행 된다는 것을 의미합니다.
이제 이 스코프 안에서 코루틴을 하나 만들었습니다 (가운데 있는 보라색 이미지).
이 코루틴은 자신이 실행되는 스코프(부모)의 컨텍스트를 그대로 상속하고 Dispatcher 만 ThreadPoolDispatcher 로 재정의 하였습니다. (재정의 하지 않으면 기본적으로 속해 있는 스코프로부터 모두 상속합니다.
이제 이 코루틴에서 수행되는 함수는 ThreadPoolDispatcher 를 이용하여 워커(백그라운드) 스레드에서 수행됩니다.
이때, launch { } 와 같이 빌더를 실행했을 경우 마지막으로 넘긴 코드 블록{ code block}, 즉, 실제 수행하고자 하는 로직이 담긴 코드 블록은 Continuation 이라는 단위로 만들어집니다.
Continuation 은 CPS(Continuation Passing Style)에서 이야기는 Continuation 개념의 구현체 입니다.
이렇게 Continuation 으로 변경 된 코드 블럭은 최초에 suspend 상태로 생성 되었다가 resume() 요청으로 인해 resumed 상태로 전환되어 실행 됩니다.
Continuation의 재개(resume)가 요청될 때마다 현재 컨텍스트의 dispatcher 에게 dispatch(스레드 전환) 가 필요한지 isDispatchNeeded() 함수를 이용해 확인 한 후 dispatch가 필요하면 dispatch()함수를 호출하여 적합한 스레드로 전달하여 수행됩니다.
하지만 만약 Dispatcher를 Dispatcher.Main.Immediate 또는 Dispatcher.UnConfinied를 사용하고 있다면, 디스패치 없이 현재 스레드에서 즉시 실행될 것입니다. 왜냐하면 이들은 isDispatchNeeded()값으로 false를 리턴하기 때문입니다.
앞서 포스팅한 Dispatcher의 내용을 보지 않았다면 아래 링크를 참고 바랍니다.
https://jtm0609.tistory.com/263
[Android] Coroutine Dispatcher에 대한 고찰 - Deep Dive
우리는 Kotlin의 흐름과 몇 가지 기본 개념의 내부 작동을 탐구했지만, 코루틴 디스패처에 대해서는 깊이 파고든 적이 없습니다. 이번 포스팅을 통해 Coroutine DisPatcher에 대해 깊게 알아보는 시간을
jtm0609.tistory.com
Coroutines are light-weight
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
launch {
delay(5000L)
print(".")
}
}
}
위 예제에서 100,000 번의 연산을 스레드를 생성해서 처리 하면 어떻게 될까? OOM이 발생할 것입니다.
코루틴을 사용하면 리소스 제한에 도달하지 않고 사용할 수 있는데, 왜 그럴까요??
이유는 아래와 같습니다.
1. Dispatchers.Default와 스레드 풀(Thread Pool)
- 기본적으로 코루틴은 Dispatchers.Default를 통해 실행됩니다. (위 예제의 경우 Main)
이는 스레드 풀을 기반으로 동작하며, 가용한 CPU 코어 수에 따라 스레드 개수가 제한됩니다. - 따라서 물리적인 스레드 수는 제한되지만, 코루틴은 스레드 풀 위에서 효율적으로 실행되어 스레드 수를 초과하지 않고도 대량의 작업을 처리할 수 있습니다.
2. 코루틴은 지연 실행(Suspension)으로 효율적
- 코드에서 delay(5000L) 함수는 일시 중단(suspend) 함수입니다. 이 함수는 스레드를 차단하지 않고, 다른 코루틴이 해당 스레드를 사용할 수 있도록 반환합니다. 이를 통해 다른 코루틴이나 작업이 해당 스레드를 사용할 수 있게 되어, 효율적인 동시성 처리와 자원 활용이 가능해집니다.
- 코루틴이 delay를 호출하면 스레드를 반환하므로, 같은 스레드에서 대기 중인 다른 코루틴이 실행됩니다. 결과적으로, 여러 코루틴이 같은 스레드를 공유하며 효율적으로 실행됩니다. 이를 협력적(concurrent) 멀티태스킹이라고 합니다.
결론적으로, 위 코드에서 10만 개의 코루틴을 생성하더라도 OOM이 발생하지 않고 정상적으로 동작할 수 있습니다.
참고자료
코루틴 공식 가이드 자세히 읽기 — Part 1 — Dive 2
코루틴은 왜 스레드 보다 가볍다고 할까?
myungpyo.medium.com
'Android > Coroutine' 카테고리의 다른 글
[Android] 중단(suspend)함수란 무엇이고 어떻게 동작할까? (feat. delay()) (1) | 2025.02.05 |
---|---|
[Android] runBlocking을 왜 주의해서 써야할까? (feat. Deadlock) (0) | 2025.02.03 |
[Android] Coroutine Dispatcher에 대한 고찰 - Deep Dive (0) | 2025.01.27 |
[Android] 코루틴(Coroutine) 내부적으로 어떻게 동작할까? (0) | 2025.01.24 |
[Android] 콜백을 코루틴(Coroutine)으로 바꿔보자(SuspendCoroutine/SuspendCancellableCoroutine) (2) | 2024.11.16 |