【Go言語】【完結編】`errgroup`:並行処理の「めんどくさい」を全部やってくれる魔法のツール

【完結編】errgroup:並行処理の「めんどくさい」を全部やってくれる魔法のツール

これまで、sync.WaitGroup を使い、チャネルでエラーを運び、select で停止信号を監視する……という実装をしてきました。勉強には最適ですが、本音を言えば 「もっと楽に書きたい」 ですよね?

そこで登場するのが、Go公式の準標準ライブラリ golang.org/x/sync/errgroup です。

これを使うと、前回の記事で書いた何十行ものコードが、驚くほど短くなります。

なぜ errgroup なのか?

errgroup は、以下の「並行処理あるある」を自動化してくれます。

  1. WaitGroupの管理不要: AddDone を自分で書かなくていい。
  2. エラーの自動伝播: 誰か1人がエラーを出したら、それをメインに報告してくれる。
  3. Contextの連動: 誰か1人が失敗したら、他の全員に自動でキャンセル信号(Context)を送る
  4. 並行数の制限: ワーカープールを自作しなくても、メソッド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 を導入して、コードを劇的に短くしてみてください!