[v.0.0] GlobalScope는 왜 위험한가? 구조적 동시성 관점에서의 설명과 대안

GlobalScope는 왜 위험한가?

구조적 동시성 관점에서의 설명과 대안

Kotlin에서는 GlobalScope.launch를 통해 간단하게 코루틴을 실행할 수 있다.
하지만 아무 생각 없이 GlobalScope를 사용하는 것은 구조적 동시성 원칙을 깨뜨리고,
비동기 코드의 안정성과 예측 가능성을 크게 해친다.


문제 코드 예시

fun fetchData() {
    GlobalScope.launch {
        // 이 코루틴은 언제 끝날지 아무도 모름
    }
}
  • fetchData()launch의 결과(Job)를 반환하지 않음
  • GlobalScope는 앱 전체 생명주기에 묶임

호출한 쪽 입장에서 생기는 문제

항목문제 내용
종료 시점 추적 불가호출한 쪽에서는 이 코루틴이 언제 끝날지 알 수 없음
취소 불가Job을 반환하지 않으므로 cancel() 호출도 불가능
예외 처리 어려움예외가 외부로 전파되지 않음. try-catch도 못함
테스트 어려움완료 시점 제어 불가 → 유닛 테스트 작성 어려움
자원 누수 가능성생명주기를 갖지 않으므로 메모리 누수 위험

즉, fetchData()를 호출한 입장에서 이 코루틴은 ‘제어 불가능한 유령’과 같다.


왜 구조적 동시성과 충돌하는가?

GlobalScope는 부모 스코프가 없기 때문에 어떤 스코프와도 생명주기를 공유하지 않는다.
이는 구조적 동시성의 핵심 원칙인

“부모 스코프가 닫히면 자식 코루틴도 함께 정리된다”
는 규칙을 위반한다.


개선 예시 1: 스코프 주입 방식

fun fetchData(scope: CoroutineScope): Job {
    return scope.launch {
        // 안전하게 실행
    }
}
  • 호출하는 쪽에서 scope를 넘겨주면 생명주기와 연결됨
  • 반환된 Job을 통해 cancel() 호출도 가능
  • 예외도 추적 가능

개선 예시 2: 구조적 코루틴 사용

suspend fun fetchData() = coroutineScope {
    launch { ... }
}
  • 호출한 쪽에서 suspend 함수로 사용
  • 내부 코루틴들이 종료될 때까지 fetchData()는 자동으로 대기
  • 예외도 위로 전파되어 처리 가능

정리: GlobalScope는 언제 써야 하나?

상황사용 권장 여부
일반적인 앱 비동기 작업❌ 피해야 함
백그라운드에서 무기한 감시 (예: 앱 알림, 상태 보고)✅ 가능 (주의 필요)
테스트 가능한 구조가 필요할 때❌ 불가능
코루틴 생명주기를 제어해야 할 때❌ 안 맞음

결론

GlobalScope는 강력하지만 위험한 도구다.
단순한 비동기 작업에 GlobalScope를 사용하면,
예외 추적, 자원 해제, 테스트 모두 어려워진다.

구조적 동시성을 지키기 위해서는
코루틴의 생명주기를 관리할 수 있는 스코프를 명확히 설계하고,
필요할 경우 Job을 반환하거나, coroutineScope 내에서 suspend 함수를 사용하는 것이 권장된다.

Built with Hugo
Theme Stack designed by Jimmy