select文はチャネルの送受信操作を多重化できる。
select文の構文
select文の書き方は、switch文に似ている。
select { case <-ch1: // ch1から受信したときに実行される処理 case v := <-c2: // ch2から受信したときに実行される処理 // 変数に値を入れることもできる case ch3 <- y: // ch3に送信したときに実行される処理 default: // どのcaseも準備できていないときに実行される処理(省略可能) }
ポイントは、「select文は上から順番に評価されない」こと。
チャネルへの送受信は実行可能かを判断して、可能であれば実行される。
つまり、送信の場合は「キャパシティ(バッファ)がいっぱいになっていないか」、受信の場合は、「チャネルに値が送信されたか、チャネルが閉じられたか」を確認し、処理を進める準備が出来ていれば実行される。
もし、どのチャネルも準備が出来ていなければ、defaultが実行される。もしdefaultを省略していたらselect文全体がブロックされる。
複数のcaseが実行可能な状況だった場合、どれか1つのcaseがランダムに実行される。
default節を省略したサンプルコード
defaultを省略した状態でselect文がブロックされるのを確認する。
package main import ( "fmt" "time" ) func main() { start := time.Now() ch := make(chan struct{}) go func() { time.Sleep(5 * time.Second) close(ch) }() fmt.Println("Selectブロック中") select { case <-ch: fmt.Printf("ch1 close. ブロック時間: %v\n", time.Since(start)) } }
実行結果は
$ go run main.go Selectブロック中 ch1 close. ブロック時間: 5.005249353s
default節を用意した場合のサンプルコード
すべてのcase文が準備が整っていない場合、default節が実行される。
package main import "fmt" func main() { ch1 := make(chan struct{}) ch2 := make(chan struct{}) // ch1, ch2ともに宣言しただけで、何も送信されてこない(受信することはない) // そのため、default節が実行される select { case <-ch1: fmt.Println("ch1") case <-ch2: fmt.Println("ch2") default: fmt.Println("default") } }
実行結果は
$ go run main.go default
複数のcaseが実行可能な場合のサンプルコード
複数のcaseが実行可能な状態の挙動を確認する。
サンプルコードに出てくるチャネル(ch1とch2)は宣言後すぐに閉じているので、いつでも何度でも受信可能な状態。
package main import "fmt" func main() { ch1 := make(chan struct{}) ch2 := make(chan struct{}) var count1, count2 int close(ch1) close(ch2) for i := 0; i < 100; i++ { // ch1, ch2ともに閉じられているので、準備が整っている(すぐに受信できる) select { case <-ch1: count1++ case <-ch2: count2++ } } fmt.Printf("count1 = %d, count2 = %d\n", count1, count2) }
実行結果は次の通り。
// 1回目 $ go run main.go count1 = 45, count2 = 55 // 2回目 $ go run main.go count1 = 53, count2 = 47 // 3回目 $ go run main.go count1 = 48, count2 = 52
ほぼ半分ずつ実行されている。Goのランタイムはcase文全体に対して疑似乱数による一様選択をしているため、等しく選択される可能性がある。
select文とfor文を組み合わせる
並行処理を書くときに、select文とfor文を組み合わせることが良くある。
package main import ( "fmt" "time" ) func main() { idCh := make(chan int) ids := []int{1, 2, 3, 4, 5} go func() { // 1秒ごとにチャネルにidを送信 // すべて送信したら閉じる defer close(idCh) for _, id := range ids { time.Sleep(1 * time.Second) idCh <- id } }() for { select { case id, ok := <-idCh: // チャネルが閉じられたら終了 if !ok { return } fmt.Println(id) default: // 何もしない。ブロックしないためにdefault節を用意しておく } } }
実行結果は
$ go run main.go 1 2 3 4 5
for文の後にも処理を続けたい場合は、Labeled Breakを使う。(returnすると、そこで処理が終了していまうため)
package main import ( "fmt" "time" ) func main() { idCh := make(chan int) ids := []int{1, 2, 3, 4, 5} go func() { // 1秒ごとにチャネルにidを送信 // すべて送信したら閉じる defer close(idCh) for _, id := range ids { time.Sleep(1 * time.Second) idCh <- id } }() loop: for { select { case id, ok := <-idCh: // チャネルが閉じられたら終了 if !ok { break loop } fmt.Println(id) default: // 何もしない。ブロックしないためにdefault節を用意しておく } } fmt.Println("Do something...") fmt.Println("Done!!") }
実行結果は
$ go run main.go 1 2 3 4 5 Do something... Done!!
参考図書
Tour of Goを終えた人にオススメです!!