9月26日、antonz.orgが「Go concurrency distilled」と題した記事を公開した。Goの並行処理全域をインタラクティブなコードサンプルとともに凝縮して解説したミニブックだ。
Goの並行処理は「書けるけど、ちゃんと理解できているか自信がない」という領域になりやすい。goroutineは簡単に起動できる一方、チャネルの閉じ方、select文の挙動、データレース回避といった細部で詰まるケースは多い。マイクロサービスやクラウドネイティブ開発でGoが広く採用される現在、並行処理の正確な理解はコードレビューや設計の場面で特に重要だ。本記事はそうした「なんとなく動かしている」状態を脱するための実践的なリファレンスとして構成されており、著者は「初心者向けではなく、クイックリフレッシャーだ」と明言している。コードは全てブラウザ上で実行・編集できるインタラクティブ形式で、GitHubリポジトリからPDF版も入手できる。また、AI生成コンテンツを一切使用していないと明記されている点も、昨今のAI生成技術記事の質にばらつきがある状況において特徴的だ。
パイプラインとエラーハンドリングが特に厚い
全体のカバー範囲は以下のとおりだ:
Goroutines / Channels / Select / Pipelines / Time / Context / Wait groups / Data races / Race conditions / Mutexes / Semaphores / Signaling / Run once / Object pool / Atomics / Testing / Scheduling / Diagnostics
この中で特に読み応えがあるのが Pipelines と チャネルの挙動の細部 だ。パイプラインは「Reader → N個のProcessor → Writer」という構成が典型で、各ステップの入出力はチャネルで繋がれる。エラーハンドリングについては、3つのアプローチが具体的なコードで示されている。
① 最初のエラーで即リターン
func process(in <-chan int) (<-chan int, <-chan error) {
out := make(chan int)
errc := make(chan error, 1)
go func() {
defer close(out)
for n := range in {
ans, err := fetchAnswer(n)
if err != nil {
errc <- err // return with error
return
}
out <- ans
}
errc <- nil // return with nil
}()
return out, errc
}
② Result型でまとめて返す
type Result struct {
answer int
err error
}
func calculate(in <-chan int) <-chan Result {
out := make(chan Result)
go func() {
defer close(out)
for n := range in {
ans, err := fetchAnswer(n)
out <- Result{ans, err}
}
}()
return out
}
③ エラー専用チャネルに流す
func calculate(in <-chan int, errc chan<- error) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
ans, err := fetchAnswer(n)
if err == nil {
out <- ans
} else {
errc <- err
}
}
}()
return out
}
3パターンを状況に応じて使い分けられるよう、それぞれの意図が明確に整理されている。
チャネルの「落とし穴」が丁寧に整理されている
チャネルの章では、よくハマるポイントが明示されている:
- nilチャネルへの送受信は永久ブロックする。
close(nil channel)はpanicを起こす - 閉じたチャネルへの送信もpanic。チャネルは一度しか閉じられない
- バッファ付きチャネルを閉じた後も、バッファ内の値は読み出せる(
ok=trueが返り続け、空になるとok=false)
stream := make(chan int, 1)
stream <- 11
close(stream)
val, ok := <-stream
fmt.Println(val, ok) // 11 true
val, ok = <-stream
fmt.Println(val, ok) // 0 false
この挙動を正確に把握していないと、パイプラインのシャットダウン処理でバグが出やすい。
WaitGroup.Goなど比較的新しいAPIにも言及
sync.WaitGroup.Go メソッドにも触れている。Add(1) + go func() + defer Done() の3点セットを1行にまとめられる:
var wg sync.WaitGroup
wg.Go(func() {
fmt.Println("worker 1")
})
wg.Go(func() {
fmt.Println("worker 2")
})
wg.Wait()
Add のカウント書き忘れによるバグを構造的に防げる点で実用的だ。
select文の3つの用途
select文については、以下の3用途が具体的なコードで示されている:
- パイプラインのデータフロー管理(複数チャネルのマージ)
- goroutineのキャンセル(cancelチャネルの監視)
- ノンブロッキング操作(
default節を使ったビジー判定)
キャンセル処理の実装は、後述の context パッケージとの比較文脈でも参照しやすい構成になっている。contextについては公式ドキュメントも合わせて参照すると理解が深まる。
Goroutines、チャネル、select、パイプライン、Context、Mutex、セマフォ、アトミック操作、テスト、スケジューリング、診断まで、Goの並行処理に関わるほぼ全域をカバーした内容だ。「実装前に頭を整理したい」「コードレビューの基準を確認したい」といった場面で繰り返し参照できる構成になっており、Go並行処理の実質的なチートシートとして手元に置いておく価値がある。
詳細はGo concurrency distilledを参照していただきたい。