errgroup不是自动收集全部错误的工具,只返回首个非nil错误且不中断goroutine;必须用WithContext初始化并显式检查ctx.Err()才能实现协同取消,否则会导致goroutine泄漏、超时失效和资源不释放。

errgroup 不是“自动收集所有错误”的工具,它只返回第一个非 nil 错误,且默认不中断正在运行的 goroutine —— 想实现「任一失败即整体取消 + 及时响应」,必须用 errgroup.WithContext 初始化,并在每个子任务中显式检查 ctx.Err()。
为什么直接 new(errgroup.Group) 会失控
用 var g errgroup.Group 或 &errgroup.Group{} 启动任务,g.Wait() 能拿到第一个错误,但其余 goroutine 仍会继续跑完。比如一个 HTTP 请求超时失败,其他几个还在发请求、占连接、写日志,甚至可能泄漏资源。
- 根本原因:
errgroup.Group内部靠context.CancelFunc传播中断信号,没传 context 就等于没装刹车 - 常见现象:日志里看到
context.DeadlineExceeded,但程序卡住几秒才退出 —— 那是其他 goroutine 还在 sleep 或阻塞 I/O - 正确初始化只有一种:
g, ctx := errgroup.WithContext(context.WithTimeout(context.Background(), 5*time.Second))
Go() 和 GoContext() 到底该选哪个
Go() 接收 func() error,完全不感知 context;GoContext() 接收 func(ctx context.Context) error,内部自动监听 ctx.Done() 并提前退出。选错会导致取消失效或资源泄漏。
- IO 类任务(
http.Get、db.Query、os.Open)→ 必须用GoContext(),并配合支持 context 的 API(如http.NewRequestWithContext(ctx, url)) - CPU 密集型短任务(JSON 解析、base64 编码)→ 可用
Go(),但得确保它真不阻塞;否则仍需手动加if ctx.Err() != nil { return ctx.Err() } - 混用风险高:一个
Go()任务卡住,GoContext()任务已退出,Wait()返回nil,你以为全成功,其实有 goroutine 在后台漏跑
Wait() 返回 nil 就代表没问题?不一定
g.Wait() 返回 nil,只说明「所有已启动的 goroutine 都结束了,且没报告非 nil 错误」—— 它不捕获 panic,也不验证逻辑是否真执行完毕。
- 如果某个
Go()闭包里panic("boom")且没recover,整个程序崩溃,根本走不到Wait() - 多个任务都出错,
Wait()只保留第一个(按完成顺序),后面那个更关键的错误(比如数据库连接失败)可能被前面的网络超时掩盖 - 想看每个任务是否真跑完?在闭包开头加
log.Printf("start: %s", taskName),确认日志条数和任务数一致
需要全部错误怎么办
errgroup 天然不聚合错误,这是设计使然。想拿到全部失败原因,得自己动手:
立即学习“go语言免费学习笔记(深入)”;
- 方案一:用
sync.Mutex+[]error手动收集,每个Go()里mu.Lock(); errs = append(errs, err); mu.Unlock() - 方案二:用
multierr.Append()(来自go.uber.org/multierr),在每个闭包末尾return multierr.Append(prevErr, thisErr) - 注意:别在
Go()里只log.Printf错误却return nil,这会让Wait()认为任务成功,彻底掩盖问题
context.WithTimeout 包了 HTTP 请求,但后续 JSON 解析或写文件耗时更长,超时信号就失效了。所有耗时环节都得主动检查 ctx.Err(),不能只靠 GoContext() 自动兜底。


















