우선 Coroutine에 대해서 알아보자.
Coroutine이란?
Co(함께, 동시에) + routine(작업들의 집합)
Coroutine은 비동적인 작업을 효율적으로 처리하기 위한 기술이다. 코루틴은 스레드와 마찬가지로 동시성을 다루지만, 스레드와는 달리 코루틴은 하나의 스레드에서 여러 개의 작업을 처리할 수 있다.

스레드?? 코루틴??
Thread (스레드)
- 운영 체제(OS) 수준의 개념이다.
- Java의 스레드는 "사용자 스레드(user thread)"이지만, 실제로는 커널 스레드(kernel thread)와 매핑되어 동작한다. (즉, Java는 그린 스레드(Green Thread)를 사용하지 않는다.)
- 운영 체제가 스레드의 전환과 스케줄링을 관리한다.
- 스레드는 선점형(preemptive)으로 동작하며, 여러 스레드는 메모리 리소스를 공유할 수 있지만, 프로세스는 공유할 수 없다.
- 공유된 리소스 사용으로 인해 동기화 문제(Thread Synchronization Problem)가 발생할 수 있다.
- Java 1.1에서는 "그린 스레드(Green Threads)"를 사용했으나 이후 폐기됨.
- 그린 스레드는 커널이 아니라, 사용자 수준에서 스케줄링을 처리하는 스레드이다.
※ 스레드는 가벼운 프로세스로도 비유된다.
Coroutine (코루틴)
- 코루틴은 스레드 위에서 실행된다.
- 코루틴은 스레드 없이 단독으로 실행되지 않는다.
- 여러 개의 코루틴을 하나의 스레드에서 실행할 수 있지만, 동시에 실행되지는 않는다.
- 중단 지점(suspension point)에서 스레드를 해방한다.
- 코루틴이 suspend되면, 실행 중이던 스레드를 해제하고 다른 코루틴이 그 스레드를 사용할 수 있도록 한다.
- 이를 통해 스레드와 메모리를 효율적으로 사용하여 많은 비동기 작업을 수행할 수 있다.
- 코루틴은 "스택리스(Stackless) 구조"를 가진다.
- 기존의 스레드는 스택을 가지고 있지만, 코루틴은 힙(Heap)에 상태를 저장하여 실행을 이어간다.
- 특정 스레드에 종속되지 않기 때문에 Context Switching(스레드 전환 비용)이 발생하지 않는다.
- 수천 개의 스레드보다 코루틴이 훨씬 가볍다.
- 스레드는 생성 비용이 크고, 개수가 많아질수록 부담이 커진다.
- 반면, 코루틴은 적은 리소스로 실행할 수 있어 수천 개의 코루틴을 효율적으로 관리할 수 있다.
※ 일부 사람들은 코루틴을 가벼운 스레드(lightweight thread)로 비유하기도 한다.
※ 하나의 스레드에서 수천 개의 코루틴이 실행될 수 있다.

