Coroutine Dispatchers 어디까지 알고있나

Gunt·약 9시간 전
1

Coroutine Dispatchers 는 간단하게 이해하고 넘어가기 쉬운 부분이다. 이름이 나름 직관적이고 사용법이 크게 어렵지 않아서 간단하게 이해하고 넘어가기 쉽다.

예를 들어 Dispatchers의 종류는 네 가지가 있고, 보통 "네트워크 요청Dispatchers.IO, UI 작업Dispatchers.Main" 정도로만 알고 있어도 개발하는 데 크게 문제는 없다.

사실 그 정도만 알고 개발을 하더라도 큰 문제없이 티 안나게 개발은 가능하다.

하지만 이런 접근 방식은 예상치 못한 성능 저하, 불필요한 컨텍스트 스위칭을 초래할 가능성이 크다.
디스패처를 제대로 이해하고 활용하면, 앱 성능을 최적화하고 리소스 관리 효율성을 극대화할 수 있다.

Dispatchers 종류와 내부 동작 원리같은 기본적인 내용은 다른 곳에서도 쉽게 찾아볼 수 있기 때문에 간단하게 정리한 게시글에서 확인하자.

Dispatchers.Main과 나머지 디스패처의 차이

차이점:

  • Dispatchers.Main안드로이드 프레임워크에서 직접 제공하는 UI 스레드를 사용한다.

  • Dispatchers.IO와 Dispatchers.DefaultKotlin 코루틴 라이브러리가 자체적으로 관리하는 별도의 스레드 풀을 사용한다.


Dispatchers.Main

  • 안드로이드의 Main Looper를 사용

  • UI 작업을 수행하는 안드로이드 시스템 스레드를 직접 활용

  • 별도의 스레드 풀을 생성하지 않으며, 기존 UI 스레드 위에서 동작

  • UI 작업을 제외한 무거운 연산을 실행하면 ANR(Application Not Responding) 발생 가능


Dispatchers.Main 사용 시 발생하는 에러

JVM 환경에서 Dispatchers.Main을 사용하면 오류가 발생한다.

fun main() = runBlocking<Unit> {
    launch(Dispatchers.Main) {
        println("Dispatchers Main : ${Thread.currentThread().name}")
    }
}

왜 문제가 발생할까?

  • Dispatchers.Main은 안드로이드 환경에서만 제공되는 메인(UI) 스레드 디스패처이다.
  • JVM 환경에서는 MainLooper가 존재하지 않으므로, 이를 사용할 수 없음.
  • 이를 해결하려면 Dispatchers.setMain()을 사용하여 테스트 환경에서 Main 디스패처를 설정해야 한다.
Dispatchers.setMain(Dispatchers.Default)

Dispatchers.IO & Dispatchers.Default

  • Kotlin 코루틴 라이브러리가 관리하는 별도의 스레드 풀에서 실행
  • OS의 기본 스레드가 아니라, 코루틴 런타임에서 동적으로 생성/관리하는 스레드를 활용
  • 네트워크, DB, 파일 I/O 등 백그라운드 작업을 효율적으로 수행할 수 있도록 최적화됨

launch(Dispatchers.Default)에서 launch(Dispatchers.IO)를 실행하면 Thread가 무조건 변경될까?

launch(Dispatchers.Default) {
    println("Default 실행 스레드: ${Thread.currentThread().name}")

    launch(Dispatchers.IO) {
        println("IO 실행 스레드: ${Thread.currentThread().name}") // 같은 스레드일 수도 있음!
    }
}
  • 코루틴이 이미 I/O 작업을 수행할 수 있는 스레드에서 실행되고 있다면, 새로운 스레드로 변경되지 않고 같은 스레드를 재사용할 수 있다.
  • 현재 실행 중인 스레드가 Dispatchers.Default(CPU 연산) 전용이라면, Dispatchers.IO에 맞는 스레드로 전환될 가능성이 크다.
