밤빵's 개발일지
[TIL]20241001 Coroutines 본문
자바를 공부하면서 비동기 처리와 멀티스레딩의 중요성을 알게됐다. 자바에서는 비동기 처리를 위해 CompletableFuture나 ExecutorService와 같은 기능을 주로 사용하지만, 코드가 복잡해지고 관리하기 어렵다. 코틀린에서는 코루틴(Coroutine)이라는 기능을 제공하여 비동기 작업을 쉽게 처리할 수 있다고 한다. 코틀린의 코루틴은 비동기 프로그래밍을 훨씬 간단하고 직관적으로 구현할 수 있게 돕는 핵심 기능이다.
▶코루틴이란?
코루틴은 경량 스레드라고도 불리고, 자바의 멀티스레드와 달리 스레드 대신 실행 단위를 더 작고 가볍게 나누어 비동기 작업을 처리할 수 있는 방식이다. 코루틴은 필요할 때만 CPU 리소스를 사용하고, 코드의 흐름을 중단하거나 재개할 수 있어 비동기 작업을 쉽게 처리할 수 있다. 코루틴을 사용하면 스레드 기반의 비동기 처리보다 리소스 소비가 적고, 코드 가독성이 높아지는 장점이 있다.
▶코루틴의 주요 개념
→ suspend 함수:
suspend 키워드를 사용하여 코루틴 내에서 실행 가능한 함수이다. suspend 함수는 특정 작업을 일시 중단(suspend)하고, 다시 재개할 수 있는 함수로, 비동기 작업이나 오래 걸리는 작업을 수행할 때 주로 사용된다.
→ CoroutineScope:
코루틴의 실행 범위를 관리하는 데 사용된다. 코루틴은 특정 스코프 내에서 실행되며, 스코프가 종료되면 그 안에 있는 모든 코루틴도 자동으로 종료된다. 대표적인 스코프로 GlobalScope(앱 전체에서 사용)와 CoroutineScope가 있다.
→ Dispatcher:
코루틴이 실행되는 스레드를 정의한다. Dispatchers.Default, Dispatchers.IO, Dispatchers.Main 등이 있으며, 각각 CPU, I/O 작업, 메인 스레드에서 코루틴이 실행되도록 한다.
→ Job과 Deferred:
코루틴의 작업 단위를 관리하는 역할을 한다. Job은 단순히 작업을 수행하고 완료 여부를 알려주는 역할을 하고, Deferred는 Job을 상속받아 작업이 완료되면 결과를 반환하는 역할을 한다.
▶코루틴의 장점
→ 비동기 작업의 간소화:
콜백 지옥을 피하고, 코드를 순차적으로 작성하듯 비동기 작업을 쉽게 구현할 수 있다.
→ 높은 효율성:
스레드보다 가볍고, 메모리 소비가 적어 동시에 많은 비동기 작업을 효율적으로 처리할 수 있다.
→ 중단과 재개가 용이:
코드 흐름을 중단하고 다시 재개할 수 있어 유연한 비동기 처리가 가능하다.
▶코루틴 사용 예시
▷예제 1: 기본적인 코루틴 사용
import kotlinx.coroutines.*
fun main() {
println("Start")
// GlobalScope에서 코루틴 시작
GlobalScope.launch {
delay(1000L) // 1초 동안 일시 중단
println("Hello from Coroutine!")
}
println("End")
Thread.sleep(2000L) // 메인 스레드 종료 방지를 위해 2초 대기
}
→ GlobalScope에서 코루틴을 시작하는 기본적인 예제로, launch를 통해 코루틴을 실행하고, delay(1000L)로 1초 동안 일시 중단한다. delay는 suspend 함수이므로 코루틴 내에서만 사용할 수 있다. 코루틴을 통해 "Hello from Coroutine!" 메시지가 출력되기까지 메인 스레드가 계속 실행되며, 결과적으로 "Start", "End", "Hello from Coroutine!" 순서로 출력된다.
▷예제 2: 코루틴 스코프와 비동기 처리
CoroutineScope와 async를 사용해 비동기 작업을 수행하고, await를 통해 결과를 받아오는 방식.
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Start")
val deferred1 = async { task1() }
val deferred2 = async { task2() }
println("Result: ${deferred1.await() + deferred2.await()}")
println("End")
}
suspend fun task1(): Int {
delay(1000L)
return 10
}
suspend fun task2(): Int {
delay(2000L)
return 20
}
→ runBlocking: 메인 함수가 코루틴으로 감싸져 있어 suspend 함수가 실행 가능하다. runBlocking은 코루틴이 완료될 때까지 현재 스레드를 차단하는 역할을 한다.
→ async: 비동기적으로 작업을 수행하고, Deferred를 반환한다. await()을 호출하여 결과를 받아올 수 있다.
→ task1과 task2: 각각 1초와 2초 동안 지연한 후 결과를 반환하는 suspend 함수이다.
task1과 task2가 병렬로 실행되기 때문에 총 2초가 소요된다. 만약 병렬 처리가 되지 않았다면 3초가 걸렸을 것.
await는 deferred1과 deferred2의 결과를 기다린 후 합산하여 출력한다.
▶Dispatcher 활용
코루틴에서는 디스패처를 사용하여 작업을 수행할 스레드를 지정할 수 있다.
→ Dispatchers.Default:
기본 디스패처로, CPU 집약적인 작업에 사용된다.
→ Dispatchers.IO:
I/O 작업(파일 읽기/쓰기, 네트워크 요청 등)에 적합하다.
→ Dispatchers.Main:
UI 작업을 위한 메인 스레드 디스패처(Android에서 주로 사용).
▷예제 3: Dispatcher를 사용한 코루틴 실행
import kotlinx.coroutines.*
fun main() = runBlocking {
launch(Dispatchers.Default) { // CPU 집약적인 작업을 수행
println("Default Dispatcher: ${Thread.currentThread().name}")
}
launch(Dispatchers.IO) { // I/O 작업을 수행
println("IO Dispatcher: ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // 현재 스레드에서 시작하되 필요시 다른 스레드로 전환
println("Unconfined Dispatcher: ${Thread.currentThread().name}")
}
launch { // runBlocking의 기본 스레드에서 실행
println("Main runBlocking: ${Thread.currentThread().name}")
}
}
→ 각 launch는 다른 디스패처를 사용하여 서로 다른 스레드에서 실행된다. 이를 통해 스레드 간 작업을 효율적으로 분배하여 최적의 성능을 낼 수 있다.
▶코루틴의 장단점
장점 | 단점 |
비동기 작업을 간단하게 구현 가능 | 코루틴의 개념과 구조에 대한 학습 필요 |
효율적인 자원 활용 및 높은 성능 | 잘못된 사용 시 디버깅이 어려울 수 있음 |
코드의 가독성과 유지보수성 향상 | 스레드의 명시적 제어가 필요할 때는 부적합 |
'Kotlin' 카테고리의 다른 글
[TIL]20241007 Safe Call(?.), Elvis 연산자(?:), Not-null Assertion(!!) 활용 (0) | 2024.10.07 |
---|---|
[TIL]20241006 Nullable과 Non-Nullable 타입: 코틀린의 null 안전성 (0) | 2024.10.06 |
[TIL]20241005 자바와 코틀린의 변수 선언 방식 차이 (1) | 2024.10.05 |
[TIL]20241004 var과 val 의 차이? (0) | 2024.10.04 |
[TIL]20241002 Coroutines② (0) | 2024.10.02 |