errgroup.Group适用于需并发执行多任务且任意出错即取消其余任务并返回首个错误的场景;不适用完全独立任务、需限流(须配合信号量)或忽略错误传播的情况。

errgroup.Group 什么时候该用,什么时候不该用
当你要并发执行多个任务,并且希望任意一个出错就立即取消其余所有任务、同时收集第一个错误时,errgroup.Group 是标准解法。它不是万能的:如果任务之间完全独立、无需错误传播或取消联动,直接用 sync.WaitGroup + 单独错误处理更轻量;如果需要控制并发数(比如最多同时跑 5 个 HTTP 请求),必须配合 semaphore 或带缓冲 channel,errgroup.Group 本身不提供限流能力。
基础用法:Go 方法必须返回 error,且不能忽略返回值
errgroup.Group.Go 接收的函数签名必须是 func() error。常见错误是传入无返回值函数,或在 goroutine 内部吞掉错误:
eg := &errgroup.Group{}
eg.Go(func() error {
resp, err := http.Get("https://api.example.com/users")
if err != nil {
return err // ✅ 必须 return,不能 log 后 return nil
}
defer resp.Body.Close()
// 处理 resp...
return nil
})
if err := eg.Wait(); err != nil {
log.Fatal(err) // ✅ 错误在这里统一捕获
}- 函数体末尾没
return nil会导致编译失败(缺少返回值) - 用
log.Printf打印错误但仍return nil,会让eg.Wait()返回nil,掩盖问题 - 多个任务都出错时,
eg.Wait()只返回第一个非nil错误,其余错误被丢弃
带上下文取消:用 WithContext 初始化,别自己 new
要支持超时或手动取消,必须用 errgroup.WithContext(ctx) 初始化,而不是 &errgroup.Group{}。否则 Go 启动的 goroutine 不会响应 context 取消信号:
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
eg, ctx := errgroup.WithContext(ctx)
<p>eg.Go(func() error {
select {
case <-time.After(5 * time.Second):
return errors.New("timeout in task")
case <-ctx.Done(): // ✅ 能收到取消信号
return ctx.Err()
}
})</p><p>if err := eg.Wait(); err != nil {
// 可能是 context.DeadlineExceeded 或其他 error
fmt.Println(err)
}-
errgroup.WithContext返回的ctx是派生上下文,所有Go函数应使用它做 I/O 或 select 判断 - 手动调用
cancel()后,未完成的Go函数会在下一次ctx.Done()检查时退出,但不会强制 kill goroutine - 若任务内部有阻塞系统调用(如无 timeout 的
http.Get),需额外传入带 timeout 的http.Client
并发限制:errgroup 本身不控制并发数,得自己加信号量
errgroup.Group 默认是“全量并发”,100 个任务会起 100 个 goroutine。要限制最大并发数(比如防止打爆下游服务),必须外层加限流:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
sem := make(chan struct{}, 5) // 最多 5 个并发
eg := &errgroup.Group{}
<p>for _, url := range urls {
url := url // 避免循环变量复用
eg.Go(func() error {
sem <- struct{}{} // 获取令牌
defer func() { <-sem }() // 释放令牌</p><pre class='brush:php;toolbar:false;'> resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
})}
if err := eg.Wait(); err != nil { // ... }
- 信号量 channel 容量即最大并发数,容量为 0 会死锁
- 务必用
defer保证令牌释放,否则后续任务永远卡住 - 不要在
Go函数外做sem <- struct{}{},那会阻塞主 goroutine
真正麻烦的是组合场景:既要错误传播、又要上下文取消、还要并发限制——这三者得同时满足,漏掉任一环节都可能让程序卡死或行为异常。

















