Kotlin

[Kotlin] 코루틴 (Coroutine) 개요

꾸준함. 2024. 10. 2. 11:09

코루틴

  • Kotlin Serialization, Kotlin Lincheck과 함께 코틀린에서 지원하는 공식 라이브러리로 동시성을 지원하는 라이브러리
  • 기존의 쓰레드 기반 비동기 프로그래밍의 복잡성과 성능 한계를 극복하기 위해 설계되었으며, 동시성을 처리하는 강력한 도구
  • 비동기 non-blocking으로 동작하는 코드를 동기 방식으로 작성할 수 있도록 지원
    • 단, 컴파일된 후의 결과는 동지적으로 동작하지 않음

 

  • CoroutineContext를 통해 Dispatcher, Error Handling, 그리고 ThreadLocal 등을 지원
  • CoroutineScope를 통해 Structured Concurrency와 Cancellation 제공
  • Flow, Channel 등의 심화 기능 제공

 

1. kLogger

  • 코틀린에서 로깅을 쉽게 사용할 수 있도록 설계된 경량 라이브러리로 복잡한 설정이나 설정 파일 없이 쉽게 설정하고 사용 가능
  • 코틀린과 완벽하게 통합되어, 코틀린 언어의 장점을 활용한 간결하고 읽기 쉬운 코드를 작성 가능

 

2. 동기 스타일 지원

  • runBlocking과 suspend 함수를 통해 비동기 non-blocking 코드를 동기 스타일로 변경 가능
    • non-blocking 하지만 동기처럼 보이는 코드를 작성 가능하기 때문에 가독성, 디버깅 측면에서 좋음

 

import kotlinx.coroutines.*
import mu.KotlinLogging
// kLogger 인스턴스 생성
private val logger = KotlinLogging.logger {}
fun main() = runBlocking {
logger.info { "애플리케이션 시작" }
// 비동기 작업 시작 (동기적으로 처리됨)
val result1 = asyncOperation1()
val result2 = asyncOperation2()
// 두 결과를 조합하여 출력
logger.info { "최종 결과: ${result1 + result2}" }
logger.info { "애플리케이션 종료" }
}
// suspend 함수로 정의된 비동기 작업
suspend fun asyncOperation1(): Int {
logger.debug { "비동기 작업 1 시작" }
delay(1000L) // 비동기 작업 시뮬레이션
logger.debug { "비동기 작업 1 완료" }
return 10
}
suspend fun asyncOperation2(): Int {
logger.debug { "비동기 작업 2 시작" }
delay(1500L) // 비동기 작업 시뮬레이션
logger.debug { "비동기 작업 2 완료" }
return 20
}
view raw .kt hosted with ❤ by GitHub

 

부연 설명

  • runBlocking은 코루틴을 블로킹 방식으로 실행하며 해당 블록 내에서 비동기 작업이 진행되지만, 마치 동기 코드처럼 순차적으로 실행
    • asyncOperation1과 asyncOperation2는 suspend 함수로 비동기 작업을 수행하고 해당 함수들은 비동기적으로 실행되지만, runBlocking 스코프 내에서 호출되므로 동기적인 방식으로 처리

 

3. CoroutineContext

  • 코루틴 실행에 필요한 정보를 관리하는 인터페이스
  • 코루틴명, CoroutineDispatcher, ThreadLocal, CoroutineExceptionHandler 등을 제공하여 코루틴의 실행 환경을 제어

 

import kotlinx.coroutines.*
import kotlin.coroutines.*
import mu.KotlinLogging
private val logger = KotlinLogging.logger {}
// ThreadLocal 데이터 정의 (스레드별로 데이터를 관리하기 위해 사용)
val threadLocal = ThreadLocal<String>()
fun main() = runBlocking {
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
logger.error(exception) { "코루틴에서 예외 발생: ${exception.message}" }
}
val context = Dispatchers.Default + CoroutineName("MyCoroutine") + exceptionHandler + threadLocal.asContextElement("InitialValue")
launch(context) {
logger.info { "코루틴 이름: ${coroutineContext[CoroutineName]}" }
logger.info { "현재 스레드: ${Thread.currentThread().name}" }
logger.info { "ThreadLocal 값: ${threadLocal.get()}" }
threadLocal.set("NewValue")
logger.info { "ThreadLocal 새로운 값: ${threadLocal.get()}" }
throw RuntimeException("테스트 예외")
}
logger.info { "메인 함수 종료" }
}
view raw .kt hosted with ❤ by GitHub

 

부연 설명

  • launch(context)에서 앞서 정의한 CoroutineContext를 사용해 코루틴을 실행
  • Dispatcher는 코루틴이 실행될 쓰레드를 제어
    • Dispatchers.Default는 기본 디스패처로, CPU 집약적인 작업에 적합한 쓰레드 풀에서 실행

 

4. CoroutineScope

  • CoroutineScope를 사용하면 코루틴의 생명 주기를 제어할 수 있으며, 코루틴 내에서 발생한 cancel이 자식 코루틴에도 전파 가능
    • CoroutineScope 함수를 통해 별도의 CoroutineScope를 생성하고 해당 스코프 내에서는 자식 Coroutine이 모두 완료되고 끝남을 보장
    • cancel이 발생할 경우 자식 CoroutineScope로 cancel을 전파
    • delay 함수는 cancel이 전파되면 CancellationException을 수행하여 cancel


