最稳妥方式是用 sync.waitgroup + sync.mutex 汇总错误:预分配线程安全错误切片,mutex 保护写入,waitgroup 确保等待完成;配合 errgroup.withcontext 实现取消与首错退出,任务中检查 ctx.err() 并将错误写入共享切片后返回 nil。

用 sync.WaitGroup + sync.Mutex 汇总错误最稳妥
Go 没有内置的“并发错误聚合”机制,errgroup 虽好但默认只返回第一个错误。真要收集全部错误,必须自己维护一个线程安全的错误切片。
常见错误是直接在 goroutine 里往全局 []error append —— 这会引发 panic 或漏写。必须加锁。
- 用
sync.Mutex保护错误切片的写入,不是所有地方都适合用atomic -
sync.WaitGroup确保主 goroutine 等待全部任务结束,别漏掉wg.Add(1)或wg.Done() - 错误切片建议预分配容量(如
make([]error, 0, len(tasks))),避免多次扩容带来的小概率竞争
用 errgroup.Group 并开启 WithContext 控制取消与提前退出
errgroup 本身不汇总全部错误,但它能帮你优雅地终止失败后的其余任务,并拿到首个错误。配合自定义错误收集,就能兼顾性能和完整性。
典型场景:批量调用外部 API,某个请求超时或 5xx,你既想立刻停止后续请求,又想保留已发生的全部错误。
立即学习“go语言免费学习笔记(深入)”;
- 用
eg, ctx := errgroup.WithContext(context.Background())启动组 - 每个任务开头检查
ctx.Err() != nil,尽早 return - 在
eg.Go(func() error { ... })内部,把错误追加到共享的线程安全切片,再返回nil(否则eg.Wait()会提前返回) - 最后统一检查共享错误切片长度,而非只依赖
eg.Wait()的返回值
避免用 channel 收集错误导致的 Goroutine 泄漏
有人倾向用 chan error 接收每个任务的错误,但若不 careful 处理,极易泄漏 goroutine —— 尤其当部分任务 panic 或未写入 channel 时。
例如:开一个 go func() { ch ,但主协程已因超时退出,这个 goroutine 就永远卡在发送上。
- 务必给错误 channel 带缓冲,容量等于任务数:
ch := make(chan error, len(tasks)) - 主 goroutine 必须用
for i := 0; i 消费完所有可能的错误 - 更推荐用 mutex + slice,语义清晰、无阻塞风险、内存局部性更好
注意 errors.Join 在 Go 1.20+ 的适用边界
errors.Join 可以把多个错误合并为一个,但它**不保留原始错误的上下文顺序,也不支持去重或过滤**。它只是方便你最终返回一个“复合错误”,而不是做中间聚合工具。
- 仅在最终返回前调用:
return errors.Join(allErrors...) - 如果某个错误是
nil,errors.Join会忽略它 —— 所以收集阶段仍需判空 - 它不会展开嵌套的
*fmt.wrapError或自定义错误类型,只扁平化一层 - 不要在循环中反复
errors.Join(a, b),性能差且语义混乱;先收集再一次性 Join
真正难的不是怎么合并,而是怎么确保每个 goroutine 的错误都可靠落地——锁、上下文、channel 缓冲,三者缺一不可。漏掉任意一环,错误就静默消失了。










