kotlin

Kotlin -코루틴 (Coroutines) [ 비동기 프로그래밍 ]

임베디드 친구 2024. 12. 22. 10:04
반응형

Kotlin 언어는 비동기 프로그래밍을 단순화하기 위해 코루틴(Coroutines)이라는 강력한 도구를 제공합니다. 코루틴은 일시 중단 가능한 함수로, 다른 비동기 처리 방식보다 효율적이고 간단하게 비동기 코드를 작성할 수 있게 합니다. 이번 포스팅에서는 코루틴의 개념과 비동기 프로그래밍, 코루틴 빌더, 그리고 채널과 흐름(Flow)에 대해 예제와 함께 다뤄보겠습니다.

코루틴 개념과 비동기 프로그래밍

코루틴(Coroutines)은 경량화된 스레드라고 할 수 있습니다. 코루틴을 통해 개발자는 비동기적인 작업을 보다 직관적이고 간단하게 처리할 수 있습니다. Java에서의 전통적인 비동기 처리 방법은 주로 스레드(Thread)를 사용하는 것이었지만, 스레드는 무겁고 자원을 많이 소모한다는 단점이 있습니다. 코루틴은 이러한 단점을 해결하기 위해 만들어졌습니다.

코루틴을 이용하면 다음과 같은 장점이 있습니다.

  • 경량성: 스레드보다 훨씬 가벼워서 많은 수의 코루틴을 동시에 실행할 수 있습니다.
  • 일시 중단 가능: 특정 시점에서 일시 중단(suspend)되고 다시 재개(resume)될 수 있어 자원 사용을 최적화할 수 있습니다.
  • 비동기 코드의 직관적 표현: 콜백 지옥 없이 비동기 코드를 동기식으로 작성할 수 있어 가독성이 좋습니다.

코틀린의 코루틴은 suspend 키워드를 사용해 일시 중단할 수 있는 함수를 만듭니다. 예를 들어, 서버로부터 데이터를 가져오는 함수를 생각해봅시다.

import kotlinx.coroutines.*

suspend fun fetchData(): String {
    delay(1000L) // 1초 대기 (네트워크 요청을 시뮬레이션)
    return "데이터를 성공적으로 가져왔습니다"
}

suspend 함수인 fetchData()는 1초 동안 대기한 후 데이터를 반환합니다. 이때 delay() 함수는 현재 스레드를 차단하지 않고, 다른 작업이 수행될 수 있게 합니다.

코루틴 빌더 (launch, async)

Kotlin에서 코루틴을 생성하는 주요 방법으로는 launchasync 두 가지 빌더가 있습니다. 이 두 빌더는 모두 새로운 코루틴을 시작하지만, 사용 방법과 목적이 다릅니다.

launch

launchJob 객체를 반환하며, 결과를 반환하지 않는 코루틴을 시작할 때 사용합니다. 주로 사이드 이펙트가 있는 작업(예: 데이터베이스에 데이터 삽입, 파일 저장 등)을 수행할 때 유용합니다.

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        val result = fetchData()
        println(result)
    }
    println("코루틴이 시작되었습니다")
}

위 코드에서 launch를 사용해 코루틴을 시작하고 fetchData()를 호출합니다. runBlocking은 현재 스레드를 차단하고 코루틴이 완료될 때까지 기다립니다. 이 코드의 출력은 다음과 같습니다.

코루틴이 시작되었습니다
데이터를 성공적으로 가져왔습니다

async

asyncDeferred 객체를 반환하며, 결과를 반환하는 코루틴을 시작할 때 사용합니다. async는 미래에 사용할 값을 약속하는 역할을 합니다. await() 함수를 통해 결과를 얻을 수 있습니다.

import kotlinx.coroutines.*

fun main() = runBlocking {
    val deferredResult = async {
        fetchData()
    }
    println("다른 작업을 수행 중입니다")
    val result = deferredResult.await()
    println(result)
}

