在 Go 语言并发编程中,使用 errgroup 包配合 context 是统一管理多个 goroutine 错误和取消的最推荐方案。它适用于“任一任务失败即终止整体”的场景,但需注意默认情况下不会自动取消正在运行的任务,必须显式检查 ctx.Err()。
先说结论:errgroup 封装了 sync.WaitGroup 和错误传播逻辑,结合 context 可实现并发任务的生命周期控制。
- 适合:批量并发任务处理,如并发调用 HTTP 接口或数据库查询
- 先看:业务需求是“首个错误即停止”还是“收集所有错误”
- 建议:始终使用 errgroup.WithContext 版本以支持任务取消
快速处理思路
直接引入 golang.org/x/sync/errgroup 包,使用 WithContext 创建组,在任务函数内检查 context 状态。
g, ctx := errgroup.WithContext(context.Background())
for _, task := range tasks {
g.Go(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return doWork()
}
})
}
return g.Wait()
为什么会这样
errgroup 的核心机制是同步等待一组 goroutine 完成,并在任一任务返回错误时取消上下文。
它内部维护了一个 sync.WaitGroup 用于计数,同时持有一个 context.Context。当调用 g.Wait() 时,主线程阻塞直到所有任务完成或首个错误发生。若使用 WithContext 创建,当某个 goroutine 返回非 nil 错误时,衍生的 context 会被取消,通知其他 goroutine 退出,从而避免资源泄漏。
分步处理
第一步:导入包并创建组。必须导入 golang.org/x/sync/errgroup,优先使用 errgroup.WithContext(context.Background()) 创建实例,以便获得可取消的 context。
第二步:启动任务。在循环中调用 g.Go(func() error {...}),注意循环变量需重新赋值以防闭包捕获问题,例如 task := task。
第三步:实现任务逻辑。在 goroutine 函数开头检查 ctx.Err() != nil,若已取消则立即返回 ctx.Err(),避免任务在超时后继续运行。
第四步:等待并处理错误。调用 g.Wait() 阻塞等待,若返回非 nil 错误,通常是第一个遇到的错误,需根据业务决定是否记录日志或重试。
怎么验证是否生效
检查 g.Wait() 的返回值是否为预期错误。观察日志中是否有任务在 context 取消后仍继续执行的记录。通过人为制造某个任务报错,确认其他长期运行的任务是否收到 ctx.Done() 信号并退出。
常见坑
循环变量捕获:在 for 循环中启动 goroutine 时,直接使用循环变量会导致所有任务共享同一个变量值,必须在循环体内复制变量。
Panic 未捕获:goroutine 内部的 panic 不会传播到主线程,必须在每个 goroutine 内部使用 defer + recover 捕获,errgroup 虽能捕获 panic 转为 error,但显式处理更安全。
无限制并发:errgroup 默认不限制并发数量,若任务量巨大需配合 semaphores 或带缓冲 channel 控制并发度,防止耗尽内存。
错误收集不全:errgroup 默认只返回第一个错误,若需收集所有错误,需自行使用 sync.Mutex 保护错误切片或在任务内发送错误到 channel。
常见问题
errgroup 和 sync.WaitGroup 有什么区别?
errgroup 封装了 WaitGroup 并增加了错误返回和 context 取消功能,WaitGroup 仅负责等待计数。
如何收集所有 goroutine 的错误而不是第一个?
需自行维护一个 error 切片,使用 sync.Mutex 保护,或在任务内将错误发送到带缓冲的 channel,最后在 Wait 后统一读取。
goroutine 中的 panic 会被 errgroup 捕获吗?
errgroup 内部会 recover panic 并转为 error 返回,但建议在业务代码内部显式处理 panic 以便记录详细堆栈。
参考来源
- Golang 怎么用 errgroup 并发执行多任务_Golang 如何统一收集多个协程的错误结果【技巧】
- Go 语言中如何在多个 Goroutine 间优雅传递错误处理函数?ErrGroup 实战【详解】
- Golang 的 errgroup 包如何帮助管理一组 goroutine 的错误
- golang 常用包详解之:errgroup - 水车 - 博客园