Kotlin Suspend 키워드의 내부 동작 원리
Kotlin의 suspend 키워드는 코루틴에서 중단(suspend)과 재개(resume)를 가능하게 하는 핵심 요소이다.
하지만 단순히 함수 앞에 suspend를 붙인다고 마법처럼 동작하는 것은 아니다. Kotlin 컴파일러는 suspend 키워드를 만나면 내부적으로 Continuation Passing Style (CPS) 패러다임을 적용하여 코드 변환을 수행한다.
아래 예시 코드는 여기서 가져왔습니다.
https://tech.devsisters.com/posts/crunchy-concurrency-kotlin/
바삭한 신입들의 동시성 이야기 - Kotlin 편
마이쿠키런 신입 안드로이드 개발자의 Kotlin Coroutine 탐방기
tech.devsisters.com
Step 1: Continuation이란? - 콜백을 확장한 개념
Kotlin 코루틴은 Continuation Passing Style (CPS)이라는 패러다임을 따른다
이 패러다임은 함수가 실행을 완료하면, 결과를 Continuation 객체를 통해 전달하는 방식이다.
즉, 함수가 직접 값을 반환하는 대신, 다음에 실행할 작업(콜백 역할)을 넘겨주는 방식
public interface Continuation<in T> {
val context: CoroutineContext
fun resumeWith(result: Result<T>)
}
Continuation의 역할
- resumeWith(result: Result<T>)
- suspend 함수가 재개(resume)될 때 결과 값을 전달하는 함수입니다.
- suspend 함수는 값을 직접 반환하는 것이 아니라, resumeWith을 통해 값을 넘깁니다.
- context: CoroutineContext
- Continuation이 실행될 환경(스레드, 디스패처 등)을 정의합니다.
- 이를 통해 특정 스레드에서 실행되도록 제어할 수 있습니다.
이해하기 쉽게 말하면?
- Continuation은 현재 실행 상태를 저장하는 객체입니다.
- resumeWith(result)을 호출하면, 이전 상태를 복원하고 다시 실행을 시작합니다.
- 코루틴에서 suspend가 호출될 때마다 Continuation을 전달하고, 중단된 위치를 기억했다가 다시 실행할 수 있도록 합니다.
Step 2: suspend fun의 변환 과정
Kotlin 컴파일러는 suspend 함수를 자동으로 변환한다.
변환 전 코드 (우리가 작성한 코드)
suspend fun getGingerBrave(api: CookieService): gingerBrave { // (1)
val dough = api.makeDough()
val magicDough = api.addMagicPowder(dough)
val cookie = api.escapeOven(magicDough)
val gingerBrave = api.fetchGingerBrave(cookie)
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
return gingerBrave //(2)(3)
}
변환 후 코드 (컴파일러가 변환한 코드)
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) { // (1)
val dough = api.makeDough()
val magicDough = api.addMagicPowder(dough)
val cookie = api.escapeOven(magicDough)
val gingerBrave = api.fetchGingerBrave(cookie)
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(gingerBrave) //(2)(3)
}
- 함수 시그니처 변화
- suspend fun에서 suspend 키워드가 사라지고, 마지막 매개변수로 Continuation이 추가된다.
- 결과 반환 방식 변경
- return을 사용하지 않고, completion.resume(value)를 호출하여 결과를 전달한다.
Step 3: Suspend & Resume - 상태 머신 (State Machine)
getGingerBrave 함수에는 여러 개의 suspend 함수가 포함되어 있다.
따라서, 어디까지 실행되었는지 기억하고 재개하는 기능이 필요한데, 이때 컴파일러는 상태 머신(state machine) 을 만들어 suspend 지점마다 실행 상태를 추적한다.
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
val dough = api.makeDough() // suspending fun
val magicDough = api.addMagicPowder(dough) // suspending fun
val cookie = api.escapeOven(magicDough) // suspending fun
val gingerBrave = api.fetchGingerBrave(cookie) // suspending fun
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(gingerBrave)
}
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
when (label) {
0 -> // Label 0 -> first execution !
val dough = api.makeDough() // suspend
1 -> // Label 1 -> resume from makeDough
val magicDough = api.addMagicPowder(dough) // suspend
2 -> // Label 2 -> resume from addMagicPowder
val cookie = api.escapeOven(magicDough) // suspend
3 -> // Label 3 -> resume from escapeOven
val gingerBrave = api.fetchGingerBrave(cookie) // suspend
4 -> { // Label 4 -> resume from fetchGingerBrave
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(gingerBrave)
}
}
}
Step 4: 내부 변수와 상태 관리
getGingerBrave 함수가 여러 번 suspend & resume되면서 내부 변수들을 유지해야 한다.
따라서, 컴파일러는 변수들을 저장할 별도의 클래스를 생성한다.
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
class GetGingerBraveStateMachine(completionL Continuation<Any?>): CoroutineImpl(completion) {
// 기존 함수 내부에 선언된 변수들
var dough: Dough? = null
var magicDough: MagicDough? = null
var cookie: Cookie? = null
var gingerBrave: GingerBrave? = null
var result: Any? = null
var label: Int = 0
override fun invokeSuspend(result: Any?) {
this.result = result
getGingerBrave(api, this)
}
}
}
- Kotlin 컴파일러는 getGingerBrave 함수안에 GetGingerBraveStateMachine이라는 클래스를 생성
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
val continuation = completion as? GetGingerBraveStateMachine ?: GetGingerBraveStateMachine
(completion)
when (continuation.label) {
0 -> {// Label 0 -> first execution !
throwOnFailure(continuation.result)
continuation.label = 1
api.makeDough(continuation) // suspend
}
1 -> {// Label 1 -> resume from makeDough
throwOnFailure(continuation.result)
continuation.dough = continuation.result as Dough
continuation.label = 2
api.addMagicPowder(continuation.dough, continuation) // suspend
}
2 -> {// Label 2 -> resume from addMagicPowder
throwOnFailure(continuation.result)
continuation.magicDough = continuation.result as MagicDough
continuation.label = 3
api.escapeOven(continuation.magicDough, continuation) // suspend
}
3 -> { // Label 3 -> resume from escapeOven
throwOnFailure(continuation.result)
continuation.cookie = continuation.result as Cookie
continuation.label = 4
api.fetchGingerBrave(continuation.cookie, continuation) // suspend
}
4 -> { // Label 4 -> resume from fetchGingerBrave
throwOnFailure(continuation.result)
continuation.gingerBrave = continuation.result as GingerBrave
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(continuation.gingerBrave)
}
else -> throw IllegalStateException
}
}
질문 7개 생각하고 답하기
1. 안드로이드 개발에 있어서 Main Thread는 왜 중요한가요?
메인 스레드는 안드로이드에서 가장 중요한 스레드로, UI를 그려주고 사용자가 화면을 눌렀을 때 이벤트를 전달받는 스레드입니다. 즉, 사용자와의 인터렉션을 담당하는 스레드입니다. 만약 이 Thread가 높은 부하를 받는 작업에 의해 블로킹된다면 안드로이드 앱은 멈춤 현상(ANR)이 생기고, 일정 시간 이상 블로킹될 때 앱은 강제로 종료됩니다. 따라서 메인 스레드에서 많은 부하를 받는 작업은 지양해야 하며, 다른 스레드를 생성해 해당 스레드에 높은 부하를 주는 작업을 수행하도록 만들어야 합니다.
2. 그렇다면 왜 코루틴을 사용하나요?
기존 방식들(Thread 상속, Excute프레임워크 사용, RXJava 사용)의 한계점은 작업의 단위가 스레드라는 점입니다. 스레드는 생성 비용이 비싸고 작업을 전환하는 비용도 비쌉니다. 또한 한 스레드가 다른 스레드부터의 작업을 기다려야 하면, 기본적으로 다른 스레드의 작업을 기다리는 스레드는 다른 작업이 스레드를 사용할 수 없도록 Blocking 되어버립니다. 이렇게 되면 해당 스레드는 하는 작업 없이 다른 작업이 끝마쳐질 때까지 기다려야 하기 때문에 자원은 낭비됩니다. 이것이 위의 작업 단위가 스레드일 경우 생기는 고질적인 문제점입니다. 하지만, 코루틴은 프로세스 안에 스레드가 있듯이, 코루틴이라고 하는 작업단위가 스레드 안에 있습니다. 즉, 스레드 안에서 더 잘게 나눠지는 작업 단위입니다. 코루틴은 어떤 특정 쓰레드에 종속적이지 않고, 스레드가 필요 없을 때 스레드의 사용권한을 양보해가면서(이리저리 옮겨가면서) 스레드를 블로킹하는 상황을 줄여가고 각 스레드를 최대한 활용할 수 있습니다. 즉, 그때그때마다 상황에 맞는 적절한 스레드에서 실행이 되기때문에, 스레드가 blocking 되어 낭비되는 일이 없고, 루틴을 교환해가며 실행하기 때문에 기존 방식보다 훨씬 빠르고 가볍습니다.
3. 코루틴과 스레드의 차이점은 무엇인가요?
코루틴은 가벼운 비동기 작업을 위한 개념으로, 하나의 스레드에서 여러 개가 실행될 수 있으며 비용이 적습니다. 반면, 스레드는 OS에서 직접 관리하는 독립적인 실행 단위로, 생성과 컨텍스트 스위칭 비용이 큽니다.
4. suspend에 대해서 아는데로 전부다 설명해보세요.
suspend는 Kotlin 코루틴에서 함수 실행을 일시 중단(suspend)하고 나중에 재개(resume)할 수 있도록 하는 키워드입니다. 이를 통해 비동기 작업을 효율적으로 처리할 수 있으며, suspend 함수는 다른 suspend 함수나 코루틴 내에서만 호출될 수 있습니다. 컴파일러는 suspend 함수를 Continuation Passing Style(CPS) 방식으로 변환하여, 실행 상태(변수, 위치)를 힙에 저장하고 필요할 때 복원합니다. 이로 인해 스택을 차지하지 않으며(stackless), 특정 스레드에 종속되지 않아 컨텍스트 스위칭 비용이 줄어듭니다. 또한, suspend 함수는 상태 머신(state machine) 방식으로 변환되어, suspend 지점에서 중단되고 resume 시점에 이어서 실행됩니다. 이 덕분에 네트워크 요청, 파일 I/O 등 시간이 오래 걸리는 작업을 효율적으로 비동기 처리할 수 있습니다.
5. suspend function 내부에서 일반 fuction들은 어떻게 동작하는지 설명해보세요.
suspend 함수 내부에서 호출되는 일반 함수들은 일반적인 함수 호출 방식과 동일하게 동작하며, 호출 스택에서 실행됩니다. 즉, suspend 함수 내의 일반 함수는 중단(suspend)되지 않고 즉시 실행되며, 일반적인 연산을 수행하는 역할 을 합니다. 하지만, 일반 함수가 suspend 함수를 호출하면, 그 지점에서 실행이 중단될 수 있으며, 이후 재개될 때 다시 호출 스택이 쌓여 실행됩니다. 따라서, suspend 함수 내부의 일반 함수는 중단되지 않으며, suspend 함수의 흐름을 따라 실행된다고 볼 수 있습니다.
6. 코루틴이 호출될 때 스택을 어떻게 관리하나요?
코루틴은 스택리스(Stackless) 구조로 동작하며, 일반적인 함수 호출처럼 호출 스택(Call Stack)에 쌓이지 않고 힙(Heap)에 실행 상태를 저장합니다. 코루틴이 호출되면, 현재 실행 중인 스레드에서 실행되지만, suspend 지점에서 스택을 해제하고 실행 상태를 저장한 후 중단(suspend)됩니다. 이후 resume 시점에 저장된 상태를 복원하여 실행을 이어갑니다. 이를 통해 코루틴은 스레드의 컨텍스트 스위칭 비용을 줄이고, 적은 리소스로 많은 동시성 작업을 수행할 수 있도록 최적화됩니다.
7. 코루틴이 suspend(중단)될 때, 해당 상태를 어떻게 저장하고 복원하나요?
코루틴이 suspend 될 때, 현재 실행 위치(프로그램 카운터), 로컬 변수, 호출 스택 등의 상태를 Continuation 객체에 저장합니다. 컴파일러는 suspend 함수를 상태 머신(state machine)으로 변환하여, 중단 지점을 기준으로 실행 단계를 나누고 label을 사용해 관리합니다. 이후 resume이 호출되면, 저장된 상태를 복원하고 중단된 지점(label)부터 실행을 재개합니다. 이를 통해코루틴은 스택을 유지하지 않으면서도 가벼운 비동기 처리를 효율적으로 수행할 수 있습니다.
코루틴 연습하기
https://github.com/wisemuji/coroutines-roomescape?tab=readme-ov-file
GitHub - wisemuji/coroutines-roomescape: DevFest Android in Korea 2024 코루틴 방탈출 🔑
DevFest Android in Korea 2024 코루틴 방탈출 🔑. Contribute to wisemuji/coroutines-roomescape development by creating an account on GitHub.
github.com
참고
https://gist.github.com/chaxiu/d87870528bbfe3e9d7e481e1f6acace1
blog.md
GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
코루틴이 경량 스레드인 이유 (Light-weight thread) | 찰스의 안드로이드
Why Coroutine is light-weight thread 코틀린 공식 문서에서는 코루틴을 경량 스레드(light-weight thread)라고 표현하고 있는데, 그 이유를 알아보자. 코루틴은 스레드에서 실행된다. 이것은 대원칙이다. 코루
charlezz.com
[Coroutine] 1. 코루틴은 어떻게 스레드 작업을 최적화 하는가?
Thread 구조와 다중 Thread 작업의 필요성 하나의 프로세스(Process) 에는 여러 스레드(Thread) 가 있고, 각 스레드는 독립적으로 작업을 수행할 수 있다. 예를 들어 JVM 프로세스 상에서는 스레드는 그림1
kotlinworld.com
https://todaycode.tistory.com/179
코루틴은 왜 빠른 걸까요?
1. 요약 2. Thread 2-1. 스레드를 사용하는 이유 2-2. 스레드의 문제점 3. Coroutine 3-1. 코루틴의 동작 방식 3-2. 코루틴과 스레드의 비교 1. 요약 🧑💻: 코루틴이 빠르다, 가볍다라고들 하는데 그 이유
todaycode.tistory.com
https://todaycode.tistory.com/181
suspend 함수란 무엇인가요?
🚀 글 읽는 순서 🚀 코루틴은 왜 빠른 걸까요? -> suspend 함수란 무엇인가요? (현재) 1. 요약 2. await 2-1. 개념 2-2. 코루틴 내부에서 실행되는 await 2-3. suspend 내부에서 실행되는 await 3. Suspend 3-1. 개념
todaycode.tistory.com
Stackful 과 Stackless 코루틴의 차이 | 찰스의 안드로이드
Stackful Coroutines vs Stackless Coroutines Stackful Coroutines 먼저 스택풀 코루틴(Stackful Coroutine)은 함수 호출시에 사용되는 분리된 스택을 가지고 있다. 코루틴이 어떻게 동작하는지 정확하게 이해하기 위해
charlezz.com
'Android > Study' 카테고리의 다른 글
[Android] Gson vs Moshi vs kotlinx.serialization Deep Dive (0) | 2025.03.26 |
---|---|
[Android] Retrofit의 내부구조와 동작 알아보기 (0) | 2025.03.26 |
[Android] ViewModel이란 무엇이고 그리고 LifeCycle 까지 (0) | 2025.03.05 |
[Android] 인텐트(Intent) 및 인텐트 필터(Intent Filter) (0) | 2025.02.20 |
[Android] Jetpack Compose의 Recomposition 정리 (0) | 2025.02.20 |
우선 Coroutine에 대해서 알아보자.
Coroutine이란?
Co(함께, 동시에) + routine(작업들의 집합)
Coroutine은 비동적인 작업을 효율적으로 처리하기 위한 기술이다. 코루틴은 스레드와 마찬가지로 동시성을 다루지만, 스레드와는 달리 코루틴은 하나의 스레드에서 여러 개의 작업을 처리할 수 있다.

