본문 바로가기
Android/Coroutine

[Android/Coroutine] 구조화된 동시성이란?

by quessr 2025. 3. 7.

 

구조화된 동시성은 코루틴에서 중요한 개념으로, 코루틴이 부모-자식 관계를 통해 체계적으로 관리되는 방식을 의미합니다.

이를 통해 코루틴의 실행 흐름을 명확하게 제어하고, 예측 가능한 방식으로 동시 실행을 관리할 수 있습니다.

또한, 특정 범위(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을 적극 활용하면 안정적인 코루틴 관리가 가능합니다.
반응형