import kotlinx.coroutines.*
import mu.KotlinLogging
private val logger = KotlinLogging.logger {}
fun main() = runBlocking {
// 별도의 CoroutineScope 생성
val customScope = CoroutineScope(Dispatchers.Default)
// CoroutineScope 내에서 코루틴 실행
customScope.launch {
try {
logger.info { "코루틴 1 시작" }
delay(2000L)
logger.info { "코루틴 1 완료" }
} catch (e: CancellationException) {
logger.warn { "코루틴 1 취소됨" }
}
}
customScope.launch {
try {
logger.info { "코루틴 2 시작" }
delay(3000L)
logger.info { "코루틴 2 완료" }
} catch (e: CancellationException) {
logger.warn { "코루틴 2 취소됨" }
}
}
delay(1000L)
logger.info { "CoroutineScope 취소 시도" }
customScope.cancel() // 스코프 취소 -> 자식 코루틴들에게 전파됨
// 취소 후 스코프 종료를 보장
customScope.coroutineContext[Job]?.join()
logger.info { "CoroutineScope 종료" }
}
view raw .kt hosted with ❤ by GitHub

 

부연 설명

  • CoroutineScope(Dispatchers.Default)를 사용하여 새로운 코루틴 스코프를 생성하며 해당 스코프 내에서 실행되는 모든 코루틴은 이 스코프의 생명 주기에 종속됨
  • delay(1000L)로 1초를 기다린 후, customScope.cancel()을 호출하여 스코프를 취소하며 이때 스코프 내에서 실행 중인 모든 자식 코루틴(코루틴 1, 코루틴 2)에 취소가 전파
  • 스코프가 취소되면, 자식 코루틴에서 CancellationException이 발생하여 try-catch에서 이를 처리

 

5. Flow

  • 코틀린은 Reactor의 Flux와 비슷한 Flow를 제공하여 비동기 스트림을 다룰 수 있는 기능을 제공
  • Flow는 데이터를 하나씩 비동기적으로 방출(emit)하고, 구독자는 데이터를 차례대로 수집(collect)할 수 있음
    • Reactor의 onNext와 같이 emit을 통해 값을 전달
    • Reactor의 subscribe와 같이 collect를 이용하여 item 전달

 

  • flux와 마찬가지로 다양한 연산 제공
    • map, flatMap, take와 같은 중간 연산자 제공
    • collect, toList, toSet과 같은 종료 연산자 제공

 

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import mu.KotlinLogging
private val logger = KotlinLogging.logger {}
// flow 함수를 정의하여 비동기 데이터 흐름 생성
fun createFlow(): Flow<Int> = flow {
for (i in 1..5) {
delay(500L)
logger.info { "emit: $i" }
emit(i)
}
}
fun main() = runBlocking {
logger.info { "Flow 시작" }
// Flow에서 데이터를 수집 (subscribe와 유사한 역할)
createFlow().collect { value ->
logger.info { "collect: $value" }
}
logger.info { "Flow 종료" }
}
view raw .kt hosted with ❤ by GitHub

 

6. Channel

  • 채널은 코루틴 간 안전하게 데이터를 주고받을 수 있는 통신 메커니즘이며 여러 코루틴이나 쓰레드에서도 안전성을 보장
  • 채널은 비동기적으로 데이터를 송수신할 수 있으며, 기본적으로는 파이프와 같은 역할 수행
  • capacityBufferOverflow 인자를 설정하여 버퍼의 크기와 동작 방식을 조정 가능
    • capacity를 통해 채널의 버퍼 크기 설정
    • BufferOverflow 버퍼가 가득 찼을 때의 동작 정의

 

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import mu.KotlinLogging
private val logger = KotlinLogging.logger {}
fun main() = runBlocking {
// 버퍼가 가득 찼을 때 최신 아이템을 삭제하는 BufferOverflow 설정
val channel = Channel<Int>(capacity = 3, onBufferOverflow = BufferOverflow.DROP_LATEST)
// 여러 데이터를 채널에 보냄
val producer = launch {
for (i in 1..5) {
logger.info { "send: $i" }
channel.send(i)
delay(100L)
}
}
// 채널에서 데이터를 수신
val consumer = launch {
for (i in 1..5) {
val received = channel.receive()
logger.info { "receive: $received" }
delay(300L)
}
}
producer.join()
consumer.join()
logger.info { "Channel 예제 종료" }
}
view raw .kt hosted with ❤ by GitHub

 

참고

패스트 캠퍼스 - Spring Webflux 완전 정복 : 코루틴부터 리액티브 MSA 프로젝트까지

반응형

'Kotlin' 카테고리의 다른 글

[Kotlin] CoroutineScope  (0) 2024.10.03
[Kotlin] CoroutineContext  (0) 2024.10.03
[Kotlin] 코루틴 (Coroutine) 기초  (0) 2024.10.02
[Kotlin] lateinit과 by lazy의 차이점  (2) 2022.07.13