
- 【完結編】errgroup:並行処理の「めんどくさい」を全部やってくれる魔法のツール
- なぜ errgroup なのか?
- 準備
- 実装コード:驚きの短さ
- コードの比較:何が良くなった?
- まとめ:使い分けの基準
【完結編】errgroup:並行処理の「めんどくさい」を全部やってくれる魔法のツール
これまで、sync.WaitGroup を使い、チャネルでエラーを運び、select で停止信号を監視する……という実装をしてきました。勉強には最適ですが、本音を言えば 「もっと楽に書きたい」 ですよね?
そこで登場するのが、Go公式の準標準ライブラリ golang.org/x/sync/errgroup です。
これを使うと、前回の記事で書いた何十行ものコードが、驚くほど短くなります。
なぜ errgroup なのか?
errgroup は、以下の「並行処理あるある」を自動化してくれます。
- WaitGroupの管理不要:
AddやDoneを自分で書かなくていい。 - エラーの自動伝播: 誰か1人がエラーを出したら、それをメインに報告してくれる。
- Contextの連動: 誰か1人が失敗したら、他の全員に自動でキャンセル信号(Context)を送る。
- 並行数の制限: ワーカープールを自作しなくても、メソッド1つで同時実行数を制限できる。
準備
標準ライブラリではないので、インストールが必要です。
go get golang.org/x/sync/errgroup
実装コード:驚きの短さ
前回の「3つのワーカーで処理し、エラーがあったら止まる」という要件を errgroup で書き直してみます。
package main import ( "context" "fmt" "time" "golang.org/x/sync/errgroup" ) func process(id int) error { fmt.Printf("Job %d 開始\n", id) time.Sleep(500 * time.Millisecond) // 処理のフリ // 偶数は失敗させるシミュレーション if id%2 == 0 { return fmt.Errorf("Job %d でエラー発生!", id) } fmt.Printf("Job %d 完了\n", id) return nil } func main() { // コンテキスト付きのerrgroupを作成 // g: グループ管理オブジェクト // ctx: 誰かが失敗した瞬間にキャンセルされるコンテキスト g, ctx := errgroup.WithContext(context.Background()) // ★ここが最強機能:同時実行数を「3」に制限(ワーカープールの代替) g.SetLimit(3) jobList := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} for _, jobID := range jobList { // 変数のキャプチャ(Go 1.22以降なら不要だが念のため) id := jobID // g.Go でゴルーチンを起動 // SetLimitしているので、3つ以上は同時に動かず待機してくれる g.Go(func() error { // Contextのキャンセルチェック // (他の誰かがエラーを出したら ctx.Done() が閉じる) select { case <-ctx.Done(): return ctx.Err() default: // 通常処理 return process(id) } }) } // 全部の処理が終わるか、誰かがエラーを出すまで待つ if err := g.Wait(); err != nil { fmt.Printf("\n【失敗】エラーが発生しました: %v\n", err) } else { fmt.Println("\n【成功】すべての処理が完了しました") } }
コードの比較:何が良くなった?
1. ワーカープールを作る必要がない
g.SetLimit(3) と書くだけで、内部的にセマフォを使った同時実行制御が行われます。わざわざ for select でワーカー関数を作る必要がなくなりました。
2. エラー処理が単純
return err するだけで、g.Wait() がそのエラーを受け取ってくれます。Result構造体を作ってチャネルに流す手間が消えました。
3. キャンセル処理が自動
errgroup.WithContext を使っているため、ゴルーチンの1つが return err すると、自動的に ctx がキャンセルされます。他のゴルーチンは ctx.Done() を検知して即座に撤退できます。
まとめ:使い分けの基準
これまでの知識をどう使い分けるべきでしょうか?
| パターン | おすすめの場面 |
|---|---|
| for select | 最も基本。無限ループで常駐する処理や、複雑なチャネル制御が必要な場合。 |
| Worker Pool | 基本の勉強、あるいは errgroup では制御しきれない細かい挙動(例:エラーでも止めずに集計したい等)が必要な場合。 |
| errgroup | 実務の9割はこれ。 API並列リクエスト、バッチ処理など、「まとめてやって、結果を知りたい」場合。 |
これで、Goの並行処理における「基礎」から「実務レベルの最適解」までをマスターしました。
for select の仕組みを知っているからこそ、errgroup のありがたみが分かるはずです。
ぜひ、次回のプロジェクトでは errgroup を導入して、コードを劇的に短くしてみてください!