Kotlin의 Dispatchers.IO와 Dispatchers.Default는 별도의 스레드 풀을 사용하지만, 내부적으로 같은 Executor 기반에서 관리되므로 스레드를 재사용할 수 있다.
즉, Dispatchers.Default에서 실행 중이던 스레드가 이미 IO 작업이 가능한 상태라면, launch(Dispatchers.IO)를 해도 같은 스레드에서 실행될 가능성이 있다.
  • Dispatchers.IO와 Dispatchers.Default는 서로 다른 용도지만, 같은 스레드 풀을 공유하면서 스레드를 재사용할 수 있다.
  • 따라서, launch(Dispatchers.IO)를 해도 같은 스레드에서 실행될 수 있다.
  • 하지만, 현재 실행 중인 스레드가 CPU 연산 전용으로 과부하가 걸려 있다면, 새로운 스레드를 생성해서 실행될 수도 있다.



Retrofit과 Dispatchers.IO의 관계

많은 개발자가 Retrofit을 사용할 때 Dispatchers.IO를 반드시 써야 한다고 생각하는데,
Retrofit은 자체적으로 비동기 처리를 하기 때문에 무조건 Dispatchers.IO를 붙일 필요는 없다.

// Dispatcher 없이 호출해도 비동기로 실행됨
suspend fun fetchData(apiService: ApiService): List<Data> {
    return apiService.getData()
}

이렇게 써도 문제가 없다.

하지만 네트워크 요청 이후 데이터베이스 저장 같은 추가적인 I/O 작업이 포함되면,
Dispatchers.IO를 활용하는 게 성능 면에서 유리하다.

suspend fun fetchData(apiService: ApiService): List<Data> {
    return withContext(Dispatchers.IO) {  // I/O 최적화
        val response = apiService.getData()
        saveToDatabase(response)  // DB 저장
        response
    }
}

네트워크 요청만 있다면Dispatchers.IO 없어도 된다.
추가적인 I/O 작업(DB 저장 등)이 있다면Dispatchers.IO를 쓰는 게 낫다.

📌 출처: https://thdev.tech/kotlin/2021/01/12/Retrofit-Coroutines/

Coroutine과 Thread의 차이

코루틴과 스레드는 비슷해 보이지만, 동작 방식이 다르다.(Thread와 Coroutine은 비교 layer가 다름)

Coroutine VS Thread

  • Thread: OS에서 직접 관리하는 실행 단위이며, 독립적인 리소스를 사용하고 컨텍스트 스위칭 비용이 높음.

  • Coroutine: Thread 내부에서 동작하는 경량 실행 단위로, 중단(suspension)과 재개(resume)가 가능하여 더 효율적인 동작이 가능함.

  • 코루틴의 스레드안드로이드 시스템 스레드에 종속되지 않으며, Kotlin 코루틴 라이브러리 내부에서 관리됨. 즉, 코루틴을 사용하면 특정 플랫폼(OS)의 스레드 관리 정책에 의존하지 않고, 코루틴 런타임이 직접 관리하는 최적화방식으로 실행된다.

스레드실행 단위(Thread of execution)
코루틴실행 컨텍스트의 개념, 코루틴은 독립적인 실행 단위라기보다는 특정 스레드 위에서 동작하는 경량 태스크

Thread.sleep() vs delay()

🛑 Thread.sleep(ms)

  • 호스레드 자체를 지정된 시간 동안 완전히 멈춘다.
  • 그동안 다른 작업을 수행할 수 없음 → 비효율적

🚀 delay(ms)

  • 코루틴만 일시 정지하고, 스레드는 계속 실행된다.
  • 다른 작업을 병렬로 수행할 수 있다.

Thread.sleep을 적용한 2초간 다음 코루틴은 동작하지 못함. 그 이후 delay로 2000, 1000ms 지연시켰지만 각각의 코루틴은 동작하여 총 4242ms에 모든 동작이 완료됨

Thread.sleep()스레드를 블로킹하지만,
delay()코루틴만 중단하고 스레드는 계속 실행된다.


정리

✅ Dispatchers.Main은 안드로이드 UI 스레드를 직접 활용하지만, 나머지 디스패처는 코루틴 라이브러리가 관리하는 별도 스레드 풀을 사용한다.
✅ Dispatchers.Main은 JVM 환경에서는 사용할 수 없으므로 주의해야 한다.
✅ Dispatchers.IO와 Dispatchers.Default는 서로 다른 용도지만, 같은 스레드 풀을 공유하면서 스레드를 재사용할 수 있다.
✅ Thread.sleep()은 스레드를 블로킹하지만, delay()는 스레드를 점유하는 목적이 아니라 해당 코루틴을 지연시키는 용도, 비동기적으로 실행된다.

profile
기술에 생각 더하기

0개의 댓글