errgroup.wait()默认只返回首个非nil错误,因设计目标是“快速失败”而非汇总;若需收集全部错误,须自行用带缓冲channel或sync.mutex+切片实现。

errgroup.Group 默认只返回第一个错误,不汇总全部;想收全错误,必须绕开 Wait() 的短路逻辑,自己用 channel 或锁收集。
为什么 Wait() 不返回所有错误
errgroup.Wait() 的设计目标是“任一失败即中止”,它内部用 errOnce.Do() 确保只记录第一个非 context.Canceled 错误,其余错误被静默丢弃。这不是 bug,是行为契约。
- 常见误判:看到
g.Wait()返回nil,就以为所有任务都成功 —— 实际可能有 goroutine panic 后没 recover,或返回了context.Canceled被忽略 - 返回值不是聚合结果,只是“首个可观测错误”;
context.DeadlineExceeded和context.Canceled通常不会触发短路,除非你手动检查并返回它们 - 如果子任务里用了
http.DefaultClient且没传ctx,超时控制就失效,Wait()可能永远卡住
想收全错误,必须放弃默认 Wait() 流程
两种可靠方式:用带缓冲的 chan error,或用 sync.Mutex + 切片。前者更符合 Go 的信道哲学,后者更易理解。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用 channel:声明
errs := make(chan error, N)(N 是最大并发数),每个 goroutine 执行完后errs ;主 goroutine 在 <code>wg.Wait()后close(errs),再遍历读取 - 用锁:定义
var mu sync.Mutex和var allErrors []error,每个 goroutine 结束前mu.Lock(); allErrors = append(allErrors, err); mu.Unlock() - 别用无缓冲 channel —— 一旦某个 goroutine 出错后卡在发送上,其他 goroutine 就无法结束,
wg.Wait()永远不返回 - 别在 goroutine 里直接
append全局切片而不加锁 —— 这是运行时 panic 高发点,Go 不保证 slice 底层数组扩容的并发安全
errgroup.WithContext 不等于“自动收集全部错误”
很多人以为只要用了 errgroup.WithContext,就能拿到全部错误。其实它只解决取消传播,不改变错误收集语义。
-
errgroup.WithContext(ctx)返回的Group仍遵循“首个错误即返回”规则;Wait()行为不变 - 若想等全部完成再汇总,必须配合外部错误收集机制(如上面的 channel 或锁),不能依赖
g.Wait()的返回值 - 子任务函数签名必须是
func() error,但你可以在这个函数里做两件事:调用真实逻辑 + 把错误发到外部 channel,然后return nil避免触发短路 - 示例写法:
g.Go(func() error { if err := doWork(); err != nil { errs
容易被忽略的三个实际细节
线上出问题,往往不是逻辑错,而是这些点没兜住:
- goroutine 里没检查
ctx.Err(),导致超时后还在发 HTTP 请求、读文件、占数据库连接 ——errgroup不会帮你 cancel IO,只负责 cancel goroutine 启动本身 - 闭包捕获循环变量(如
for i := range tasks { g.Go(func() error { use(i) }) }),结果所有 goroutine 都用最后一个i值 —— 必须显式拷贝:i := i - 某个子任务 panic 且没 recover,
errgroup会把它转成panic: xxx类型的 error,但如果你的错误处理只匹配特定类型(如*url.Error),就会漏掉
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