위 코드에서는 async로 코루틴을 시작하고, 다른 작업을 수행한 후 await()를 통해 결과를 받아옵니다. 이 코드의 출력은 다음과 같습니다.

다른 작업을 수행 중입니다
데이터를 성공적으로 가져왔습니다

채널과 흐름 (Flow)

비동기 데이터 스트림을 처리할 때는 채널(Channel)흐름(Flow)을 사용할 수 있습니다.

채널 (Channel)

채널(Channel)은 코루틴 간의 데이터를 주고받을 수 있는 방법을 제공합니다. 이는 생산자-소비자 패턴을 구현하는 데 유용합니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel

fun main() = runBlocking {
    val channel = Channel<Int>()

    launch {
        for (x in 1..5) {
            channel.send(x)
        }
        channel.close()
    }

    launch {
        for (y in channel) {
            println(y)
        }
    }
}

이 예제에서는 하나의 코루틴이 채널에 데이터를 전송하고, 다른 코루틴이 데이터를 수신합니다. 채널은 코루틴 간의 통신을 간단하게 만들어줍니다.

흐름 (Flow)

Kotlin의 Flow는 데이터 스트림을 처리할 때 사용되는 새로운 방식입니다. Flow는 콜드 스트림(cold stream)으로, 수집될 때까지 아무 작업도 수행하지 않습니다. 이는 Sequence와 유사하지만, 비동기적으로 작동한다는 점에서 다릅니다.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    flow {
        for (i in 1..5) {
            emit(i)
            delay(500L)
        }
    }.collect { value ->
        println("받은 값: $value")
    }
}

이 코드에서 flow 빌더는 1부터 5까지의 값을 생성하며 emit() 함수를 통해 각 값을 방출합니다. collect() 함수는 Flow의 값을 수집하여 처리합니다. 각 값이 방출될 때마다 500ms의 지연이 발생합니다.

코루틴을 활용한 비동기 프로그래밍 예제

코루틴의 진정한 강력함은 비동기 작업을 쉽게 조합할 수 있다는 데 있습니다. 예를 들어, 두 개의 비동기 작업을 동시에 수행하고 결과를 조합하는 예제를 살펴보겠습니다.

import kotlinx.coroutines.*

suspend fun fetchFirstData(): String {
    delay(1000L)
    return "첫 번째 데이터"
}

suspend fun fetchSecondData(): String {
    delay(1500L)
    return "두 번째 데이터"
}

fun main() = runBlocking {
    val first = async { fetchFirstData() }
    val second = async { fetchSecondData() }

    println("두 작업을 동시에 수행 중입니다...")
    println("결과: ${first.await()} + ${second.await()}")
}

이 예제에서는 fetchFirstData()fetchSecondData()를 동시에 실행하고 결과를 조합합니다. 두 작업이 비동기로 수행되기 때문에, 총 실행 시간은 2500ms가 아닌 1500ms에 가깝습니다.

결론

Kotlin의 코루틴은 비동기 프로그래밍을 훨씬 단순화해주며, launch, async, Channel, Flow 등을 통해 다양한 상황에 맞는 비동기 처리를 쉽게 구현할 수 있습니다. 특히, 복잡한 비동기 작업을 보다 직관적이고 가독성 좋게 표현할 수 있다는 점에서 코루틴은 매우 강력한 도구입니다.

이번 포스팅에서는 코루틴의 개념과 코루틴 빌더, 그리고 채널과 Flow를 살펴보았습니다.

반응형

'kotlin' 카테고리의 다른 글

Kotlin으로 DSL (Domain-Specific Language) 작성하기  (0) 2024.12.24
Kotlin과 Java의 상호 운용  (0) 2024.12.23
Kotlin의 고급 기능  (0) 2024.12.21
Kotlin 함수형 프로그래밍  (0) 2024.12.19
Kotlin 애노테이션과 리플렉션  (0) 2024.12.18