코틀린 코루틴의 Channel과 Deferred
코틀린 코루틴의 Channel과 Deferred
코틀린 코루틴은 코루틴 간 데이터를 전달하기 위한 두 가지 핵심 동시성 프리미티브(concurrency primitive)를 제공합니다. 바로 Channel과 Deferred입니다. Deferred<T>는 미래에 완료될 단일 결과를 나타내고, Channel<T>는 값의 스트림을 나타냅니다. 이 두 프리미티브가 어떻게 다르고, 파이프라인(pipeline), 팬아웃(fan-out), 팬인(fan-in) 같은 패턴을 언제 적용해야 하는지 이해하는 것은 동시성 아키텍처를 설계하는 데 있어 필수적입니다. 실제 면접에서도 Channel과 Deferred의 차이를 묻는 질문이 빈번하게 출제되므로, 각각의 동작 원리와 사용 시나리오를 명확히 정리해 두시는 것이 좋습니다.
이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
Deferred와Channel의 카디널리티(cardinality)와 용도 차이를 설명할 수 있습니다.produce와 채널 체이닝을 활용하여 파이프라인을 구성할 수 있습니다.- 여러 소비자에게 작업을 분배하는 팬아웃(fan-out) 패턴을 이해하고 설명할 수 있습니다.
- 여러 생산자의 결과를 하나로 모으는 팬인(fan-in) 패턴을 이해하고 설명할 수 있습니다.
- 단일 결과가 필요한 경우와 스트림이 필요한 경우를 기준으로
Deferred와Channel중 적합한 선택을 내릴 수 있습니다.
Deferred: 단일 비동기 결과
Deferred<T>는 async에 의해 생성되며, 미래에 사용 가능해질 정확히 하나의 값을 나타냅니다. await()를 호출하면 결과가 준비될 때까지 호출자를 일시 중단(suspend)시키며, 값이 한 번 생성되면 이후 await() 호출 시 동일한 결과를 즉시 반환합니다. 즉, Deferred는 결과를 내부적으로 캐싱하므로 여러 번 await()를 호출해도 연산이 재실행되지 않습니다.
val deferred: Deferred<String> = async {
fetchUserName(userId)
}
val name: String = deferred.await()
Deferred는 코루틴이 단일 연산이나 네트워크 호출을 수행하고 하나의 결과를 반환하는 상황에 적합합니다. 다른 언어의 Future나 Promise와 유사한 개념이라고 생각하시면 됩니다. 콜백 기반 접근 방식과 달리, await()는 구조화된 동시성(structured concurrency)과 자연스럽게 통합됩니다. 부모 스코프가 취소되면 Deferred도 함께 취소되므로, 생명주기 관리를 별도로 신경 쓸 필요가 없습니다.
Channel: 값의 스트림
Channel<T>은 코루틴 간에 값의 스트림을 송수신할 수 있도록 지원합니다. 일시 중단이 가능한 큐(suspending queue)처럼 동작하여, 채널이 가득 차면 send()가 일시 중단되고, 채널이 비어 있으면 receive()가 일시 중단됩니다. Channel은 다양한 용량(capacity) 전략을 지원합니다.
- Rendezvous (용량 0): 수신자가 준비될 때까지 송신자가 일시 중단되며, 반대의 경우도 마찬가지입니다. 생산자와 소비자 간의 엄격한 핸드오프(handoff)를 보장하는 방식입니다.
- Buffered: 고정 크기 버퍼를 사용하여, 버퍼가 가득 차지 않는 한 송신자가 대기 없이 진행할 수 있습니다. 생산자와 소비자의 일시적인 속도 차이를 흡수하는 데 유리합니다.
- Conflated: 가장 최근 값만 유지합니다. 소비자가 느린 경우 중간 값은 버려지므로, 상태 업데이트처럼 최신 값만 의미가 있는 경우에 적합합니다.
- Unlimited: 무제한 버퍼를 사용하여 송신자를 일시 중단하지 않지만, 소비자가 뒤처지면 메모리를 무한정 소모할 수 있으므로 주의가 필요합니다.
Channel은 생산자-소비자(producer-consumer) 패턴이나 파이프라인 아키텍처처럼, 코루틴 간에 데이터가 지속적으로 흐르는 경우에 유용합니다. 용량 전략 선택에 따라 백프레셔(backpressure) 동작과 메모리 사용량이 크게 달라지므로, 사용 사례에 맞는 전략을 신중히 선택해야 합니다.
파이프라인 구축
파이프라인(pipeline)은 채널을 통해 서로 통신하는 코루틴들을 체이닝하는 패턴입니다. 각 단계(stage)는 하나의 채널에서 데이터를 소비하고, 변환을 수행한 뒤, 선택적으로 다른 채널에 전송합니다. produce 빌더를 사용하면 ReceiveChannel에 값을 전송하는 코루틴을 간편하게 생성할 수 있습니다.