📘
【Go】A Tour of Goから学ぶチャネル(Channel)
A Tour of Goを参考にチャネルへの操作のサンプルコードを記載します。
チャネル(Channel)
チャネル( Channel )型は <-
を用いて値を送受信します。
チャネルの生成(バッファなしチャネル)
make関数で初期化します。
ch := make(chan int)
チャネルの送受信
ch <- v // vをチャネルchへ送信する
v := <-ch // chから受信した変数をvへ割り当てる
チャネルは片方の準備ができるまで送受信をブロックします。
そのためゴルーチンの同期を可能にします。
サンプルコードをもとに解説をします。
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 変数sumをチャネルcへ送信
}
func sum1(s []int, c chan int) {
time.Sleep(3 * time.Second)
sum(s, c)
}
func sum2(s []int, c chan int) {
sum(s, c)
}
func main() {
s := []int{1, 1, 1}
c := make(chan int)
go sum1(s, c)
go sum2(s, c)
x, y := <-c, <-c // チャネルCから変数を受信
fmt.Println(x + y)
}
上記のコードは2つのゴルーチンを使い合計を計算し、その結果を足し合わせています。
sum1()にて3秒間処理をSleepさせていますが、x, y := <-c, <-c
にて両方の結果を受信するまで処理を待つため、結果が正しく出力されます。
バッファなしチャネルのアンチパターン
チャネルへの送信と受信数が合っていないとデットロックが生じます。
チャネルへの送信数が多い
func main() {
ch := make(chan int)
ch <- 1
ch <- 2
fmt.Println(<-ch)
}
チャネルからの受信数が多い
func main() {
ch := make(chan int)
ch <- 1
fmt.Println(<-ch)
fmt.Println(<-ch)
}
バッファありチャネル
バッファありチャネルの初期化
make関数の第二引数にバッファの長さを指定することによってバッファありチャネルを初期化できます。
ch := make(chan int, 2)
バッファありチャネルの特徴
- バッファが詰まったときはチャネルへの送信をブロックする。
- バッファが空の時はチャネルの受信をブロックする。
バッファありチャネルを使い、値を送受信するサンプルコードです。
func main() {
ch := make(chan int, 2)
go func() {
defer close(ch)
for i := 0; i < 5; i++ {
ch <- 1
}
}()
for x := range ch {
fmt.Println(x)
}
}
実行結果
% go run .
1
1
1
1
1
問題なく結果が出力されています。
そのため送受信のブロックは実行結果からはよくわかりません。
そこでチャネルの送信処理と受信処理それぞれにPrint文を挿入して結果を見てみます。
func main() {
ch := make(chan int, 2)
go func() {
defer close(ch)
for i := 0; i < 5; i++ {
ch <- 1
fmt.Printf("i:%d\n", i)
}
}()
time.Sleep(1 * time.Second)
for x := range ch {
fmt.Println(x)
}
}
実行結果
% go run .
i:0
i:1 // 全てのバッファを使ったためチャネルへの送信をブロック。
1
1 // バッファが空になったので受信をブロック。
1 // ゴルーチン上で新しい値が送信されたので受信を開始。
i:2
i:3
i:4
1
1
バッファが2つのチャネルに対して、ゴルーチン上で値を送信していますが、バッファ上限の2つ送信したタイミングで受信処理されるまで送信がブロックされています。
そして、受信処理が開始されバッファに空きが発生すると新しいデータがチャネルに送信されています。
参考
Discussion