KotlinのChannelについて

採用はこちら

Kotlin の Channel(チャネル) は、コルーチン間で安全にデータを受け渡すための “通信・キュー” の抽象化です。

しばしば Flow と比較されますが、Channel は

  • 状態やストリーム表現ではなく
  • 並行処理における通信・調停のための低レベル構造

という位置づけになります。

目次

Channelの本質

Channelは「キュー + 同期ポイント」

Channelは次の2つを同時に満たします。

  1. 値を一時的に保持できる(キュー)
  2. 送信と受信を suspend によって自然に同期できる

つまり、

  • ロックを書かず
  • スレッドをブロックせず
  • コルーチン同士が協調して処理を進められる

というのが最大の価値です。

Producer Coroutine ──▶ Channel ──▶ Consumer Coroutine

基本構文と挙動

Channelの生成

val channel = Channel<Int>()

これは Rendezvous Channel(容量0) を生成します。

  • 値を「溜めない」
  • 送信と受信が出会った瞬間に値が渡る

送信(send)

launch {
    channel.send(1)
}
  • sendsuspend関数
  • 受信側がいなければ コルーチンが停止(suspend)

受信(receive)

launch {
    val value = channel.receive()
}
  • 値が来るまで suspend
  • スレッドは止まらない

Channelは「ブロック」しない

Java の BlockingQueue との決定的な違いです。

項目BlockingQueueChannel
待機スレッドをブロックコルーチンをサスペンド
スケール悪い非常に良い
非同期設計困難前提

これにより、Channel は 大量の並行処理でも破綻しにくい構造になります。

Channelの種類(容量設計がすべて)

Rendezvous Channel

Channel<Int>()
  • 送信と受信が 必ず同期
  • バックプレッシャーが自然にかかる

向いている用途

  • 生産と消費の速度を揃えたい
  • 処理の順序・整合性が重要

Buffered Channel

Channel<Int>(capacity = 10)
  • 最大10件まで保持
  • 超えると send が suspend

向いている用途

  • 一時的な負荷の偏りを吸収
  • IO待ち・ネットワーク処理

Conflated Channel

Channel<Int>(Channel.CONFLATED)
  • 常に最新1件のみ保持
  • 古い値は破棄される

注意点

  • StateFlow の代替ではない
  • 「状態管理」ではなく「通信の簡略化」

向いている用途

  • 進捗率
  • センサー値
  • UIに渡す“最新情報だけ”の通知

Unlimited Channel

Channel<Int>(Channel.UNLIMITED)
  • send が基本的に suspend しない
  • バックプレッシャーが消失

注意

  • メモリ枯渇の危険
  • 明確な理由がない限り避ける

受信の基本形:forループ

launch {
    for (value in channel) {
        println(value)
    }
}
  • close() されるまで受信し続ける
  • close後は 自然終了
  • receive() を直接呼ぶより安全

Channelのcloseとは何か

channel.close()
  • これ以上 send できない
  • すでに送られた値はすべて受信される
  • forループが終了する

※ close後に send すると例外になるため、実務では 送信側の責務を明確に分けるのが重要です。

Channelが得意な典型パターン

Producer–Consumer

fun CoroutineScope.producer() = produce {
    repeat(5) {
        send(it)
    }
}
  • produce は Channel を返す
  • 生産と消費を明確に分離できる

Fan-out

repeat(3) {
    launch {
        for (value in channel) {
            println("Worker $it got $value")
        }
    }
}

重要な性質

  • 1つの値は 1つのConsumerにのみ届く
  • ブロードキャストではない

→ 並列ワーカー処理に最適

Fan-in

val channel = Channel<Int>()

repeat(3) {
    launch {
        channel.send(it)
    }
}
  • 複数Producer → 1 Consumer
  • 集約処理・ログ収集など

ChannelとFlowの正確な違い

Channel

  • hot
  • 通信・キューのモデル
  • 値は基本的に 1回消費
  • 並行処理・バックプレッシャー制御が主役

Flow(cold)

  • collect されて初めて流れる
  • 宣言的・変換に強い
  • データストリームの表現向き

SharedFlow / StateFlow(hot)

  • 複数購読を前提
  • イベント配信 / 状態管理向き
  • UI層ではこちらが主流

よくある誤用と注意点

Channelで状態管理しようとする

→ StateFlowの役割

UIイベントを雑にChannelで流す

→ 取りこぼし・二重処理の原因
→ SharedFlow のほうが安全なケースが多い

closeを設計せず使う

→ 無限待機・リークの原因

実務での判断基準

  • 並行処理・ワーカー制御・キュー → Channel
  • 状態の保持と購読 → StateFlow
  • イベント配信(複数購読) → SharedFlow
  • データ変換パイプライン → Flow

まとめ

  • Channelは コルーチン間通信の基礎構造
  • send / receive は suspend で自然な同期
  • 容量設計が最重要
  • ブロードキャスト用途ではない
  • Flow / StateFlow と役割を混同しないことが最大のポイント

以上、KotlinのChannelについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次