selectの概要
チャネルの待機処理 Goの予約語 | ||
select 概要 |
||
|
基本的なselectの使い方
以下のコードは、2つのチャネルのうち、先に送信されたデータを受信する例です。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "チャネル1のデータ"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "チャネル2のデータ"
}()
select {
case msg := <-ch1:
fmt.Println("受信:", msg)
case msg := <-ch2:
fmt.Println("受信:", msg)
}
}
解説:
ch1
とch2
の2つのチャネルを作成し、それぞれゴルーチンで異なる時間後にデータを送信します。select
は、どちらかのチャネルからデータを受信できるようになった時点で、その分岐の処理を実行します。- この場合、1秒後に
ch2
がデータを送信するため、ch2
のケースが実行されます。
実行結果:
受信: チャネル2のデータ
defaultを使った非ブロッキング動作
すべてのチャネルがブロックされている場合に、待機せずに処理を進めるためにはdefault
を使用します。
package main
import "fmt"
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println("受信:", msg)
default:
fmt.Println("データがないため即時処理")
}
}
解説:
ch
チャネルにはデータが送られていないため、通常のcase
文はブロックされます。default
を定義することで、待機せずに「データがないため即時処理」と出力されます。
実行結果:
データがないため即時処理
タイムアウト処理
チャネルのデータ受信を一定時間待ち、それ以上待たずに処理を進める場合は、time.After
を使用します。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println("受信:", msg)
case <-time.After(2 * time.Second):
fmt.Println("タイムアウト")
}
}
解説:
- 通常のチャネル受信を待機しますが、
time.After
を使って2秒以上データが来なければ「タイムアウト」と出力されます。 - これにより、無限にブロックされるのを防ぐことができます。
実行結果:
タイムアウト
注意事項
- select文はチャネル専用: 通常の条件分岐には使用できず、チャネルの送受信でのみ利用可能。
- 複数のケースが同時に準備完了する場合はランダムに実行: どのケースが選ばれるかは不定。
- デフォルトケースは慎重に使用する: デフォルトケースがあると、非同期処理を待たずに次の処理が進むため、意図しない動作につながることがある。
よくある質問
- Q: selectを使わずにチャネルを待機できますか?
- A: はい。通常の
<-ch
を使えば、1つのチャネルのデータを待機できますが、複数のチャネルを待つ場合はselect
が必要です。 - Q: selectはどのような場面で使いますか?
- A: 複数のチャネルのデータを同時に待機し、どれかが準備できたら即座に処理を行う場合に使用します。
- Q: selectを使うとデッドロックを防げますか?
- A: 部分的に防ぐことはできますが、チャネルの適切な管理が必要です。特に、受信側がいない状態で送信し続けるとデッドロックが発生します。
- Q: defaultを常に指定すべきですか?
- A: いいえ。
default
を使うと、チャネルのデータを待たずに処理が進むため、状況に応じて適切に判断する必要があります。
まとめ
select
は複数のチャネルの送受信を同時に待機し、準備ができたものを実行する。default
を使うとブロックを回避し、即座に処理を実行できる。time.After
を利用することで、一定時間経過後にタイムアウト処理を行える。- 適切に利用すれば、並行処理の制御を柔軟に行うことができる。