バッファ付きチャネル
容量、チャネルのクローズ、チャネルに対するrange
「バッファ付きチャネル」はCoddyKit上の無料Go Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはGo Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Go Academyコースには全4レッスンが含まれています。
バッファ付きチャネルとは
バッファ付きチャネルには容量があり、バッファに空きがある限り送信はブロックされません。チャネルはキューのように機能します:
package main
import "fmt"
func main() {
ch := make(chan int, 3) // capacity 3
ch <- 1 // does not block — buffer has room
ch <- 2 // does not block
ch <- 3 // does not block
// ch <- 4 // would block — buffer full
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}キューとしてのバッファ付きチャネル
バッファ付きチャネルは、ゴルーチン間のFIFOキューを実装します:
package main
import ("fmt"; "sync")
func producer(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
fmt.Printf("produced %d\n", i)
}
}
func main() {
ch := make(chan int, 2) // buffer 2 — producer runs ahead
var wg sync.WaitGroup
wg.Add(1)
go producer(ch, &wg)
wg.Wait()
close(ch)
for v := range ch { fmt.Printf("consumed %d\n", v) }
}チャネルのlen()とcap()
バッファ付きチャネルの現在のサイズと容量を確認します:
package main
import "fmt"
func main() {
ch := make(chan string, 5)
ch <- "a"
ch <- "b"
ch <- "c"
fmt.Println("cap:", cap(ch)) // 5
fmt.Println("len:", len(ch)) // 3 — items currently buffered
<-ch
fmt.Println("len:", len(ch)) // 2
}プロデューサーとコンシューマーの分離
バッファによって、プロデューサーの速度とコンシューマーの速度を分離できます。プロデューサーは、ブロックするまでバッファサイズ分だけ先行して処理できます:
package main
import ("fmt"; "sync"; "time")
func produce(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
}
}
func consume(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range ch {
time.Sleep(10 * time.Millisecond) // slower consumer
fmt.Println("consumed:", v)
}
}
func main() {
ch := make(chan int, 3)
var wg sync.WaitGroup
wg.Add(2)
go produce(ch, &wg)
go consume(ch, &wg) // BUG: produce closes before consume sees all
// In practice, coordinate close carefully
fmt.Println("decoupled")
}バッファ付きチャネルによるセマフォ
容量Nのバッファ付きチャネルを使うと、同時に実行するゴルーチンをN個に制限できます。これは単純なセマフォです:
package main
import ("fmt"; "sync"; "time")
func main() {
sem := make(chan struct{}, 3) // max 3 concurrent
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
sem <- struct{}{} // acquire
defer func() { <-sem }() // release
fmt.Printf("job %d running\n", i)
time.Sleep(50 * time.Millisecond)
}()
}
wg.Wait()
}バッファ付きチャネルを閉じる
バッファ付きチャネルを閉じても、受信側はクローズを検出する前に、バッファに残った値を読み出せます:
package main
import "fmt"
func main() {
ch := make(chan int, 5)
for i := 1; i <= 3; i++ { ch <- i }
close(ch)
// Drain all buffered values after close:
for v := range ch {
fmt.Println(v) // 1, 2, 3
}
// range exits after channel closed and drained
}バッファ付きとバッファなし: 選択する基準
選択の指針:
- バッファなし: 確実な同期的受け渡し、厳密な順序、ランデブーによる同期
- バッファ付き: プロデューサーとコンシューマーの速度を分離、ブロックを削減、セマフォを実装、N個の結果を収集
- まずはバッファなしをデフォルトとし、サイズを正当化できる場合にのみバッファを追加します
- バッファサイズが小さすぎると依然としてブロックし、大きすぎるとバグを隠してしまいます
バッファ付きチャネルによる結果の収集
並列に実行するゴルーチンから、ブロックせずに結果を収集するにはバッファ付きチャネルを使用します:
package main
import ("fmt"; "sync")
func square(n int) int { return n * n }
func main() {
nums := []int{1, 2, 3, 4, 5}
results := make(chan int, len(nums)) // buffer = goroutine count
var wg sync.WaitGroup
for _, n := range nums {
wg.Add(1)
n := n
go func() {
defer wg.Done()
results <- square(n)
}()
}
wg.Wait()
close(results)
for r := range results { fmt.Println(r) }
}満杯のバッファによるゴルーチンリーク
バッファが満杯で読み手が存在しない場合、送信側は永遠にブロックされ、ゴルーチンリークが発生します:
package main
import ("fmt"; "time"; "runtime")
func main() {
ch := make(chan int, 2)
// Start goroutine that fills buffer and then blocks
go func() {
ch <- 1
ch <- 2
ch <- 3 // blocks: buffer full, no receiver
fmt.Println("never reached")
}()
time.Sleep(50 * time.Millisecond)
fmt.Println("goroutines:", runtime.NumGoroutine()) // 2 — leaked
}バッファ付きチャネルとしてのtime.After
time.Afterは容量1の <-chan time.Timeを返します。ランタイムは指定した期間の経過後にそこへ送信し、その後チャネルはガベージコレクションの対象になります:
package main
import ("fmt"; "time")
func doWithTimeout(work func() string) (string, bool) {
result := make(chan string, 1)
go func() { result <- work() }()
select {
case r := <-result:
return r, true
case <-time.After(100 * time.Millisecond):
return "", false
}
}
func main() {
r, ok := doWithTimeout(func() string {
time.Sleep(50 * time.Millisecond)
return "done"
})
fmt.Println(r, ok)
}確認問題
容量いっぱいのバッファ付きチャネルに送信するとどうなりますか?
復習: バッファ付きチャネル
まとめ:
make(chan T, N)は容量Nのバッファ付きチャネルを作成します- 送信はバッファが満杯の場合にのみブロックされ、受信はバッファが空の場合にのみブロックされます
len(ch)— 現在の項目数、cap(ch)— 容量- プロデューサーとコンシューマーの分離、並列処理の結果の収集、セマフォの実装に使用します
- クローズするとEOFになるまで値を読み出せます。
range chがこれを自動的に処理します
よくある質問
「バッファ付きチャネル」レッスンは無料ですか?
はい。「バッファ付きチャネル」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Go Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Go Academyコースには全4レッスンが含まれています。
「バッファ付きチャネル」で何を学びますか?
容量、チャネルのクローズ、チャネルに対するrange ブラウザで直接実行するハンズオンコードでGo Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Go Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのGo Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「バッファ付きチャネル」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このGo Academyレッスンでコードを書いて実行できますか?
はい。すべてのGo Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ゴルーチンの起動
- バッファなしチャネル
- バッファ付きチャネル
- チャネルの方向とパイプラインパターン