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へ
Ruby: Queue.new / push / pop → Go: make(chan T) / ch <- val / <-ch
Ruby: SizedQueue.new(n) → Go: make(chan T, n)(バッファ付きchannel)
Ruby: 該当なし → Go: select { case }(複数チャネル同時待機)
Ruby: .select.map.eachチェーン → Go: channelパイプライン
メリット
- ✓ 言語レベルでサポート — 外部ライブラリなしで並行パターン実装可能
- ✓ selectでタイムアウト、キャンセル、マルチプレキシングを簡潔に表現
デメリット
- ✗ チャネル方向を間違えるとデッドロック — デバッグが厄介
- ✗ Rubyの Enumerableチェーンに比べパイプラインコードが冗長