💪

【Go 腕試し】複数チャネルのselect

に公開

What's this?

Go の入門者から上級者まで幅広く多くの Gopher が Go を楽しみつくすためのクイズを作りました。
ぜひチャレンジしてみてください!

さっそく問題!

package main

import "fmt"

func main() {
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)

    ch1 <- 1
    ch2 <- 2

    select {
    case v := <-ch1:
        fmt.Println(v)
    case v := <-ch2:
        fmt.Println(v)
    }
}
  1. 1 が出力される
  2. 2 が出力される
  3. 12 がランダムに出力される
  4. デッドロックが発生する

※環境依存がない前提での出題ですが、何かあればごめんなさい。。。
※Go 1.25 で動作確認をしています。

回答・解説

答えはこちら

正解は 3 の 1 または 2 がランダムに出力されるです!
正解できましたかね?
簡単だぜ!ってかたはごめんなさい。次こそリベンジします。

解説はこんな感じです。

package main

import "fmt"

func main() {
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)

    ch1 <- 1
    ch2 <- 2

    // select文に到達した時点で、両方のcaseが準備完了している
    select {
    case v := <-ch1:
        fmt.Println(v)  // ch1から受信可能
    case v := <-ch2:
        fmt.Println(v)  // ch2からも受信可能
    }
    // Goの仕様:複数のcaseが準備完了している場合は疑似ランダムに1つを選択
    // そのため、実行ごとに 1 が出力されたり 2 が出力されたりする
}

ポイント

多くの人は「select は上から順に評価される」と思ってコードを読んだと思います。ただし、実際には複数の case が準備完了している場合、Go は疑似ランダムに 1 つを選択します。

これは公平性(fairness)を保つための意図的な設計です。もし上から順に評価されると、以下のようなループで特定のチャネルが永遠に無視される可能性があります:

// もし上から順なら、ch1に常にデータがある場合、ch2は永遠に選ばれない
for {
    select {
    case v := <-ch1:  // 常に優先
        process(v)
    case v := <-ch2:  // 無視される
        process(v)
    }
}

そのため、Go はランダム選択を採用しています。

実行してみると

$ go run main.go
1
$ go run main.go
2
$ go run main.go
1

同じコードでも実行ごとに結果が変わることが確認できるはずです。
細かい仕様ですが、へ~と思ってくださればうれしいです。


References

さいごに

どうでしたか?
Gopher のみなさん、楽しんでもらえましたかね?
今回余裕の正解だった上級者の方はごめんなさい。次こそリベンジします!
今後も Go の勉強になるようなクイズを作りますので、次回のチャレンジもお待ちしております!

Discussion