🔄

【Go】Go 1.25で追加されたWaitGroup.Goの紹介

に公開

はじめに

2025年08月12日、Go1.25がリリースされました。
様々なパッケージやツールチェーンなどが追加・更新されました。
今回はその中でも、syncパッケージに追加された WaitGroup.Go() の使い方やメリットを、Go 1.24までの書き方との違いを比較しながら紹介します!

この記事はSpeakerDeckにも公開しております!
興味がある方は合わせてご覧いただけますと嬉しいです!

この記事でわかること

  • Go 1.24までのGoroutine
  • Go 1.25からのGoroutine
  • WaitGroup.Go() の使い方と役割

環境

  • go version go1.25.0 darwin/arm64
  • macOS Sequoia バージョン15.6.1

Go 1.24までのGoroutine

Goの特徴の一つに goroutine があります。
goroutine とは「Goランタイムで管理される軽量スレッド」のことであり、並行処理を実現する仕組みのことを指します。
go というキーワードを関数の前につけることで、簡単に実装することができます!

さて、Go 1.24までのGoroutineは例えば以下のように実装するのが一般的でした。

main.go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := range 10 {
        wg.Add(1)
        go func ()  {
            defer wg.Done()
            fmt.Println(i)
        }()
    }
    wg.Wait()
}

goroutine を起動するためには、以下の要素を使う必要があります。

  • sync.WaitGroup
    • syncパッケージ は複数の goroutine 間でのデータ共有や処理調整を安全に行うための機能を提供
    • WaitGroupsyncパッケージ の構造体
  • WaitGroup.Add()
    • Goroutineカウンタを指定した数の分だけ増やす
  • WaitGroup.Done()
    • Goroutineカウンタを一つ減らす
  • WaitGroup.Wait()
    • カウンタが0になるまで待つ

これを実行すると、以下のような結果になります。

ターミナル
# go run main.go
0
5
6
9
4
7
1
8
2
3

どの goroutine がいつ実行されるかはランダムなので、同じプログラムを実行しても同じ順序とは限りません。

Go 1.25からのGoroutine

Go 1.24までのGoroutineは、以下の要素を使う必要がありました。

  • sync.WaitGroup
  • WaitGroup.Add()
  • WaitGroup.Done()
  • WaitGroup.Wait()

これをGo 1.25で追加された WaitGroup.Go() を使うことで簡略化することができます。
先ほどの実装を書き換えてみます。

main.go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for i := range 10 {
        wg.Go(func() {
            fmt.Println(i)
        })
    }
    wg.Wait()
}

WaitGroup.Go() を使うことで、

  • WaitGroup.Add()
  • WaitGroup.Done()

これらのメソッドを明示的に書く必要がなくなりました。
実は WaitGroup.Go() は、内部で二つのメソッドを呼んでいるのです!

func (wg *WaitGroup) Go(f func()) {
    wg.Add(1)
    go func() {
        defer wg.Done()
        f()
    }()
}

これを実行すると、以下のような結果になります。

ターミナル
# go run main.go
0
8
1
6
5
4
3
2
7
9

同じくランダム実行なので、同じプログラムを実行しても同じ順序とは限りません。

WaitGroup.Go()のメリット

やっていること自体は従来と変わらないですが、WaitGroup.Go() を使うことで得られるメリットは「ヒューマンエラーの軽減」が大きいです。

  • Add(1) を書き忘れて Wait() がいつまでも待機しデッドロックが発生
  • Done() を書き忘れて同じくデッドロック
  • Add()goroutine の中や遅いタイミングで呼び出し、不整合が起こる
    このような問題を防ぎやすくなるので、簡潔かつ安全に goroutine を起動できます。

まとめ

今回は Go 1.25 で追加された WaitGroup.Go() を紹介しました。

  • Go 1.24 までは Add()goroutine 起動 → defer Done() という定型コードを書く必要あり
  • Go 1.25 からは WaitGroup.Go() を使うことで、これらをワンライナーで書ける
  • 内部的には従来どおり Add()Done() を呼んでいるため動作は変わらないが、可読性を高めるだけではなく書き忘れによるデッドロックや呼び出し順序の不整合といったヒューマンエラーを防ぎやすくなった

つまり WaitGroup.Go() は従来の機能を簡潔・安全に書ける新しい書き方 です。
今後は積極的に活用していくと、より読みやすく安全な並行処理コードを書くことができるでしょう。

参考

Discussion