1. ホーム
  2. go

[解決済み] 複数のゴルーチンが1つのチャンネルをリッスンする

2023-01-11 13:21:30

質問

複数のゴルーチンが同じチャネルで同時に受信を試みています。チャネルで受信を開始した最後のゴルーチンが値を取得するようです。これは言語仕様のどこかにあるのでしょうか、それとも未定義の動作なのでしょうか。

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        <-c
        c <- fmt.Sprintf("goroutine %d", i)
    }(i)
}
c <- "hi"
fmt.Println(<-c)

出力します。

goroutine 4

遊び場での例

EDITです。

思ったより複雑なことに気づきました。メッセージはすべてのゴルーチンを回って渡されます。

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        msg := <-c
        c <- fmt.Sprintf("%s, hi from %d", msg, i)
    }(i)
}
c <- "original"
fmt.Println(<-c)

出力します。

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

注意 上記の出力は最近のバージョンのGoでは古くなっています(コメントを参照)

プレイグラウンドでの例

どのように解決するのですか?

確かに複雑ですが、いくつかの経験則があるので、物事はもっと簡単に感じられるはずです。

  • チャンネルに正式な引数を使用することを好む に正式な引数を使うことを好みます。この方法でより多くのコンパイラチェックを受けることができますし、より良いモジュール性も得られます。
  • 特定のゴー・ルーチンで同じチャンネルの読み書きの両方を避けることができます。 (「メイン」ルーチンを含む)。さもなければ、デッドロックがより大きなリスクとなります。

この2つのガイドラインを適用した、あなたのプログラムの別バージョンがこちらです。このケースでは、多くのライターとランプ、チャンネル上の1つのリーダーを実証しています。

c := make(chan string)

for i := 1; i <= 5; i++ {
    go func(i int, co chan<- string) {
        for j := 1; j <= 5; j++ {
            co <- fmt.Sprintf("hi from %d.%d", i, j)
        }
    }(i, c)
}

for i := 1; i <= 25; i++ {
    fmt.Println(<-c)
}

http://play.golang.org/p/quQn7xePLw

これは単一のチャネルに書き込む 5 つのゴー ルーチンを作成し、それぞれが 5 回書き込んでいます。メインゴールーチンは25のメッセージをすべて読み込みますが、その順番がしばしば順不同であることに気づくかもしれません(つまり、並行処理が明白であることです)。

この例は、Goチャンネルの特徴を示しています。複数のライターが1つのチャンネルを共有することが可能で、Goは自動的にメッセージをインターリーブします。

同じことが、1つのチャネルに1人のライターと複数のリーダーがいる場合にも当てはまります(2番目の例)。

c := make(chan int)
var w sync.WaitGroup
w.Add(5)

for i := 1; i <= 5; i++ {
    go func(i int, ci <-chan int) {
        j := 1
        for v := range ci {
            time.Sleep(time.Millisecond)
            fmt.Printf("%d.%d got %d\n", i, j, v)
            j += 1
        }
        w.Done()
    }(i, c)
}

for i := 1; i <= 25; i++ {
    c <- i
}
close(c)
w.Wait()

これは 第二例 には、メインゴルーチンに課せられた待ちが含まれています。この待ちがなければ、メインゴルーチンは速やかに終了し、他の5つのゴルーチンを早期に終了させることになります。 (これは olov に感謝します) .

どちらの例でも、バッファリングは必要ありませんでした。一般的に、バッファリングはパフォーマンス向上のためだけのものと考えるのが良い原則です。プログラムがデッドロックしないのであれば なしで バッファがなければデッドロックは起きません。 バッファでもデッドロックは起こりません (しかし、その逆で は常に真である)。そのため もう一つの経験則として、バッファリングなしで開始し、必要に応じて後で追加してください。 .