在 Go 開發中,同步多個 Goroutine 的完成狀態常見兩大工具:標準庫 sync.WaitGroupgolang.org/x/sync/errgroup。本文從 API、錯誤處理、Context、併發控制 等面向比較兩者差異,並給出典型使用時機與程式碼範例。

一、核心概念

名稱 所屬套件 主要職責
WaitGroup sync (標準庫) 計數同步:等待一組 Goroutine 全部執行完畢
errgroup.Group golang.org/x/sync/errgroup WaitGroup + 收集第一個錯誤 + Context 取消 + 併發上限

二、基本用法

1. WaitGroup

1
2
3
4
5
6
7
8
9
10
11
var wg sync.WaitGroup
urls := []string{"/a", "/b", "/c"}

for _, u := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
fetch(u) // 忽略錯誤
}(u)
}
wg.Wait()

特性:

  • 僅負責計數,不傳遞錯誤。
  • 需手動 Add / Done 配對;漏呼叫易導致 deadlock。

2. errgroup.Group

1
2
3
4
5
6
7
8
9
10
11
12
13
ctx := context.Background()
eg, ctx := errgroup.WithContext(ctx)

for _, u := range urls {
u := u // capture
eg.Go(func() error {
return fetchWithCtx(ctx, u) // 若回傳 error → 取消其他 Goroutine
})
}

if err := eg.Wait(); err != nil {
log.Printf("fail: %v", err)
}

特性:

  • Go(func() error) 收集第一個 non-nil error 並回傳於 Wait()
  • 任何 Goroutine 回錯,ctx 即取消,其他 Goroutine 可感知停止。

三、差異比較

面向 WaitGroup errgroup.Group
錯誤收集 收第一個 non-nil error
Context 整合 WithContext 產生可取消 ctx
併發上限 無,需自行 semaphore errgroup.Group + parallelism (errgroup.WithContext 無限制;可搭配 semaphore 包)
API 簡潔度 低:需 Add/Done 高:自動 Add/DoneGo()
依賴 標準庫 外部套件 (x/sync)

小貼士:若僅需同步且忽略錯誤,WaitGroup 足矣。若需錯誤傳遞或提前取消,請選擇 errgroup

四、併發上限範例 (semaphore 搭配 errgroup)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eg, ctx := errgroup.WithContext(context.Background())
sem := make(chan struct{}, 5) // 同時最多 5 個

for _, u := range urls {
u := u
eg.Go(func() error {
sem <- struct{}{} // acquire
defer func() { <-sem }() // release
return fetchWithCtx(ctx, u)
})
}

if err := eg.Wait(); err != nil {
// handle first error
}

五、選用建議

場景 建議工具 原因
簡單並行計算,不關心錯誤 WaitGroup 最輕量、零依賴
需要錯誤即停、Context 取消 errgroup 簡單封裝、省去樣板碼
須限制同時併發量 errgroup + semaphore Go1.20 起可用 x/sync/semaphore
舊專案、極度追求依賴零化 WaitGroup 不額外引入模組

六、常見陷阱

  1. WaitGroup 計數不平AddDone 不對等 → 永遠卡在 Wait()
  2. errgroup 忽視 ctx:Goroutine 內部未使用傳入 ctx → 無法提早中止。
  3. 併發過高:無限開 Goroutine,導致記憶體/FD 耗盡;應設限。

七、結語

sync.WaitGrouperrgroup.Group 分別代表 最小同步原語高階錯誤協調工具。依需求選擇,搭配 Context 與併發控制,可讓你的 Goroutine 更加可控、可觀測。

記得:並發容易,協調不易。善用適合的工具,才能寫出優雅且健壯的並行程式!