|
| 1 | +--- |
| 2 | +title: "Go1.25リリース連載:sync" |
| 3 | +date: 2025/08/01 00:00:00 |
| 4 | +postid: a |
| 5 | +tag: |
| 6 | + - Go |
| 7 | + - Go1.25 |
| 8 | +category: |
| 9 | + - Programming |
| 10 | +thumbnail: /images/20250801a/thumbnail.jpg |
| 11 | +author: 辻大志郎 |
| 12 | +lede: "Go 1.25のマイナーアップデートから sync パッケージを取り上げて紹介します。" |
| 13 | +--- |
| 14 | +<img src="/images/20250801a/top.jpg" alt="" width="1024" height="599"> |
| 15 | + |
| 16 | +# はじめに |
| 17 | + |
| 18 | +製造エネルギー事業部の辻です。[Go1.25 リリース連載](/articles/20250730a/) の3本目です。 |
| 19 | + |
| 20 | +マイナーアップデートから [`sync`](https://pkg.go.dev/[email protected]) パッケージを取り上げて紹介します。 |
| 21 | + |
| 22 | +# アップデートサマリ |
| 23 | + |
| 24 | +> The new method on WaitGroup, WaitGroup.Go, makes the common pattern of creating and counting goroutines more convenient. |
| 25 | +
|
| 26 | +https://go.dev/doc/go1.25#syncpkgsync |
| 27 | + |
| 28 | +`sync` パッケージに新しく `WaitGroup.Go()` というメソッドが追加されました。ゴルーチンの生成と完了を集計する一般的なパターンを便利に記述できるようになりました。 |
| 29 | + |
| 30 | +<div class="note info" style="background: #e5f8e2; padding:16px; margin:24px 12px; border-radius:8px;"><span class="fa fa-fw fa-check-circle"></span> |
| 31 | + |
| 32 | +このメソッドを利用すると、たとえば以下のような並行処理のコードをより簡潔に記述できるようになります。 |
| 33 | + |
| 34 | +```go Go1.24以前 |
| 35 | + var wg sync.WaitGroup |
| 36 | + tasks := []string{"task 1", "task 2", "task 3"} |
| 37 | + |
| 38 | + for _, t := range tasks { |
| 39 | + // 1. ゴルーチン起動前にカウンタをインクリメント |
| 40 | + wg.Add(1) |
| 41 | + |
| 42 | + go func(task string) { |
| 43 | + // 2. ゴルーチン終了時にカウンタをデクリメント |
| 44 | + defer wg.Done() |
| 45 | + fmt.Printf("Executing %s\n", task) |
| 46 | + }(t) |
| 47 | + } |
| 48 | + |
| 49 | + wg.Wait() |
| 50 | + fmt.Println("All tasks completed.") |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +```go Go1.25 |
| 55 | + var wg sync.WaitGroup |
| 56 | + tasks := []string{"task 1", "task 2", "task 3"} |
| 57 | + |
| 58 | + for _, t := range tasks { |
| 59 | + // wg.Go() を呼び出すだけでOK |
| 60 | + wg.Go(func() { |
| 61 | + fmt.Printf("Executing %s\n", t) |
| 62 | + }) |
| 63 | + } |
| 64 | + |
| 65 | + wg.Wait() |
| 66 | + fmt.Println("All tasks completed.") |
| 67 | +``` |
| 68 | + |
| 69 | +</div> |
| 70 | + |
| 71 | +なお、`go vet` の静的解析で ` sync.WaitGroup.Add()` のミスパターンの検知ができるようになりました。こちらは真野さんの記事で解説予定ですので本記事では割愛します。 |
| 72 | + |
| 73 | +# 背景など |
| 74 | + |
| 75 | +関連するIssueは [#63796:sync: add WaitGroup.Go](https://github.com/golang/go/issues/63796) です。 |
| 76 | + |
| 77 | +Go の並行処理の実装パターンとして以下のようなコードがよく見られます。 |
| 78 | + |
| 79 | +```go |
| 80 | + var wg sync.WaitGroup |
| 81 | + for i := 1; i <= 5; i++ { |
| 82 | + // i := i この変数の確保は Go1.22 で不要になっています |
| 83 | + wg.Add(1) |
| 84 | + go func() { |
| 85 | + defer wg.Done() |
| 86 | + work(i) |
| 87 | + }() |
| 88 | + } |
| 89 | + wg.Wait() |
| 90 | +``` |
| 91 | + |
| 92 | +実装として `wg.Add(1)` や `defer wg.Done()` は必要ですが、忘れがちなミスパターンの一つでもあります。 |
| 93 | + |
| 94 | +<div class="note info" style="background: #e5f8e2; padding:16px; margin:24px 12px; border-radius:8px;"><span class="fa fa-fw fa-check-circle"></span> |
| 95 | + |
| 96 | +なおGo1.21以前はループ変数をクロージャで扱う際にコード内で明示的に `i := i` などしてメモリ確保が必要でしたが、Go1.22以降はGo側でよしなにメモリを確保できるようになっており、アプリ開発者が `i := i` とする実装は不要になっています。 |
| 97 | + |
| 98 | +</div> |
| 99 | + |
| 100 | +今回のリリースされる `WaitGroup.Go()` メソッドで、上記のようなパターンをカプセル化し、Goの並行処理をより簡潔に実装できるようになります。以下が `WaitGroup.Go()` メソッドの実装です。 |
| 101 | + |
| 102 | +```go |
| 103 | +func (wg *WaitGroup) Go(f func()) { |
| 104 | + wg.Add(1) |
| 105 | + go func() { |
| 106 | + defer wg.Done() |
| 107 | + f() |
| 108 | + }() |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +https://cs.opensource.google/go/go/+/release-branch.go1.25:src/sync/waitgroup.go;l=235-241 |
| 113 | + |
| 114 | +# errgroup との違いは? |
| 115 | + |
| 116 | +Goの並行処理の実装をするときに準標準ライブラリである [`golang.org/x/sync/errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup) パッケージを利用されている方もいるのではないか、と思います。私も `errgroup` にはよくお世話になっています。 |
| 117 | + |
| 118 | +`errgroup` パッケージは標準ライブラリである `sync` パッケージに依存しています。`sync` パッケージはより低レイヤーなパッケージと言えますが、並行処理を扱う上で以下のような機能的な違いがあります。 |
| 119 | + |
| 120 | +|特徴|sync.WaitGroup (と Go メソッド)|golang.org/x/sync/errgroup| |
| 121 | +|:----|:----|:----| |
| 122 | +|主な目的|全てのゴルーチンの完了を待機|全てのゴルーチンの完了を待機 + 最初のエラーを伝播 + キャンセル| |
| 123 | +|エラー処理|Goメソッドには存在しない。必要に応じて個々のゴルーチンで実装、呼び出し元との連携はチャネル等を利用|最初のエラーを自動的に捕捉・伝播| |
| 124 | +|キャンセル|なし| `context.Context` と連携し、エラー発生時に他のゴルーチンをキャンセル| |
| 125 | +|同時実行数制御|チャネル等で実装が必要| [`SetLimit()`](https://pkg.go.dev/golang.org/x/sync/errgroup#Group.SetLimit) で指定可能| |
| 126 | + |
| 127 | +上記からもわかるように今回導入される `WaitGroup.Go()` メソッドが既存の `errgroup` パッケージを代替するものか?というとそうではなさそうです。 |
| 128 | + |
| 129 | +高機能なAPIを提供するという意味で `errgroup` パッケージは今後も使われるのではないか、と思います。一方、シンプルな機能で十分な場合や、ゴルーチンで発生するすべてのエラーを収集したい場合は `WaitGroup.Go()` とチャネルを利用した実装が必要でしょう。 |
| 130 | + |
| 131 | +# まとめ |
| 132 | + |
| 133 | +Go 1.25で `sync` パッケージの `WaitGroup.Go()` メソッドを紹介しました。 |
| 134 | + |
| 135 | +エラーハンドリングやコンテキストの伝播、同時実行数制御が必要な場合では引き続き `errgroup` が便利ですが、シンプルな処理であれば `WaitGroup.Go()` が有用になるでしょう。 |
0 commit comments