テック技報最新号へ →
コード

Go: wg.Add はゴルーチンを起動する前に呼ぶ(Go 1.25 以降は wg.Go)

wg.Add(1) をゴルーチンの中で呼ぶと、wg.Wait() が先に走ってカウンタ 0 を見て、作業が終わる前に戻ってしまう。Add は go 文の前に呼ぶか、Go 1.25 の wg.Go(f) を使う。

@amitsinha__07 · 投稿 2026-10-28
#go#concurrency#sync

ダメな書き方

go
var wg sync.WaitGroup
for _, job := range jobs {
    go func() {
        wg.Add(1) // ← ここだと Wait に間に合わないことがある
        defer wg.Done()
        process(job)
    }()
}
wg.Wait() // まだ Add されていなければ即座に戻る

ゴルーチンがいつ走り出すかはスケジューラ次第なので、手元では動いて本番でたまに処理が抜ける、という再現しにくいバグになる。

正しい書き方

go
// 従来: 起動する側で Add してから go
for _, job := range jobs {
    wg.Add(1)
    go func() {
        defer wg.Done()
        process(job)
    }()
}
wg.Wait()

// Go 1.25 以降: Add と Done をまとめてやってくれる
for _, job := range jobs {
    wg.Go(func() { process(job) })
}
wg.Wait()

注意点

  • Go 1.25 から go vet の waitgroup チェックが、ゴルーチン内での wg.Add を警告する。CI で go vet を回していれば検出できる。
  • wg.Go に渡す関数は panic しないこと(panic はそのままプロセスを落とす)。エラーを集めたいなら golang.org/x/sync/errgroup の g.Go を使う。
  • Go 1.22 以降はループ変数がイテレーションごとに作られるので、上の例で job を引数で渡し直す必要はない。

出典

X の元の投稿を開く