코딩

[코루틴 (1)] 코루틴 탄생 배경과 기본 개념

Eastpark 2025. 1. 15. 23:10
728x90
반응형

앞으로 이 시리즈에서는 코루틴의 기초부터 안드로이드 실무 적용, Compose와의 결합, 고급 퍼포먼스 최적화 기법까지 폭넓게 다룰 예정입니다. 그 여정의 시작점으로, 이번 글에서는 코루틴이 왜 등장하게 되었고, 어떤 개념적 특징을 갖고 있는지 살펴봅니다.


핵심 키워드

코루틴 탄생 배경

비동기 프로그래밍

스레드(Thread) 한계

경량 스레드(Lightweight Thread)

suspend 함수


1. 비동기 프로그래밍의 필요성과 전통적 한계

 

1.1 비동기 작업의 중요성

현대의 애플리케이션, 특히 안드로이드 앱이나 백엔드 서버는 네트워크, 파일 I/O, DB 조회 등 시간이 오래 걸리는 작업을 자주 수행합니다. 이런 작업을 단일 스레드(메인 스레드)에서 순차적으로 처리하면 UI가 멈추거나 응답이 느려져 사용자 경험이 떨어집니다.

비동기 프로그래밍은 동시에 여러 일을 처리하거나, 특정 작업이 끝날 때까지 메인 스레드를 대기시키지 않아도 되게 만드는 핵심 기법입니다.

그러나 기존에는 스레드(Thread)를 직접 다루거나, 콜백(Callback) 방식을 남발해야 했고, 이는 코드 가독성 저하와 디버깅 복잡도로 이어졌습니다.

 

1.2 전통적인 스레드 모델의 문제

안드로이드/자바 환경에서는 오랫동안 ThreadRunnable, 또는 AsyncTask 같은 구형 API를 사용해왔습니다.

스레드 생성 비용: 각 스레드는 고정된 스택 메모리를 가지고 시작되므로, 스레드를 많이 만들면 메모리 낭비와 성능 저하가 발생합니다.

콜백 지옥: 비동기 로직이 중첩되는 상황에서 콜백이 여러 겹으로 들어가면, 가독성과 유지보수성이 크게 떨어집니다.

동시성 제어: 여러 스레드가 공유 자원에 접근하는 과정에서 동기화(Synchronization) 문제가 일어나기 쉽습니다.

 

이처럼 스레드 기반 비동기 처리는 유지보수성과 성능 면에서 점차 한계를 드러냈습니다.

반응형

2. 코루틴(Coroutine)의 등장이유

 

2.1 코틀린에서의 해답

Kotlin 언어는 자바의 한계를 보완하면서도 함수형 프로그래밍 기법을 지원하는 현대적 언어입니다. “콜백 지옥” 없이 비동기 코드를 동기적 느낌으로 작성하고 싶다는 요구가 커지면서, Kotlin에서 코루틴(Coroutine) 개념을 공식 지원하게 되었습니다.

경량 스레드(Lightweight Thread): 코루틴은 기본적으로 “함수의 일시 중단(Suspend)과 재개(Resume)” 메커니즘을 제공해, 기존 스레드보다 훨씬 더 많은 수의 병행 작업을 수행할 수 있습니다.

구조적 동시성(Structured Concurrency): 코루틴 스코프 내에서 실행되는 모든 비동기 작업을 체계적으로 관리할 수 있게 하여, 예외 처리나 취소(Cancellation)가 직관적으로 이뤄집니다.

 

2.2 코루틴과 자바스크립트 Promise 비교

자바스크립트의 Promise/async/await 패턴처럼, Kotlin 코루틴도 비동기 로직을 동기 코드처럼 작성할 수 있게 해줍니다.

다만 코루틴은 더욱 유연한 스코프 관리, 취소 메커니즘, 디스패처 분리 기능 등을 갖추고 있어 안드로이드/서버 개발에 최적화되어 있습니다.


3. 코루틴의 기본 개념

 

3.1 suspend 함수

코루틴에서는 특수 키워드인 suspend를 이용해 “일시 중단 가능한 함수”를 정의합니다.