스레드?? 코루틴??
Thread (스레드)
- 운영 체제(OS) 수준의 개념이다.
- Java의 스레드는 "사용자 스레드(user thread)"이지만, 실제로는 커널 스레드(kernel thread)와 매핑되어 동작한다. (즉, Java는 그린 스레드(Green Thread)를 사용하지 않는다.)
- 운영 체제가 스레드의 전환과 스케줄링을 관리한다.
- 스레드는 선점형(preemptive)으로 동작하며, 여러 스레드는 메모리 리소스를 공유할 수 있지만, 프로세스는 공유할 수 없다.
- 공유된 리소스 사용으로 인해 동기화 문제(Thread Synchronization Problem)가 발생할 수 있다.
- Java 1.1에서는 "그린 스레드(Green Threads)"를 사용했으나 이후 폐기됨.
- 그린 스레드는 커널이 아니라, 사용자 수준에서 스케줄링을 처리하는 스레드이다.
※ 스레드는 가벼운 프로세스로도 비유된다.
Coroutine (코루틴)
- 코루틴은 스레드 위에서 실행된다.
- 코루틴은 스레드 없이 단독으로 실행되지 않는다.
- 여러 개의 코루틴을 하나의 스레드에서 실행할 수 있지만, 동시에 실행되지는 않는다.
- 중단 지점(suspension point)에서 스레드를 해방한다.
- 코루틴이 suspend되면, 실행 중이던 스레드를 해제하고 다른 코루틴이 그 스레드를 사용할 수 있도록 한다.
- 이를 통해 스레드와 메모리를 효율적으로 사용하여 많은 비동기 작업을 수행할 수 있다.
- 코루틴은 "스택리스(Stackless) 구조"를 가진다.
- 기존의 스레드는 스택을 가지고 있지만, 코루틴은 힙(Heap)에 상태를 저장하여 실행을 이어간다.
- 특정 스레드에 종속되지 않기 때문에 Context Switching(스레드 전환 비용)이 발생하지 않는다.
- 수천 개의 스레드보다 코루틴이 훨씬 가볍다.
- 스레드는 생성 비용이 크고, 개수가 많아질수록 부담이 커진다.
- 반면, 코루틴은 적은 리소스로 실행할 수 있어 수천 개의 코루틴을 효율적으로 관리할 수 있다.
※ 일부 사람들은 코루틴을 가벼운 스레드(lightweight thread)로 비유하기도 한다.
※ 하나의 스레드에서 수천 개의 코루틴이 실행될 수 있다.

