errgroup.group默认只返回首个非nil错误,必须用withcontext初始化并显式检查ctx.err()才能实现协同取消;直接new会致goroutine泄漏、超时失效、资源不释放。

errgroup.Group 不是“自动收集全部错误”的工具,它默认只返回第一个非 nil 错误;想统一管理并发任务,必须用 errgroup.WithContext 初始化,并在每个子任务里显式检查 ctx.Err(),否则 goroutine 会继续跑、资源不释放、超时失效。
为什么直接 new errgroup.Group{} 会出问题
手动写 &errgroup.Group{} 或 var g errgroup.Group 看似能跑通,但它完全不感知上下文:即使你给子任务传了带超时的 context.Context,g.Wait() 返回错误后,其他 goroutine 仍继续执行,HTTP 连接没关、文件句柄没 close、数据库连接卡在 read —— 典型的 goroutine 泄漏。
- 错误写法:
g := &errgroup.Group{}→ 后续所有g.Go()都无法被取消 - 正确写法:
g, ctx := errgroup.WithContext(context.WithTimeout(context.Background(), 5*time.Second)) -
ctx必须显式传给每个 IO 操作,比如http.NewRequestWithContext(ctx, ...),不能指望errgroup自己“生效”
Go() 和 GoContext() 到底该用哪个
二者语义完全不同,混用会导致取消失效:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
Go()接收func() error,不碰ctx,适合纯 CPU 计算(如 JSON 解析、base64 编码) -
GoContext()接收func(context.Context) error,内部监听ctx.Done()并提前退出,专为 HTTP/DB/File 等可取消 IO 设计 - 常见错误:
Go()里调time.Sleep(10 * time.Second),而ctx已超时,它仍会睡满 10 秒,Wait()卡住 - 正确姿势:
g.GoContext(func(ctx context.Context) error { return http.GetWithContext(ctx, url) })
errgroup.Wait() 返回 nil 就代表全成功?别信
g.Wait() 返回 nil 只说明「所有已启动的 goroutine 都结束了,且没报告非 nil 错误」。但它不管 panic、不验证逻辑是否真执行完、也不保证你返回的 error 是你预期的类型。
- 某个
Go()闭包里panic("db connection lost")且没recover,程序直接崩溃,根本走不到Wait() - 5 个任务都失败,
Wait()只返回第一个错误(比如context.DeadlineExceeded),后面 4 个sql.ErrNoRows全丢掉 - 务必在每个
Go()或GoContext()的闭包里显式处理并返回error,不要只log.Printf不返回
想拿到全部错误,不能靠 errgroup 默认行为
errgroup.Group 默认不聚合错误,g.Wait() 只暴露第一个非 nil 错误。如果需要排查多个失败点(比如批量调用 3 个服务,3 个都超时),得自己收集:
- 方案一:用切片 +
sync.Mutex手动收集(适合错误数少、不关心顺序) - 方案二:每个
Go()回调里用multierr.Append()累积错误(需引入go.uber.org/multierr) - 性能注意:高并发下频繁锁写错误切片有开销,但比丢错误强;调试时临时加
fmt.Printf更轻量 - 别用
errgroup.WithContext(context.Background())试图“等全部完成再汇总”,它不会改变短路行为,Wait()依然只返回第一个错误
最常被忽略的一点:errgroup 不是并发控制器,它不限制 goroutine 数量;循环启动几百个 g.Go(),容易打爆调度器或耗尽内存——该上 semaphore 或 worker pool 的地方,别硬套 errgroup。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










