ErrGroup 比手动 WaitGroup+channel 更可靠,因其天然支持错误即取消、共享 context、幂等错误处理;而手动实现易导致 goroutine 泄漏、取消失效、panic 丢失等问题。

ErrGroup 为什么比自己写 waitgroup + channel 更可靠
因为 errgroup.Group 天然支持「任意子任务出错即取消其余运行中任务」,而手动用 sync.WaitGroup 和 chan error 很难干净实现错误传播与上下文取消联动。
常见错误现象:context.DeadlineExceeded 或自定义错误发生后,其他 goroutine 还在傻跑,资源没释放,超时逻辑失效。
- 它底层复用
context.WithCancel,所有子任务共享同一个ctx,一错全停 - 不需要手动 close channel 或判断是否已 send 过 error ——
Go方法内部做了幂等保护 - 如果不用
errgroup.WithContext而直接 newerrgroup.Group{},错误仍会收集,但**不会自动取消**其他 goroutine
Go 方法里 panic 会导致 ErrGroup 忽略错误吗
会。只要 goroutine 内部 panic 且没 recover,errgroup.Group.Go 就捕获不到错误,整个 group 的 Wait() 可能永远阻塞,或返回 nil(取决于 panic 发生时机)。
使用场景:调用外部 SDK、解析未知格式数据、HTTP 请求体解码等易 panic 场景必须兜底。
立即学习“go语言免费学习笔记(深入)”;
- 务必在
Go传入的函数里加 defer-recover,把 panic 转成 error 返回 - 不要依赖
recover捕获所有异常 —— 比如runtime.ErrStackOverflow无法 recover - 示例中别写
return errors.New("xxx"),而要显式调用g.Go(func() error { ... })的 error 返回路径
g.Go(func() error {
defer func() {
if r := recover(); r != nil {
// 日志记录 panic 内容
gerr := fmt.Errorf("panic: %v", r)
// 注意:这里不能直接 return,得设到闭包外变量或用 channel 同步
}
}()
// ... 实际逻辑
return nil
})
多个子任务共用一个 http.Client 时要不要自己加锁
不用。标准库的 *http.Client 是并发安全的,它的 Do 方法内部不修改共享字段,只读取 Transport、Timeout 等配置。
容易踩的坑是误以为要保护 http.Client,结果给每个请求套上 sync.Mutex,反而制造瓶颈。
-
http.Client的零值可用,但建议显式设置Timeout,否则默认无限等待 - 如果用了自定义
http.Transport,要确保MaxIdleConns和MaxIdleConnsPerHost足够支撑并发数,否则连接池会成为瓶颈 - ErrGroup 并发数超过 Transport 连接池上限时,表现是大量请求卡在
net/http.transport.roundTrip,错误日志里看不到明显报错
Wait 返回 error 后,怎么知道是哪个子任务失败的
errgroup.Group 不提供失败任务索引或名字,它只聚合第一个非 nil error。这是设计取舍:强调“失败即终止”,而非诊断。
性能影响:加额外标识会增加内存分配和 map 查找开销,违背轻量聚合初衷。
- 如果必须定位,改用带命名的 error 包装,比如
fmt.Errorf("upload-%s: %w", filename, err) - 或者在 goroutine 内部记录日志,用唯一 trace ID 关联上下文
- 切勿在
Go函数里用闭包变量拼接错误(如return fmt.Errorf("%v: %w", i, err)),i 可能被循环覆盖
复杂点在于:ErrGroup 的“第一个错误”不是按启动顺序,而是按完成顺序;谁先 panic / return error,谁就胜出。这点常被忽略。