Kotlin Suspend 키워드의 내부 동작 원리
Kotlin의 suspend 키워드는 코루틴에서 중단(suspend)과 재개(resume)를 가능하게 하는 핵심 요소이다.
하지만 단순히 함수 앞에 suspend를 붙인다고 마법처럼 동작하는 것은 아니다. Kotlin 컴파일러는 suspend 키워드를 만나면 내부적으로 Continuation Passing Style (CPS) 패러다임을 적용하여 코드 변환을 수행한다.
아래 예시 코드는 여기서 가져왔습니다.
https://tech.devsisters.com/posts/crunchy-concurrency-kotlin/
바삭한 신입들의 동시성 이야기 - Kotlin 편
마이쿠키런 신입 안드로이드 개발자의 Kotlin Coroutine 탐방기
tech.devsisters.com
Step 1: Continuation이란? - 콜백을 확장한 개념
Kotlin 코루틴은 Continuation Passing Style (CPS)이라는 패러다임을 따른다
이 패러다임은 함수가 실행을 완료하면, 결과를 Continuation 객체를 통해 전달하는 방식이다.
즉, 함수가 직접 값을 반환하는 대신, 다음에 실행할 작업(콜백 역할)을 넘겨주는 방식
public interface Continuation<in T> {
val context: CoroutineContext
fun resumeWith(result: Result<T>)
}
Continuation의 역할
- resumeWith(result: Result<T>)
- suspend 함수가 재개(resume)될 때 결과 값을 전달하는 함수입니다.
- suspend 함수는 값을 직접 반환하는 것이 아니라, resumeWith을 통해 값을 넘깁니다.
- context: CoroutineContext
- Continuation이 실행될 환경(스레드, 디스패처 등)을 정의합니다.
- 이를 통해 특정 스레드에서 실행되도록 제어할 수 있습니다.
이해하기 쉽게 말하면?
- Continuation은 현재 실행 상태를 저장하는 객체입니다.
- resumeWith(result)을 호출하면, 이전 상태를 복원하고 다시 실행을 시작합니다.
- 코루틴에서 suspend가 호출될 때마다 Continuation을 전달하고, 중단된 위치를 기억했다가 다시 실행할 수 있도록 합니다.
Step 2: suspend fun의 변환 과정
Kotlin 컴파일러는 suspend 함수를 자동으로 변환한다.
변환 전 코드 (우리가 작성한 코드)
suspend fun getGingerBrave(api: CookieService): gingerBrave { // (1)
val dough = api.makeDough()
val magicDough = api.addMagicPowder(dough)
val cookie = api.escapeOven(magicDough)
val gingerBrave = api.fetchGingerBrave(cookie)
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
return gingerBrave //(2)(3)
}
변환 후 코드 (컴파일러가 변환한 코드)
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) { // (1)
val dough = api.makeDough()
val magicDough = api.addMagicPowder(dough)
val cookie = api.escapeOven(magicDough)
val gingerBrave = api.fetchGingerBrave(cookie)
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(gingerBrave) //(2)(3)
}
- 함수 시그니처 변화
- suspend fun에서 suspend 키워드가 사라지고, 마지막 매개변수로 Continuation이 추가된다.
- 결과 반환 방식 변경
- return을 사용하지 않고, completion.resume(value)를 호출하여 결과를 전달한다.
Step 3: Suspend & Resume - 상태 머신 (State Machine)
getGingerBrave 함수에는 여러 개의 suspend 함수가 포함되어 있다.
따라서, 어디까지 실행되었는지 기억하고 재개하는 기능이 필요한데, 이때 컴파일러는 상태 머신(state machine) 을 만들어 suspend 지점마다 실행 상태를 추적한다.
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
val dough = api.makeDough() // suspending fun
val magicDough = api.addMagicPowder(dough) // suspending fun
val cookie = api.escapeOven(magicDough) // suspending fun
val gingerBrave = api.fetchGingerBrave(cookie) // suspending fun
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(gingerBrave)
}
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
when (label) {
0 -> // Label 0 -> first execution !
val dough = api.makeDough() // suspend
1 -> // Label 1 -> resume from makeDough
val magicDough = api.addMagicPowder(dough) // suspend
2 -> // Label 2 -> resume from addMagicPowder
val cookie = api.escapeOven(magicDough) // suspend
3 -> // Label 3 -> resume from escapeOven
val gingerBrave = api.fetchGingerBrave(cookie) // suspend
4 -> { // Label 4 -> resume from fetchGingerBrave
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(gingerBrave)
}
}
}
Step 4: 내부 변수와 상태 관리
getGingerBrave 함수가 여러 번 suspend & resume되면서 내부 변수들을 유지해야 한다.
따라서, 컴파일러는 변수들을 저장할 별도의 클래스를 생성한다.
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
class GetGingerBraveStateMachine(completionL Continuation<Any?>): CoroutineImpl(completion) {
// 기존 함수 내부에 선언된 변수들
var dough: Dough? = null
var magicDough: MagicDough? = null
var cookie: Cookie? = null
var gingerBrave: GingerBrave? = null
var result: Any? = null
var label: Int = 0
override fun invokeSuspend(result: Any?) {
this.result = result
getGingerBrave(api, this)
}
}
}
- Kotlin 컴파일러는 getGingerBrave 함수안에 GetGingerBraveStateMachine이라는 클래스를 생성
fun getGingerBrave(api: CookieService, completion: Continuation<Any?>) {
val continuation = completion as? GetGingerBraveStateMachine ?: GetGingerBraveStateMachine
(completion)
when (continuation.label) {
0 -> {// Label 0 -> first execution !
throwOnFailure(continuation.result)
continuation.label = 1
api.makeDough(continuation) // suspend
}
1 -> {// Label 1 -> resume from makeDough
throwOnFailure(continuation.result)
continuation.dough = continuation.result as Dough
continuation.label = 2
api.addMagicPowder(continuation.dough, continuation) // suspend
}
2 -> {// Label 2 -> resume from addMagicPowder
throwOnFailure(continuation.result)
continuation.magicDough = continuation.result as MagicDough
continuation.label = 3
api.escapeOven(continuation.magicDough, continuation) // suspend
}
3 -> { // Label 3 -> resume from escapeOven
throwOnFailure(continuation.result)
continuation.cookie = continuation.result as Cookie
continuation.label = 4
api.fetchGingerBrave(continuation.cookie, continuation) // suspend
}
4 -> { // Label 4 -> resume from fetchGingerBrave
throwOnFailure(continuation.result)
continuation.gingerBrave = continuation.result as GingerBrave
Log.d("You can't catch me! I'm the Gingerbre.. I'm Ginger Brave!")
completion.resume(continuation.gingerBrave)
}
else -> throw IllegalStateException
}
}
질문 7개 생각하고 답하기
1. 안드로이드 개발에 있어서 Main Thread는 왜 중요한가요?
메인 스레드는 안드로이드에서 가장 중요한 스레드로, UI를 그려주고 사용자가 화면을 눌렀을 때 이벤트를 전달받는 스레드입니다. 즉, 사용자와의 인터렉션을 담당하는 스레드입니다. 만약 이 Thread가 높은 부하를 받는 작업에 의해 블로킹된다면 안드로이드 앱은 멈춤 현상(ANR)이 생기고, 일정 시간 이상 블로킹될 때 앱은 강제로 종료됩니다. 따라서 메인 스레드에서 많은 부하를 받는 작업은 지양해야 하며, 다른 스레드를 생성해 해당 스레드에 높은 부하를 주는 작업을 수행하도록 만들어야 합니다.
2. 그렇다면 왜 코루틴을 사용하나요?
기존 방식들(Thread 상속, Excute프레임워크 사용, RXJava 사용)의 한계점은 작업의 단위가 스레드라는 점입니다. 스레드는 생성 비용이 비싸고 작업을 전환하는 비용도 비쌉니다. 또한 한 스레드가 다른 스레드부터의 작업을 기다려야 하면, 기본적으로 다른 스레드의 작업을 기다리는 스레드는 다른 작업이 스레드를 사용할 수 없도록 Blocking 되어버립니다. 이렇게 되면 해당 스레드는 하는 작업 없이 다른 작업이 끝마쳐질 때까지 기다려야 하기 때문에 자원은 낭비됩니다. 이것이 위의 작업 단위가 스레드일 경우 생기는 고질적인 문제점입니다. 하지만, 코루틴은 프로세스 안에 스레드가 있듯이, 코루틴이라고 하는 작업단위가 스레드 안에 있습니다. 즉, 스레드 안에서 더 잘게 나눠지는 작업 단위입니다. 코루틴은 어떤 특정 쓰레드에 종속적이지 않고, 스레드가 필요 없을 때 스레드의 사용권한을 양보해가면서(이리저리 옮겨가면서) 스레드를 블로킹하는 상황을 줄여가고 각 스레드를 최대한 활용할 수 있습니다. 즉, 그때그때마다 상황에 맞는 적절한 스레드에서 실행이 되기때문에, 스레드가 blocking 되어 낭비되는 일이 없고, 루틴을 교환해가며 실행하기 때문에 기존 방식보다 훨씬 빠르고 가볍습니다.
3. 코루틴과 스레드의 차이점은 무엇인가요?
코루틴은 가벼운 비동기 작업을 위한 개념으로, 하나의 스레드에서 여러 개가 실행될 수 있으며 비용이 적습니다. 반면, 스레드는 OS에서 직접 관리하는 독립적인 실행 단위로, 생성과 컨텍스트 스위칭 비용이 큽니다.
4. suspend에 대해서 아는데로 전부다 설명해보세요.
suspend는 Kotlin 코루틴에서 함수 실행을 일시 중단(suspend)하고 나중에 재개(resume)할 수 있도록 하는 키워드입니다. 이를 통해 비동기 작업을 효율적으로 처리할 수 있으며, suspend 함수는 다른 suspend 함수나 코루틴 내에서만 호출될 수 있습니다. 컴파일러는 suspend 함수를 Continuation Passing Style(CPS) 방식으로 변환하여, 실행 상태(변수, 위치)를 힙에 저장하고 필요할 때 복원합니다. 이로 인해 스택을 차지하지 않으며(stackless), 특정 스레드에 종속되지 않아 컨텍스트 스위칭 비용이 줄어듭니다. 또한, suspend 함수는 상태 머신(state machine) 방식으로 변환되어, suspend 지점에서 중단되고 resume 시점에 이어서 실행됩니다. 이 덕분에 네트워크 요청, 파일 I/O 등 시간이 오래 걸리는 작업을 효율적으로 비동기 처리할 수 있습니다.
5. suspend function 내부에서 일반 fuction들은 어떻게 동작하는지 설명해보세요.
suspend 함수 내부에서 호출되는 일반 함수들은 일반적인 함수 호출 방식과 동일하게 동작하며, 호출 스택에서 실행됩니다. 즉, suspend 함수 내의 일반 함수는 중단(suspend)되지 않고 즉시 실행되며, 일반적인 연산을 수행하는 역할 을 합니다. 하지만, 일반 함수가 suspend 함수를 호출하면, 그 지점에서 실행이 중단될 수 있으며, 이후 재개될 때 다시 호출 스택이 쌓여 실행됩니다. 따라서, suspend 함수 내부의 일반 함수는 중단되지 않으며, suspend 함수의 흐름을 따라 실행된다고 볼 수 있습니다.
6. 코루틴이 호출될 때 스택을 어떻게 관리하나요?
코루틴은 스택리스(Stackless) 구조로 동작하며, 일반적인 함수 호출처럼 호출 스택(Call Stack)에 쌓이지 않고 힙(Heap)에 실행 상태를 저장합니다. 코루틴이 호출되면, 현재 실행 중인 스레드에서 실행되지만, suspend 지점에서 스택을 해제하고 실행 상태를 저장한 후 중단(suspend)됩니다. 이후 resume 시점에 저장된 상태를 복원하여 실행을 이어갑니다. 이를 통해 코루틴은 스레드의 컨텍스트 스위칭 비용을 줄이고, 적은 리소스로 많은 동시성 작업을 수행할 수 있도록 최적화됩니다.
7. 코루틴이 suspend(중단)될 때, 해당 상태를 어떻게 저장하고 복원하나요?
코루틴이 suspend 될 때, 현재 실행 위치(프로그램 카운터), 로컬 변수, 호출 스택 등의 상태를 Continuation 객체에 저장합니다. 컴파일러는 suspend 함수를 상태 머신(state machine)으로 변환하여, 중단 지점을 기준으로 실행 단계를 나누고 label을 사용해 관리합니다. 이후 resume이 호출되면, 저장된 상태를 복원하고 중단된 지점(label)부터 실행을 재개합니다. 이를 통해코루틴은 스택을 유지하지 않으면서도 가벼운 비동기 처리를 효율적으로 수행할 수 있습니다.
코루틴 연습하기
https://github.com/wisemuji/coroutines-roomescape?tab=readme-ov-file
GitHub - wisemuji/coroutines-roomescape: DevFest Android in Korea 2024 코루틴 방탈출 🔑
DevFest Android in Korea 2024 코루틴 방탈출 🔑. Contribute to wisemuji/coroutines-roomescape development by creating an account on GitHub.
github.com
참고
https://gist.github.com/chaxiu/d87870528bbfe3e9d7e481e1f6acace1
blog.md
GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
코루틴이 경량 스레드인 이유 (Light-weight thread) | 찰스의 안드로이드
Why Coroutine is light-weight thread 코틀린 공식 문서에서는 코루틴을 경량 스레드(light-weight thread)라고 표현하고 있는데, 그 이유를 알아보자. 코루틴은 스레드에서 실행된다. 이것은 대원칙이다. 코루
charlezz.com
[Coroutine] 1. 코루틴은 어떻게 스레드 작업을 최적화 하는가?
Thread 구조와 다중 Thread 작업의 필요성 하나의 프로세스(Process) 에는 여러 스레드(Thread) 가 있고, 각 스레드는 독립적으로 작업을 수행할 수 있다. 예를 들어 JVM 프로세스 상에서는 스레드는 그림1
kotlinworld.com
https://todaycode.tistory.com/179
코루틴은 왜 빠른 걸까요?
1. 요약 2. Thread 2-1. 스레드를 사용하는 이유 2-2. 스레드의 문제점 3. Coroutine 3-1. 코루틴의 동작 방식 3-2. 코루틴과 스레드의 비교 1. 요약 🧑💻: 코루틴이 빠르다, 가볍다라고들 하는데 그 이유
todaycode.tistory.com
https://todaycode.tistory.com/181
suspend 함수란 무엇인가요?
🚀 글 읽는 순서 🚀 코루틴은 왜 빠른 걸까요? -> suspend 함수란 무엇인가요? (현재) 1. 요약 2. await 2-1. 개념 2-2. 코루틴 내부에서 실행되는 await 2-3. suspend 내부에서 실행되는 await 3. Suspend 3-1. 개념
todaycode.tistory.com
Stackful 과 Stackless 코루틴의 차이 | 찰스의 안드로이드
Stackful Coroutines vs Stackless Coroutines Stackful Coroutines 먼저 스택풀 코루틴(Stackful Coroutine)은 함수 호출시에 사용되는 분리된 스택을 가지고 있다. 코루틴이 어떻게 동작하는지 정확하게 이해하기 위해
charlezz.com
'Android > Study' 카테고리의 다른 글
[Android] Gson vs Moshi vs kotlinx.serialization Deep Dive (0) | 2025.03.26 |
---|---|
[Android] Retrofit의 내부구조와 동작 알아보기 (0) | 2025.03.26 |
[Android] ViewModel이란 무엇이고 그리고 LifeCycle 까지 (0) | 2025.03.05 |
[Android] 인텐트(Intent) 및 인텐트 필터(Intent Filter) (0) | 2025.02.20 |
[Android] Jetpack Compose의 Recomposition 정리 (0) | 2025.02.20 |