Go 1.25で追加されたWaitGroup.Go()の使い方|並行処理がよりシンプルに
- 公開日
- カテゴリ:golang
- タグ:golang

Go 1.25 が 2025年8月12日に正式リリースになりました。
- Go 1.25 is released - The Go Programming Language
- Go 1.25 Release Notes - The Go Programming Language
その中で、sync パッケージにおいて、新たに WaitGroup.Go メソッド が追加されました。これによって、ゴルーチンを作成してカウントするという一般的な実装がより簡単になったので使ってみたいと思います。
contents
sync パッケージおよび WaitGroup
Go では、並行処理を簡単に扱える仕組みとして goroutine
(ゴルーチン)が用意されています。go
キーワードを使うだけで非同期実行が可能になる一方で、「すべての処理が終わるまで待つ」ためには明示的な制御が必要です。そこで登場するのが、sync
パッケージに含まれる WaitGroup
です。
sync パッケージは、「同期処理(Synchronization)」を目的とした標準ライブラリであり、Mutex(排他制御のためのロック)や Once(一度だけの実行を保証)、Cond(条件変化を通知・待機)といったスレッド間の調整に用いられる複数のツールが揃っています。その中でも WaitGroup
は特に汎用性が高く、複数の goroutine を安全に起動し、それらの完了を待つために用いられます。
具体的には、次のような場面で利用されます。
- 複数の API に同時アクセスして結果を待ちたいとき
- バッチ処理やファイル読み込みなどを並行で実行し、すべてが終わるのを確認してから次に進みたいとき
- goroutine の実行がメイン処理より遅延することがあり、プログラムの早期終了を防ぎたいとき
- 非同期に処理を書いたつもりが、実はその処理が完了する前にプログラムが終了していた……というのは、Go あるあるのひとつ。
これまでは、以下のような典型的な構文が使われてきました。
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// 非同期処理
}()
wg.Wait()
このように、Add()
→ go
→ Done()
→ Wait()
という 4 つの要素を組み合わせることで、並行処理を安全に完了まで管理することができます。一方で、Add()
と Done()
の書き忘れや位置の誤りによって意図しないブロックやデッドロックが発生することも少なくありません。
今回の Go 1.25 では、これらの一連の流れをより安全かつ簡潔に扱うための新機能として、WaitGroup
に Go()
メソッドが追加されました。次のセクションでは、この Go()
メソッドを用いた新しい記述方法と、従来の書き方との比較を通じてその利便性を確認していきます。
従来の書き方(Go 1.24まで)
Go 1.24 までの WaitGroup
を用いた実装では、非同期処理を開始するたびに Add(1)
を呼び出し、処理が終了したタイミングで Done()
を明示的に呼ぶ必要がありました。Add()
と Done()
の対を適切に記述しないと、goroutine の終了を待てなかったり(Add()
記述漏れ)、逆に Wait()
が永久にブロックされたり(Done()
記述漏れ)と、意図しない挙動を引き起こす原因にもなります。
以下は、複数の URL に対して並列で HTTP リクエストを送信し、そのステータスコードを出力するサンプルコードです。goroutine を安全に制御するための最小構成として、WaitGroup
を実装しています。
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
var wg sync.WaitGroup
urls := []string{
"https://www.google.com",
"https://www.example.com",
"https://www.github.com",
}
for _, url := range urls {
u := url
wg.Add(1)
go func() {
defer wg.Done()
resp, err := http.Get(u)
if err != nil {
fmt.Printf("Failed to fetch %s: %v\n", u, err)
return
}
defer resp.Body.Close()
fmt.Printf("Fetched %s: %d\n", u, resp.StatusCode)
}()
}
wg.Wait()
fmt.Println("All fetches completed.")
}
このように、Add()
→ go
→ defer Done()
→ Wait()
という 4 ステップを毎回手動で記述する必要があり、簡潔さや記述ミスの観点ではやや冗長でした。次に紹介する Go 1.25 の Go()
メソッドでは、こうした煩雑さが解消されています。
Go 1.25で追加されたGo()メソッド
Go 1.25 では、sync.WaitGroup
に新たに Go()
メソッドが追加されました。これにより、従来必要だった Add()
・go
・defer Done()
の3ステップを一括でカプセル化できるようになり、goroutine を使った並列処理の記述が格段に簡潔になりました。
新たに追加された Go()
メソッドは、次のように使います。
wg.Go(func() {
// goroutine 内で行いたい処理
})
この Go()
メソッドは、内部で以下の処理を自動的に行ってくれます。
wg.Add(1)
を呼び出す- 新しい goroutine を起動する
- 処理の終了時に
wg.Done()
を実行する
そのため、開発者は goroutine の中に記述した処理の内容だけに集中すればよくなり、Add()
や Done()
の書き忘れや順序ミスによるバグのリスクも軽減されます。
以下は、Go 1.25 の Go()
メソッドを使って書き換えたコードです。処理内容は先ほどの Go 1.24 のコードと同じで、複数の URL に対して並列で HTTP リクエストを行っていますが、記述が非常にシンプルになっていることがわかります。
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
var wg sync.WaitGroup
urls := []string{
"https://www.google.com",
"https://www.example.com",
"https://www.github.com",
}
for _, url := range urls {
u := url
wg.Go(func() {
resp, err := http.Get(u)
if err != nil {
fmt.Printf("Failed to fetch %s: %v\n", u, err)
return
}
defer resp.Body.Close()
fmt.Printf("Fetched %s: %d\n", u, resp.StatusCode)
})
}
wg.Wait()
fmt.Println("All fetches completed.")
}
このように、goroutine の管理に必要な記述を一行にまとめられることで、可読性・保守性の両面で大きなメリットが得られます。次のセクションでは、Go 1.24 の従来構文と Go 1.25 の Go()
メソッドを並べて比較し、その違いを整理していきます。
Go 1.24と1.25のコード比較と違い
ここでは、Go 1.24 までの従来の書き方と、Go 1.25 で追加された WaitGroup.Go()
メソッドを使った書き方を比較し、実際にどのような違いがあるのかを整理してみます。
両者は処理内容こそ同じですが、コードの構造や記述量、バグの起きにくさといった観点で違いがあり、特に Go()
メソッドによる記述の簡潔さが際立ちます。
以下は、比較ポイントを表にまとめたものです。
比較項目 | Go 1.24 | Go 1.25 |
---|---|---|
goroutine 実行方法 | go func() を使って自前で記述 | wg.Go(func()) を使用 |
Add の記述 | wg.Add(1) を毎回呼び出す必要がある | 自動で Add(1) を内部で呼び出す |
Done の記述 | defer wg.Done() を忘れずに書く必要がある | 自動で Done() を呼び出す |
コードの安全性 | Add/Done の書き漏れリスクあり | 書き漏れの心配がない |
記述の簡潔さ | 3ステップでやや冗長 | 1ステップで完結 |
このように、Go 1.25 で追加された Go()
メソッドを使うことで、goroutine を使った並行処理の基本パターンを1行に集約できるようになります。結果としてコードの見通しがよくなり、書き間違いによるバグのリスクも減らすことができます。
特に、複数の処理をループで並列実行するようなシーンでは記述が繰り返されやすいため、Go()
の恩恵は大きく感じられそうです。
まとめ
sync.WaitGroup
は Go における並行処理の基本ツールとして広く使われてきましたが、従来の Add()
と Done()
をセットで扱う書き方は、シンプルである一方でミスを誘発しやすい側面もありました。
Go 1.25 で追加された WaitGroup.Go()
メソッドは、このような典型パターンをより安全に、簡潔に記述できるようにしてくれる強力なアップデートです。処理単位をそのまま渡すだけで、goroutine の起動とカウント管理を一手に担ってくれるため、より直感的に並行処理を書くことができるようになりました。
Go 1.25 以降の環境であれば、積極的にこの Go()
メソッドを使っていき、可読性を高めながら Add()
や Done()
の記述漏れといったヒューマンエラーも防いでいきたいですね。