Go言語の大きな特徴である並行処理。前回はGoroutineについて学びました。今回は、Goroutine間で安全にデータをやり取りするための仕組みであるChannel(チャネル)と、複数のチャネルを扱うためのselect文について学びましょう!
Channel(チャネル)とは?
チャネルは、Goroutine間でデータを送受信するためのパイプのようなものです。 Go言語では「通信によってメモリを共有する」という考え方が推奨されており、チャネルはその中心的な役割を担います。
チャネルを使うことで、複数のGoroutineが同時に同じデータにアクセスしようとして起こる問題(競合状態)を防ぎ、安全なデータのやり取りが可能になります。
チャネルは以下の特徴を持っています:
- 型付けされている: チャネルは特定の型のデータしか送受信できません(例:
chan int
はint型専用)。 - 送受信操作:
<-
演算子を使ってデータの送信と受信を行います。 - 同期機構: デフォルト(バッファなしチャネル)では、送信側と受信側が揃うまで処理がブロックされます。これによりGoroutine間の同期を取ることができます。
チャネルの作成
チャネルは組み込みのmake
関数を使って作成します。
make
で作成しない場合、チャネル変数の値はnil
となり、そのまま送受信しようとするとデッドロック(プログラムが停止してしまう状態)になります。
チャネルへの送信と受信
<-
演算子を使ってチャネルにデータを送信したり、チャネルからデータを受信したりします。
- 送信:
チャネル変数 <- 値
- 受信:
変数 := <-チャネル変数
上記の例では、sendData
Goroutineがチャネルにデータを送信するまで、main
Goroutineのmessage := <-ch
の部分で処理が待機(ブロック)されます。データが送信されると、main
Goroutineは受信を完了し、処理を続行します。
バッファなしチャネル vs バッファ付きチャネル
make
で作成する際、第二引数でバッファサイズを指定できます。
- バッファなしチャネル (Unbuffered Channel):
make(chan T)
- サイズが0のチャネル。
- 送信操作は、対応する受信操作が準備できるまでブロックされる。
- 受信操作は、対応する送信操作が準備できるまでブロックされる。
- Goroutine間の確実な同期が必要な場合に適しています。
- バッファ付きチャネル (Buffered Channel):
make(chan T, capacity)
- サイズが
capacity
のバッファを持つチャネル。 - 送信操作は、バッファがいっぱいになるまでブロックされない。
- 受信操作は、バッファが空になるまでブロックされない。
- 一時的にデータを溜めておくキューのような使い方ができます。 FIFO(先入れ先出し)の順序が保証されます。
- サイズが
バッファ付きチャネルは、送信側と受信側の処理速度に差がある場合などに便利ですが、バッファがいっぱいになると送信側が、バッファが空になると受信側がブロックされる点はバッファなしチャネルと同じです。
チャネルのクローズ
close
関数を使ってチャネルを閉じることができます。これは、もうこれ以上データを送信しないことを受信側に伝えるための合図です。
注意点:
- 閉じたチャネルに送信しようとするとパニックが発生します 。
- 閉じたチャネルから受信すると、バッファに残っている値を受信し続けます。バッファが空になると、そのチャネルの型のゼロ値(intなら0, stringなら””など)が即座に返され、2つ目の戻り値
ok
がfalse
になります。 - チャネルを閉じるのは、通常は送信側の役割です。
- すでに閉じているチャネルを再度
close
しようとするとパニックが発生します。
for range
を使ってチャネルから受信することもできます。この場合、チャネルが閉じられると自動的にループが終了します。
select文
select
文は、複数のチャネル操作(送受信)を同時に待機し、いずれか一つが実行可能になったときにその操作を実行するための仕組みです。switch
文に似ていますが、チャネル操作専用です。
基本的な使い方
select
文はcase
節を持ち、各case
はチャネルの送受信操作に対応します。
複数のcase
が同時に実行可能になった場合、select
はそのうちの一つをランダムに選択して実行します。
defaultケース
select
文にdefault
ケースを追加すると、どのcase
もすぐに実行できない場合にdefault
ケースが実行されます。これにより、select
文がブロックしなくなります(ノンブロッキング操作)。
タイムアウト処理
select
とtime.After
チャネルを組み合わせることで、タイムアウト処理を簡単に実装できます。time.After
は指定した時間が経過した後に現在時刻を送信するチャネルを返します。
最初のselect
では、c1
からの受信(2秒後)よりもtime.After
(1秒後)が先に実行可能になるため、タイムアウトします。二番目のselect
では、c2
からの受信(0.5秒後)がtime.After
(1秒後)よりも先に実行可能になるため、結果を受信できます。
まとめ
今回は、Goの並行処理において重要な役割を果たすチャネルとselect文について学びました。
- チャネル: Goroutine間で安全にデータを送受信するための型付きパイプ。
make
で作成し、<-
で送受信する。バッファなし(同期的)とバッファ付き(非同期的)がある。close
で閉じることができる。 - select文: 複数のチャネル操作を待ち受け、最初に実行可能になったものを(ランダムに)実行する。
default
でノンブロッキングに、time.After
と組み合わせてタイムアウト処理を実装できる。
これらの機能を使いこなすことで、Goの強力な並行処理をより安全かつ効率的に実装できるようになります。最初は少し難しく感じるかもしれませんが、実際にコードを書いて動かしてみるのが一番の近道です!どんどん試してみてくださいね。
次回は、並行処理をより安全に行うためのsync
パッケージ(MutexやWaitGroupなど)について見ていきましょう!