errgroup.group只返回第一个错误因其“快速失败”设计:任一goroutine出错即终止其余任务并返回该错误;若需汇总全部错误,应改用buffered channel配合waitgroup。

errgroup.Group 为什么只返回第一个错误
因为 errgroup.Group 的设计目标是“快速失败”:只要任意一个 Go() 函数返回非 nil 错误,g.Wait() 就立即返回该错误,并通过绑定的 context.Context 取消其余正在运行的 goroutine。它不收集全部错误,也不重试。
- 这是默认行为,无需额外配置;若你期望所有任务都跑完再汇总错误,
errgroup.Group不适合 - 必须用
errgroup.WithContext(ctx)才能触发自动取消,裸用new(errgroup.Group)时 cancel 逻辑不会生效 - 错误值本身不带 goroutine 标识,建议在
error构造时显式带上上下文,例如fmt.Errorf("task %s failed: %w", taskID, err)
recover 无法跨 goroutine 捕获 panic
你在主 goroutine 里写 defer recover(),对子 goroutine 中的 panic 完全无效——这是最常被忽略的并发陷阱。
- 每个 goroutine 需要自己处理自己的 panic,必须在
g.Go()包裹的函数内部第一行加defer func() { if r := recover(); r != nil { /* log or wrap */ } }() - 仅 recover 不够:panic 后 goroutine 终止,但若它正往无缓冲 channel 发送数据,接收方会永久阻塞,引发死锁
- 推荐组合:recover + error channel 封装,比如返回
struct{ Result any; Err error },避免直接 send/error panic 交织
用 buffered channel + WaitGroup 收集全部错误
当你需要知道“哪几个任务失败了”,而不是“只要一个失败就停”,就得放弃 errgroup.Group,改用带缓冲的 chan error 配合 sync.WaitGroup。
- channel 容量至少为 goroutine 数量(如
make(chan error, len(tasks))),否则发送错误时可能阻塞 goroutine 退出 - 每个 goroutine 执行完后调用
wg.Done(),**不要**在 goroutine 内关闭 channel;应在wg.Wait()后由主 goroutine 关闭 - 接收全部错误时用
for i := 0; i ,别用 <code>range—— channel 关闭前可能有未发送项,range会提前退出
errgroup.WithContext 被忽略的 cancel 时机
errgroup.WithContext(ctx) 创建的 group 确实会在首个错误返回时调用 cancel(),但这个 cancel **只影响后续新建的 goroutine**,对已启动且未检查 ctx.Done() 的 goroutine 没有强制终止能力。
- 你的业务函数(如
fetchURL(ctx, url))必须主动监听ctx.Done()并提前退出,否则它会继续执行到自然结束 - HTTP 请求、数据库查询等阻塞操作需使用支持 context 的客户端(如
http.Client带WithContext方法),否则 cancel 无效 - 长时间 CPU 密集型计算中,需定期插入
select { case 检查,不能只靠一次判断
真正难的不是选哪种方案,而是判断「这个任务是否允许部分失败」「失败后要不要继续等其他结果」「有没有外部依赖需要主动清理」——这些决策点比代码写法更影响最终健壮性。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











