Channels
Go 言語の Channel:Goroutine 間の通信メカニズム。
Channel 概念
- FIFO:Queue 構造
- データの受信と送信
- Goroutines 間の通信のための組み込み機能
goroutine からデータを返す必要がある場合は、Channel を使用。
Channel の作成
make(chan val-type)Channel Buffering
デフォルトでは channel はバッファなし
Unbuffered Channel
- 読み書きの両方が完了するまで main は終了できない
- バッファなし channel は値を格納する容量がない
- 送信者は受信者が値を受け取るまでブロックする
- goroutine 間の同期通信に使用
- 送信者と受信者の両方が準備できてから値を交換
func main(){
ch := make(chan int)
go func(){
ch <- 42
}()
fmt.Println(<-ch)
}Buffered Channel
- 読み書きは異なる Goroutine 環境で行う必要がある
- 値を格納する定義された容量を持つ
- 送信者は channel が満杯の時だけブロック
- 受信者は channel が空の時だけブロック
- 送信者と受信者のタイミングを分離し、ある程度の非同期性を提供
func main(){
ch := make(chan int, 2)
ch <- 100
ch <- 10
result1 := <-ch
result2 := <-ch
}Buffered と Unbuffered の使い分け
Buffered Channel は:起動した goroutine の数が分かっている、goroutine の数を制限したい、またはキューに入れる作業量を制限したい場合に適している。
Channel Operations
値の送信
<-は channel 送信演算子vは channelchの要素型に代入可能な値
ch <- v値の受信
<-は channel 受信演算子valは channel から読み取ったデータを格納する変数
val := <-chChannel を閉じる
- close 関数の引数は channel 値
close(ch)okが true なら channel は開いているokが false なら channel は閉じており、受け取る値がない
v, ok := <-chChannel 容量の確認
cap(ch)Channel 長さの確認
len(ch)Deadlock、Panic、Resource Leak Cases
Channel Deadlock
// Case 1: 単一 Goroutine がバッファなし Channel に書き込み
func deadlock1() {
ch := make(chan int)
ch <- 1 // deadlock: 受信者がいない
}
// Case 2: 単一 Goroutine がバッファなし Channel から読み取り
func deadlock2() {
ch := make(chan int)
<-ch // deadlock: 送信者がいない
}
// Case 3: 循環依存
func deadlock3() {
ch1, ch2 := make(chan int), make(chan int)
go func() {
ch1 <- 1 // ch2 を待つ
<-ch2
}()
go func() {
ch2 <- 1 // ch1 を待つ
<-ch1
}()
}
// Case 4: バッファが満杯
func deadlock4() {
ch := make(chan int, 1)
ch <- 1
ch <- 2 // deadlock: バッファが満杯
}
// Case 5: nil channel 操作
func deadlock5() {
var ch chan int
<-ch // deadlock: nil channel
}
// Case 6: select 全てブロック
func deadlock6() {
ch := make(chan int)
select {
case <-ch:
case ch <- 1:
} // deadlock: default なし
}
// Case 7: range で channel が閉じられていない
func deadlock7() {
ch := make(chan int)
for v := range ch { // deadlock: 閉じられていない
}
}
// Case 8: mutex の誤用
func deadlock8() {
var mu sync.Mutex
ch := make(chan int)
mu.Lock()
ch <- 1 // deadlock: mutex が解除されない
}Channel Panic
// Case 1: 閉じた後に書き込み
func panic1() {
ch := make(chan int)
close(ch)
ch <- 1 // panic: 閉じた channel に書き込み
}
// Case 2: 二重クローズ
func panic2() {
ch := make(chan int)
close(ch)
close(ch) // panic: 二重クローズ
}
// Case 3: 閉じた channel に送信
func panic3() {
ch := make(chan int)
go func() {
close(ch)
}()
ch <- 1 // panic の可能性: 閉じた channel に送信
}
// Case 4: nil channel クローズ
func panic4() {
var ch chan int
close(ch) // panic: nil channel クローズ
}
// Case 5: 閉じた channel で select 送信
func panic5() {
ch := make(chan int)
close(ch)
select {
case ch <- 1: // panic: 閉じた channel に送信
default:
}
}Resource Leak
// Case 1: Goroutine リーク
func leak1() {
ch := make(chan int)
go func() {
<-ch // goroutine が永遠にブロック
}()
}
// Case 2: 受信者が送信者より多い
func leak2() {
ch := make(chan int, 1)
ch <- 1
close(ch)
<-ch
<-ch // deadlock にならないが、ゼロ値を受信
}
// Case 3: Context キャンセルが処理されない
func leak3() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan int)
go func() {
select {
case <-ctx.Done():
return
case ch <- 1:
}
}()
cancel() // goroutine がリークする可能性
}Select
- 複数の channel 操作を待つことができる
- Channel と Goroutines と組み合わせて同期と並行処理を管理する強力なツール
select vs switch
| select | switch |
|---|---|
| ブロック可能(channel で使用) | 非ブロッキング |
| 非決定的(ランダムに case を選択) | 決定的(順番に一致する case を選択) |