일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- 안드로이드 갤러리 접근
- scope function
- 안드로이드 fcm
- 안드로이드 카카오 로그인
- MVP Architecture
- 안드로이드
- 영어독립365
- Java
- Android
- 66챌린지
- Android WebView
- android recyclerview
- Android ViewPager2
- 알고리즘 자바
- DataBinding
- 습관만들기
- Android Jetpack
- 카카오 알고리즘
- 프로그래머스 알고리즘
- Kotlin
- OkHttp Interceptor
- Android ProgressBar
- Android Interceptor
- Android 12
- WebView
- Android 12 대응
- Android Navigation
- Kotlin FCM
- 영어공부
- 코틀린 코루틴
- Today
- Total
나미래 Android 개발자
Android 개발자를 위한 CoroutineContext 깊이 이해하기 본문
코틀린 코루틴을 쓰면서 CoroutineContext는 반드시 마주치는 개념이다. 하지만 공식 문서를 읽거나 튜토리얼을 보면, "Context에 여러 요소를 담는다"는 식의 설명만 있어서 막연하게 느껴지기 쉽다. 나 역시 그랬다. 그래서 이번 글에서는, Android 개발자 입장에서 실제로 살아 있는 지식이 될 수 있도록, CoroutineContext를 '완전히' 풀어 설명하려고 한다.
CoroutineContext란 무엇인가?
CoroutineContext를 쉽게 말하면, 코루틴이 실행될 때 필요한 환경 정보들을 모아놓은 곳이다.
예를 들어,
- 어떤 쓰레드(Dispatcher)에서 돌릴지
- 어떤 작업 그룹(Job)에 속하는지
- 예외가 발생하면 어떻게 처리할지(ExceptionHandler)
- 디버깅을 위한 이름(Name)을 무엇으로 할지
이런 다양한 설정값들을 모아놓은 일종의 '컨테이너'다.
하지만 중요한 점은, 이 구조가 단순한 Map이나 List처럼 저장되는 것이 아니라는 것이다. 코루틴은 '가볍고 빠른 컨텍스트 전파'를 목표로 하기에, 아주 특별한 방식으로 Context를 다루고 있다.
CoroutineContext의 내부 구조: Map처럼 보이지만 LinkedList
표면적으로 보면, CoroutineContext는 Map처럼 Key로 원하는 Element를 꺼낼 수 있다.
val dispatcher = context[ContinuationInterceptor]
val job = context[Job]
이런 식으로 Key 기반으로 데이터를 꺼낼 수 있으니, 얼핏 보면 HashMap 같은 자료구조로 착각하기 쉽다.
하지만 실제 내부 구현은 LinkedList 형태다. CombinedContext라는 구조체를 통해 각 Element가 좌측(left) 방향으로 계속 이어붙는 식으로 저장된다. 각각의 Element는 Key를 가지고 있어서, 필요한 Key를 탐색할 때는 왼쪽부터 하나하나 비교해 나가면서 찾는다.
기술적으로는 O(n) 탐색이다. 하지만 대부분의 코루틴 Context는 Job, Dispatcher, Name, ExceptionHandler 정도만 가지고 있기 때문에, 보통 2~5개 정도의 Element밖에 없다. 이 정도면 성능상 큰 문제가 없고, 오히려 LinkedList의 단순하고 가벼운 구조가 이점을 가져다준다.
왜 LinkedList로 만들었을까?
코루틴의 목표는 가볍고 빠른 Context 전파다.
코루틴은 launch, async로 새 코루틴을 만들 때마다, 또는 suspend/resume 할 때마다 Context를 복사하고 넘겨야 한다. 이때, Context가 무겁고 복잡했다면 코루틴 생성이나 재개가 매우 느려졌을 것이다.
LinkedList 스타일의 얕은 복사는 다음과 같은 이점을 준다.
- Context 복사가 매우 빠르다 (참조만 복사)
- 새로운 Element 추가가 쉽다 (left에 하나 덧붙이면 끝)
- 메모리 오버헤드가 거의 없다
이 덕분에 launch, async 같은 Builder들이 매우 빠르게 작동할 수 있다.
얕은 복사 vs 깊은 복사: 메모리 구조로 이해하기
얕은 복사(Shallow Copy)란, 객체의 참조(주소)만 복사하는 것이다. Heap에 있는 객체 자체는 복사되지 않는다.
[Stack]
a -> 0x1000
b -> 0x1000
[Heap]
0x1000 -> Person(name="Alice")
a와 b는 같은 객체를 가리킨다. b.name을 변경하면 a.name도 함께 변한다.
깊은 복사(Deep Copy)는 객체 자체를 복제해서 완전히 다른 메모리 공간에 복사하는 것이다.
[Stack]
a -> 0x1000
b -> 0x2000
[Heap]
0x1000 -> Person(name="Alice")
0x2000 -> Person(name="Alice")
a와 b는 서로 다른 객체를 가리키므로, b를 수정해도 a에는 영향이 없다.
코루틴에서는 "Context 복사"를 할 때 무겁고 비싼 Deep Copy 대신, Shallow Copy를 채택했다. 이는 불변(Immutable) Element가 대부분이기 때문에 가능한 설계다.
launch/async 시 Context 전파 방식
CoroutineScope.launch나 async를 호출할 때,
- 부모 Scope의 Context를 가져오고
- 새로운 Job을 만들고
- 필요하면 추가적인 Element를 합쳐서
- 새로운 Context를 구성한다.
이때 중요한 점은,
- 부모 Context를 직접 수정하지 않고
- 얕은 복사 후 필요한 Element만 추가해서 새 Context를 만든다는 것이다.
실제 메모리 구조 흐름
ParentCoroutine
└── CoroutineContext_P
├─ Job_P
└─ Dispatcher.Main
launch() 호출
ChildCoroutine
└── CoroutineContext_C
├─ Job_C (부모 Job_P에 연결된 새로운 Job)
├─ Dispatcher.Main (부모 것 복사)
└─ CoroutineName("ChildCoroutine") (새로 추가)
ChildCoroutine은 Parent의 Context를 확장해서 자기만의 Context를 가지므로, Child가 종료되더라도 Parent는 영향을 받지 않는다.
Job 연결은 어떻게 될까?
코루틴 간 부모-자식 관계는 Context 전파만으로 끝나지 않는다. 실제로는 Job Element를 통해 명시적인 트리 구조를 형성한다.
- launch 시 새로운 Job이 생성된다.
- 이 Job은 부모 Job에 등록된다.
- 부모가 취소되면 자식들도 함께 취소된다.
이것이 바로 Structured Concurrency다.
val parentScope = CoroutineScope(Job() + Dispatchers.Main)
val childJob = parentScope.launch {
// ChildCoroutine
}
여기서 parentScope의 Job이 취소되면, childJob도 자동으로 취소된다. 이 덕분에 Android 앱의 생명주기 관리에서도 코루틴을 안전하게 사용할 수 있다.
정리: CoroutineContext를 이해해야 코루틴이 보인다
CoroutineContext를 제대로 이해하면, 코루틴의 진짜 구조와 동작 방식이 보이기 시작한다.
- Context는 단순히 Key-Value 저장소가 아니다.
- LinkedList 기반의 얕은 복사 체인이다.
- launch/async 때 Context를 가볍게 복사하고 확장한다.
- Job을 새로 만들어 부모-자식 관계를 형성한다.
코루틴은 결국 Context의 전파와 Job의 트리 관리라는 두 개의 축 위에서 동작한다.
'안드로이드 > Coroutine' 카테고리의 다른 글
코틀린 coroutineScope 사용법과 예제 - 코루틴 스코프 함수 완벽 가이드 (3) | 2025.08.07 |
---|---|
[Android Coroutine] 구조적 동시성을 완성하는 마지막 퍼즐: 코루틴 취소(Cancellation) (0) | 2025.05.03 |
[Android Coroutine] 구조적 동시성 쉽게 이해하기 – Job과 launch의 관계 (0) | 2025.05.01 |