errgroup:釋放 Go 的并發能力
errgroup 是 Go 官方擴展庫 golang.org/x/sync 提供的并發控制工具。在 sync.WaitGroup 的基礎上,它新增了錯誤傳播、上下文取消及并發限制等能力,為編寫健壯的并發程序提供了更高層的抽象。

errgroup 的優勢
與 sync.WaitGroup 相比,errgroup.Group 具備以下特性:
- 集中錯誤處理:首個非 nil 錯誤會被捕獲并由 Wait 返回,同時自動取消其他協程。
- 上下文取消:通過 errgroup.WithContext 與 context.Context 集成,可在任一協程出錯時主動取消剩余任務,避免資源浪費。
- 簡化代碼:開發者無需手動維護錯誤狀態或同步邏輯,樣板代碼顯著減少。
- 并發數量限制:SetLimit 方法可限制同時運行的 goroutine 數量,防止系統過載。
回顧 sync.WaitGroup
在介紹 errgroup.Group 之前,讓我們先回顧一下 sync.WaitGroup 的用法。
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
urls := []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
var (
wg sync.WaitGroup
err error
)
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, e := http.Get(u)
if e != nil {
err = e
return
}
defer resp.Body.Close()
fmt.Printf("fetch url %s status %s\n", u, resp.Status)
}(url)
}
wg.Wait()
if err != nil {
fmt.Printf("error: %v\n", err)
}
}執行結果:
$ go run examples/main.go
fetch url http://www.google.com/ status 200 OK
fetch url http://www.golang.org/ status 200 OK
Error: Get "http://www.somestupidname.com/": dial tcp: lookup www.somestupidname.com: no such hostsync.WaitGroup 的典型用法如下:
var wg sync.WaitGroup
for ... {
wg.Add(1)
go func() {
defer wg.Done()
// do something
}()
}
wg.Wait()errgroup 基本用法
errgroup.Group 的使用模式類似于sync.WaitGroup:
package main
import (
"fmt"
"net/http"
"golang.org/x/sync/errgroup"
)
func main() {
urls := []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
var g errgroup.Group
for _, url := range urls {
u := url
g.Go(func() error {
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Printf("fetch url %s status %s\n", u, resp.Status)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("error: %v\n", err)
}
}執行結果:
$ go run examples/main.go
fetch url http://www.google.com/ status 200 OK
fetch url http://www.golang.org/ status 200 OK
Error: Get "http://www.somestupidname.com/": dial tcp: lookup www.somestupidname.com: no such host上下文取消
errgroup 提供 errgroup.WithContext 來添加取消功能:
package main
import (
"context"
"fmt"
"net/http"
"sync"
"golang.org/x/sync/errgroup"
)
func main() {
urls := []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
g, ctx := errgroup.WithContext(context.Background())
var result sync.Map
for _, url := range urls {
u := url
g.Go(func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
result.Store(u, resp.Status)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("error: %v\n", err)
}
result.Range(func(k, v any) bool {
fmt.Printf("fetch url %s status %s\n", k, v)
return true
})
}執行結果:
$ go run examples/withcontext/main.go
Error: Get "http://www.somestupidname.com/": dial tcp: lookup www.somestupidname.com: no such host
fetch url http://www.google.com/ status 200 OK如示例所示,somestupidname.com 請求失敗后,其余協程自動取消,節省了不必要的網絡開銷。
限制并發數量
errgroup 提供 errgroup.SetLimit 來限制并發執行的 goroutines 數量:
package main
import (
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
var g errgroup.Group
g.SetLimit(3) // 同時僅允許 3 個 goroutine
for i := 1; i <= 10; i++ {
id := i
g.Go(func() error {
fmt.Printf("goroutine %d start\n", id)
time.Sleep(2 * time.Second)
fmt.Printf("goroutine %d done\n", id)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Printf("error: %v\n", err)
}
fmt.Println("all goroutines complete")
}執行結果:
$ go run examples/main.go
Goroutine 3 is starting
Goroutine 1 is starting
Goroutine 2 is starting
Goroutine 2 is done
Goroutine 1 is done
Goroutine 5 is starting
Goroutine 3 is done
Goroutine 6 is starting
Goroutine 4 is starting
Goroutine 6 is done
Goroutine 5 is done
Goroutine 8 is starting
Goroutine 4 is done
Goroutine 7 is starting
Goroutine 9 is starting
Goroutine 9 is done
Goroutine 8 is done
Goroutine 10 is starting
Goroutine 7 is done
Goroutine 10 is done
All goroutines complete.TryGo:嘗試啟動任務
errgroup 提供errgroup.TryGo 來嘗試啟動一個任務,這需要與 errgroup.SetLimit 一起使用。
TryGo 在達到并發上限時立即返回 false,避免阻塞:
package main
import (
"fmt"
"time"
"golang.org/x/sync/errgroup"
)
func main() {
var g errgroup.Group
g.SetLimit(3)
for i := 1; i <= 10; i++ {
id := i
if g.TryGo(func() error {
fmt.Printf("goroutine %d start\n", id)
time.Sleep(2 * time.Second)
fmt.Printf("goroutine %d done\n", id)
return nil
}) {
fmt.Printf("goroutine %d accepted\n", id)
} else {
fmt.Printf("goroutine %d rejected (limit reached)\n", id)
}
}
_ = g.Wait()
}執行結果:
$ go run examples/main.go
Goroutine 1 started successfully
Goroutine 1 is starting
Goroutine 2 is starting
Goroutine 2 started successfully
Goroutine 3 started successfully
Goroutine 4 could not start (limit reached)
Goroutine 5 could not start (limit reached)
Goroutine 6 could not start (limit reached)
Goroutine 7 could not start (limit reached)
Goroutine 8 could not start (limit reached)
Goroutine 9 could not start (limit reached)
Goroutine 10 could not start (limit reached)
Goroutine 3 is starting
Goroutine 2 is done
Goroutine 3 is done
Goroutine 1 is done
All goroutines complete.結論
errgroup 在 sync.WaitGroup 之上提供了錯誤傳播、上下文取消與并發控制功能,簡化了復雜并發場景下的錯誤處理與資源管理。通過合理使用 WithContext、SetLimit 及 TryGo,開發者能更高效地編寫可維護、穩健的并發代碼。





























