はい、それは複雑ですが、物事をより簡単に感じさせる経験則がいくつかあります。
- グローバル スコープでチャネルにアクセスする代わりに、go-routine に渡すチャネルに正式な引数を使用することを好みます。このようにして、より多くのコンパイラチェックを取得でき、モジュール性も向上します。
- 特定の go-routine (「メイン」のものを含む)で同じチャネルで読み取りと書き込みの両方を回避します。そうしないと、デッドロックのリスクがはるかに高くなります。
これらの 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
これは、1 つのチャネルに書き込みを行う 5 つの go-routine を作成し、それぞれが 5 回書き込みます。メインの go-routine は 25 個のメッセージすべてを読み取ります。表示される順序が連続していないことが多いことに気付くかもしれません (つまり、並行性が明らかです)。
この例は、Go チャネルの機能を示しています。複数のライターが 1 つのチャネルを共有することが可能です。Go はメッセージを自動的にインターリーブします。
次の 2 番目の例に示すように、同じことが 1 つのチャネルの 1 つのライターと複数のリーダーに適用されます。
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()
この2 番目の例には、メインの goroutine に課された待機が含まれています。そうしないと、すぐに終了し、他の 5 つの goroutine が早期に終了してしまいます (この修正についてolovに感謝します) 。
どちらの例でも、バッファリングは必要ありませんでした。一般に、バッファリングをパフォーマンスの向上のみと見なすことは良い原則です。プログラムがバッファーなしでデッドロックしない場合は、バッファーでもデッドロックしません(ただし、その逆は常に真とは限りません)。したがって、別の経験則として、バッファリングなしで開始し、後で必要に応じて追加します。