はじめに
Goにおいてバッファ付きチャネルの基本と利点を整理する。
バッファ付きチャネルの基本
バッファサイズは、チャネル内に一時的に保持できる値の最大数。
以下の場合、c は最大10個の整数を保持できる。
c := make(chan int, 10)
送信側(c <- x
)は、チャネルのバッファに空きがあれば即座にデータを送信できる。
受信側(<-c
)は、チャネルのバッファにデータがあれば即座にデータを受け取れる。
バッファ付きチャネルの利点
バッファ付きチャネルを使用することで、送信側と受信側の処理を完全に同期させる必要がなくなり、非同期的に動作できるようになる。
非バッファ付きチャネルの場合
・非バッファ付きチャネルでは、送信側と受信側は必ず同時に通信しなければならない。
・送信側が値を送信しようとすると、受信側がその値を受け取るまでブロックされる(待機する)。
・同様に、受信側が値を受け取ろうとすると、送信側が値を送信するまでブロックされる。
バッファ付きチャネルの場合
・バッファ付きチャネルでは、バッファに空きがある限り、送信側は値を送信してすぐに次の処理に進むことができる(非同期的に動作できる)。
・バッファにデータがある限り、受信側も即座に値を受け取ることができる。
・バッファがいっぱいになると、送信側は再びブロックされる(受信側がデータを消費するまで待機)。
・バッファが空の場合、受信側は再びブロックされる(送信側がデータを送るまで待機)。
具体例での違い
非バッファ付きチャネルの場合
送信側(c <- 1
)は受信側(<-c
)が準備されるまでブロックされる。
結果として、送信側と受信側は必ず同時に動作しなければならない。
c := make(chan int)
go func() {
c <- 1 // 受信側が準備されるまでブロックされる
fmt.Println("送信完了")
}()
fmt.Println(<-c) // 値を受信してブロック解除
バッファ付きチャネルの場合
送信側は2つの値をバッファに送信して、次の処理に進むことができる。
受信側もバッファから値を取り出すため、両者が同期していなくても問題ない。
c := make(chan int, 2)
go func() {
c <- 1 // バッファに空きがあるので即座に送信可能
c <- 2 // 2つ目も即座に送信可能
fmt.Println("送信完了")
}()
fmt.Println(<-c) // バッファから即座に受信可能
fmt.Println(<-c) // 2つ目も即座に受信可能
A Tour of Goでの実例
fibonacci
関数(ゴルーチン)と main
関数は柔軟に非同期で動作することができる。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c) // チャネルを閉じると、受信側が送信終了を検出できる
}
func main() {
c := make(chan int, 10) // チャネル c に最大10個の値をバッファリング
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
fibonacci 関数の送信部分
c <- x
この送信操作は、バッファがいっぱいでない限りブロックされない。
バッファがいっぱいになると、受信側(for i := range c
)がデータを消費するまで待機する。
main 関数の受信部分
for i := range c {
fmt.Println(i)
}
チャネルに値があればすぐに受信して出力する。
チャネルが空の場合、送信側がデータを送るまで待機する。