해당 함수가 네트워크 통신, 디스크 I/O 등 시간이 걸리는 작업을 수행할 때, 스레드를 점유하지 않고 코루틴이 중단될 수 있습니다(스레드는 다른 코루틴 수행에 재활용).

작업이 끝나면 코루틴을 재개(Resume) 하여 이어서 코드를 실행합니다.

suspend fun fetchData(): String {
    // 네트워크 호출 가정
    // 일시 중단 후, 결과를 받아 재개
    return "Fetched data"
}

 

3.2 CoroutineScope와 Dispatcher

코루틴을 실행하려면 CoroutineScope가 필요합니다. 스코프는 코루틴이 어떤 환경(Dispatcher)에서 동작하는지, 어떤 Job과 연결돼 있는지를 지정합니다.

Dispatcher:

Dispatchers.IO : I/O 작업(네트워크, 파일)용

Dispatchers.Default : CPU 집약적 연산용

Dispatchers.Main : 메인 스레드(UI) 작업용

Job: 코루틴을 식별하는 객체로, 취소나 예외 전파 시 중요한 역할을 합니다.

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        // IO용 코루틴
    }
    launch(Dispatchers.Default) {
        // CPU 집약적 코루틴
    }
}

위 예시에서 runBlocking은 단순히 예제용으로 전체 코루틴이 끝날 때까지 기다려주기 위한 함수입니다. 실제 안드로이드 앱에선 runBlocking 대신 LifecycleScopeViewModelScope 등을 사용합니다.


4. 코루틴이 가져다주는 장점

 

1. 코드 가독성 개선

suspend 함수와 async/await 또는 launch 패턴을 사용해 콜백 없이도 비동기 로직을 “동기 형태”로 작성할 수 있습니다.

2. 효율적 자원 활용

스레드를 매번 새로 만들지 않고, 코루틴 레벨에서 컨텍스트를 전환할 수 있어 경량화됩니다.

3. 안정적인 취소/에러 처리

구조적 동시성 덕분에, 부모 스코프가 취소되면 자식 코루틴도 자동 취소되는 등, 에러와 취소 로직을 쉽게 관리할 수 있습니다.

4. 다양한 플랫폼 지원

코루틴은 Kotlin Multiplatform 환경에서도 동작해, 안드로이드 외의 플랫폼에서도 재사용이 가능합니다.

728x90

5. 시리즈에서 다루게 될 내용

 

앞으로 이어질 시리즈(총 14편)에서는 다음과 같은 주제를 구체적으로 살펴볼 예정입니다.

0. 코루틴 탄생 배경과 기본 개념

1. 코루틴 기초(suspend, CoroutineScope, Dispatcher)

2. Structured Concurrency와 코루틴 스코프 관리

3. 예외 처리와 취소(Cancellation) 기법

4. 코루틴 채널(Channel)과 select

5. Flow 심화(Flow, StateFlow, SharedFlow)

6. 안드로이드 실무 적용(Retrofit, Room, ViewModel)

7. 동시성 문제 해결(Mutex, Semaphore)

8. 코루틴 테스트 & 디버깅 전략

9. 고급 퍼포먼스 최적화

10. WorkManager + 코루틴 연동

11. LifecycleScope & 안드로이드 생명주기 대응

12. Compose + 코루틴 + Flow 시너지

13. Compose Navigation + 코루틴 취소/재시작 사례

 

(위 순서와 내용은 유동적으로 조정될 수 있습니다.)


마무리 및 다음 예고

 

이번 글에서는 코루틴(Coroutine)이 왜 등장했는지, 스레드 기반 비동기 모델이 가진 한계를 어떻게 보완해주는지, 그리고 suspend, CoroutineScope 같은 기본 개념을 짚어보았습니다.

 

다음 편에서는 코루틴의 기초 API(suspend, launch, async)를 더 깊이 파헤치고, Dispatcher를 어떻게 효율적으로 사용해 스레드를 제어하는지 예제와 함께 살펴볼 예정입니다.

728x90
반응형