📡

Channel — goroutine間のパイプライン

Rubyにはない概念。goroutine間でデータを安全にやり取りする通路。

Rubyでスレッド間でデータをやり取りするにはQueueを使う:queue = Queue.new; Thread.new { queue.push(42) }; val = queue.pop

Goのchannelも似ている。ただし言語文法に組み込まれている:

ch := make(chan int)
go func() { ch <- 42 }()  // 送信
val := <-ch                // 受信

<-演算子が核心。ch <- 42はチャネルに値を送り、<-chはチャネルから値を受け取る。QueueのPush/Popと同じ役割だが文法が簡潔。

バッファ付きchannelとバッファなしchannel

make(chan int) — バッファなし。送信側は受信側の準備ができるまでブロックされる。同期的ハンドオフ。
make(chan int, 10) — バッファ10。バッファが満杯になるまで送信側はブロックされない。RubyのSizedQueue.new(10)に似ている。

select — 複数チャネルを同時に待機

select {
case msg := <-ch1:
    fmt.Println(msg)
case msg := <-ch2:
    fmt.Println(msg)
case <-time.After(5 * time.Second):
    fmt.Println("timeout")
}

複数チャネルのうち先に値が来た方を処理する。Rubyにはこういうものがない。IO.selectがソケットで似た役割をするが、汎用チャネルマルチプレキシングはGoならではの領域。

パイプラインパターン

channelを繋げるとUnixパイプラインのようなデータ処理パイプラインを作れる:

生成 → フィルタ → 変換 → 出力

各段階がgoroutineとして並行実行される。Rubyでarray.select.map.eachチェーンするように、Goではchannelを繋げてストリーム処理をする。

RubyからGoへ

1

Ruby: Queue.new / push / pop → Go: make(chan T) / ch <- val / <-ch

2

Ruby: SizedQueue.new(n) → Go: make(chan T, n)(バッファ付きchannel)

3

Ruby: 該当なし → Go: select { case }(複数チャネル同時待機)

4

Ruby: .select.map.eachチェーン → Go: channelパイプライン

メリット

  • 言語レベルでサポート — 外部ライブラリなしで並行パターン実装可能
  • selectでタイムアウト、キャンセル、マルチプレキシングを簡潔に表現

デメリット

  • チャネル方向を間違えるとデッドロック — デバッグが厄介
  • Rubyの Enumerableチェーンに比べパイプラインコードが冗長

ユースケース

同時HTTPリクエストの結果を集約して処理する時(fan-out/fan-inパターン) goroutine + channelでジョブキューを実装しSidekiqなしでバックグラウンド処理