Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20241001 Coroutines 본문

Kotlin

[TIL]20241001 Coroutines

최밤빵 2024. 10. 1. 19:17

자바를 공부하면서 비동기 처리와 멀티스레딩의 중요성을 알게됐다. 자바에서는 비동기 처리를 위해 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는 다른 디스패처를 사용하여 서로 다른 스레드에서 실행된다. 이를 통해 스레드 간 작업을 효율적으로 분배하여 최적의 성능을 낼 수 있다.

 

▶코루틴의 장단점

장점 단점
비동기 작업을 간단하게 구현 가능 코루틴의 개념과 구조에 대한 학습 필요
효율적인 자원 활용 및 높은 성능 잘못된 사용 시 디버깅이 어려울 수 있음
코드의 가독성과 유지보수성 향상 스레드의 명시적 제어가 필요할 때는 부적합