구조화된 동시성은 코루틴에서 중요한 개념으로, 코루틴이 부모-자식 관계를 통해 체계적으로 관리되는 방식을 의미합니다.
이를 통해 코루틴의 실행 흐름을 명확하게 제어하고, 예측 가능한 방식으로 동시 실행을 관리할 수 있습니다.
또한, 특정 범위(Scope) 내에서 코루틴을 실행함으로써 오류 처리를 용이하게 하고, 불필요한 리소스 낭비나 메모리 누수를 방지할 수 있도록 돕습니다.
1. 구조화된 동시성의 핵심 개념
1) 코루틴 스코프 (Coroutine Scope)
- 모든 코루틴은 반드시 코루틴 스코프 안에서 실행됩니다.
- 코루틴 스코프를 사용하면 코루틴이 언제 시작되고 종료되는지를 명확히 제어할 수 있습니다.
- 대표적인 코루틴 스코프는 다음과 같습니다.
- CoroutineScope: 개발자가 직접 생성하여 관리 가능
- GlobalScope: 앱이 종료될 때까지 유지되므로 사용에 주의해야 함
- viewModelScope: Android ViewModel에서 제공하는 코루틴 스코프 (자동 관리됨)
- lifecycleScope: Android LifecycleOwner (Activity, Fragment 등)에서 제공하는 스코프
2) 부모-자식 관계
- 부모 코루틴이 취소되면 모든 자식 코루틴도 함께 취소됩니다.
- 자식 코루틴이 예외를 발생시키면, 부모 코루틴에도 영향을 미칠 수 있습니다.
3) 코루틴 빌더 (Coroutine Builders)
- launch: 부모 코루틴과 독립적인 작업 수행 (결과 반환 없음)
- async: 값을 반환하는 코루틴 실행 (await로 결과 반환)
- withContext: 기존 스레드를 차단(blocking)하지 않고 다른 디스패처로 전환
2. 구조화된 동시성을 활용한 예제
launch 사용 예시 (부모-자식 관계)
fun main() = runBlocking {
val parentJob = launch {
launch {
delay(1000L)
println("자식 코루틴 1 실행 완료")
}
launch {
delay(2000L)
println("자식 코루틴 2 실행 완료")
}
}
delay(500L)
parentJob.cancel() // 부모 코루틴 취소 → 모든 자식 코루틴도 함께 취소됨
println("부모 코루틴 취소됨")
}
실행 결과
부모 코루틴 취소됨
- parentJob.cancel()이 호출되면서 자식 코루틴들도 취소됩니다.
- 따라서 "자식 코루틴 1 실행 완료"와 "자식 코루틴 2 실행 완료"는 출력되지 않습니다.
async 사용 예시 (결과 반환)
suspend fun fetchData(): String {
delay(1000L)
return "데이터 로드 완료"
}
fun main() = runBlocking {
val result = async { fetchData() }
println("결과: ${result.await()}") // await()으로 값 반환
}
실행 결과
결과: 데이터 로드 완료
withContext 사용 예시 (스레드 전환)
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
delay(1000L)
"데이터 로드 완료"
}
fun main() = runBlocking {
val result = fetchData()
println("결과: $result")
}
- withContext(Dispatchers.IO)를 사용하면, 네트워크 또는 파일 I/O 같은 작업을 백그라운드 스레드에서 실행할 수 있습니다.
- launch나 async와 다르게, withContext는 현재 스코프에서 실행되는 코루틴을 차단하지 않습니다.
3. 구조화된 동시성의 장점
안정적인 코루틴 관리
- 부모 코루틴이 종료되면 자식 코루틴도 함께 정리되므로 메모리 누수를 방지할 수 있습니다.
예외 처리 용이
- 예외가 발생하면 부모가 감지하여 쉽게 처리할 수 있습니다.
fun main() = runBlocking {
val parentJob = launch {
try {
launch {
delay(1000L)
throw Exception("자식 코루틴에서 오류 발생")
}
} catch (e: Exception) {
println("부모가 예외 처리함: ${e.message}")
}
}
parentJob.join()
}
코드 가독성 향상
- CoroutineScope를 활용하여 명확한 범위 안에서 동시 실행을 제어할 수 있습니다.
4. 언제 구조화된 동시성을 사용해야 할까?
상황 | 구조화된 동시성 사용 여부 |
ViewModel에서 API 요청을 관리할 때 | viewModelScope 사용 |
Fragment에서 백그라운드 작업 실행할 때 | lifecycleScope 사용 |
UI 업데이트 없이 독립적인 백그라운드 작업 실행할 때 | CoroutineScope 사용 |
여러 개의 비동기 작업을 실행하지만, 부모 코루틴이 필요 없을 때 | GlobalScope 사용 가능 (주의 필요) |
5. 정리
- 구조화된 동시성은 부모-자식 관계를 이용해 코루틴을 체계적으로 관리하는 방식입니다.
- CoroutineScope를 활용하면 코루틴이 명확한 범위 내에서 실행되도록 보장할 수 있습니다.
- 부모 코루틴이 취소되면 자식 코루틴도 함께 취소됩니다.
- viewModelScope, lifecycleScope을 적극 활용하면 안정적인 코루틴 관리가 가능합니다.
반응형
'Android > Coroutine' 카테고리의 다른 글
[Android/Coroutine] Cold Stream vs Hot Stream 쉽게 이해하기: 언제 어떤 것을 써야 할까? (0) | 2025.03.28 |
---|---|
[Android/Coroutine] SharingStarted.WhileSubscribed(): 효율적인 Flow 공유 전략 (0) | 2025.03.20 |
[Android/Coroutine] launch vs async 차이점 정리 (0) | 2025.02.19 |
코루틴과 스레드, 그리고 프로세스: 그 유사성과 차이 (0) | 2025.01.24 |
[Android/Kotlin] 코루틴: Kotlin에서 비동기 프로그래밍을 간소화하는 방법 (1) | 2024.05.29 |