Go: errgroup.SetLimit で並列数に上限を付ける(自前のセマフォが要らない)
golang.org/x/sync/errgroup の g.SetLimit(n) を呼ぶと、実行中の goroutine が n 個に達した時点で g.Go がブロックする。「同時に n 個まで」と「最初のエラーで残りをキャンセル」を一度に書ける。
使う場面
URL の一覧を並列に取得する、ファイルを並列に処理するなど、件数が多くて全部を同時に走らせると相手や自分のリソースを食い潰すとき。バッファ付きチャネルのセマフォと sync.WaitGroup とエラー集約を自分で組み合わせていたなら、これで置き換えられる。
やり方
go
import (
"context"
"golang.org/x/sync/errgroup"
)
func fetchAll(ctx context.Context, urls []string) error {
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8) // 同時に動くのは 8 個まで
for _, u := range urls {
g.Go(func() error { // 8 個動いている間はここで待つ
return fetch(ctx, u) // ctx は最初のエラーでキャンセルされる
})
}
return g.Wait() // 最初に返ったエラー(なければ nil)
}- 待たずに諦めたいときは
g.TryGo(f)を使う。枠が空いていなければfalseを返し、関数は実行されない。 - Go 1.22 以降はループ変数がイテレーションごとに作られるので、
u := uのコピーは不要。
注意点
SetLimitは goroutine が動いている最中に呼ぶと panic する。g.Goを呼ぶ前に設定する。g.Go自体がブロックするので、呼び出し側のループも止まる。ループの中で別の重い処理をしているなら、その間ほかの枠は空かない。WithContextで作った ctx はWaitが返った時点でもキャンセルされる。Waitの後の処理には元の ctx を使う。- 1 件ずつのメモリやコストが重い処理(パスワードハッシュなど)の上限の考え方は Go: Argon2id のハッシュ計算はセマフォで同時実行数を絞る(ログイン集中でメモリが溢れるのを防ぐ) も参照。エラーを返さない並列処理なら Go: wg.Add はゴルーチンを起動する前に呼ぶ(Go 1.25 以降は wg.Go)。