在 Go 1.21 版本中使用 errgroup 处理并发任务,必须通过 errgroup.WithContext 初始化并传入带超时或取消能力的 context,子任务内需显式检查 ctx.Err() 以实现错误传播和资源释放。
先说结论:errgroup.WithContext 是唯一安全的初始化方式,直接实例化 Group 会导致无法响应取消信号。
- 适合场景:需要并发执行多个任务且任意出错即取消其余任务的场景。
- 先看配置:确认 context 是否包含超时设置,避免使用 context.Background() 直接初始化。
- 建议操作:子任务中优先使用支持 context 的 IO 接口,并手动检查 ctx.Done()。
快速处理思路
以下是 Go 1.21 中标准的 errgroup 并发错误处理代码结构,确保任务可被取消且错误可捕获。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
eg, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
url := url // 避免闭包捕获变量问题
eg.Go(func() error {
// 必须使用支持 context 的请求方法
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
_, err := http.DefaultClient.Do(req)
return err
})
}
if err := eg.Wait(); err != nil {
log.Printf("task failed: %v", err)
}为什么会这样
errgroup 依赖 context 的 Done 通道传播中断信号,不使用 WithContext 初始化会导致子 goroutine 无法感知取消请求。
直接用 new(errgroup.Group) 或字面量初始化会丢失上下文控制能力,当某个任务出错或超时时,其余任务可能继续运行导致资源泄漏。errgroup.WithContext 返回的 context 是派生上下文,所有子任务共享同一取消信号,确保任意任务返回错误后,其他监听该 context 的任务能及时退出。
分步处理
按以下步骤实施并发任务管理,确保错误处理和资源控制符合预期。
第一步:初始化带超时的 Context
使用 context.WithTimeout 或 context.WithCancel 创建根 context,避免直接使用 context.Background()。确保超时时间覆盖整个任务链,包括网络请求和数据处理。
第二步:使用 WithContext 创建 Group
调用 errgroup.WithContext(ctx) 获取 group 和派生 context。不要复用已取消的 context,否则调用 Go 方法时可能直接 panic。
第三步:提交任务并检查 Context
在 eg.Go 的闭包内,所有阻塞操作必须使用支持 context 的版本。纯 CPU 计算需定期检查 ctx.Err()。循环提交任务时,务必在循环内复制迭代变量,防止闭包捕获同一个变量地址。
第四步:统一等待并处理错误
调用 eg.Wait() 阻塞直到所有任务完成。注意 Wait() 只返回第一个非 nil 错误,后续错误会被丢弃。如需收集全部错误,需在闭包内自行维护错误切片。
怎么验证是否生效
通过日志和错误类型确认并发控制是否按预期工作。
检查取消信号:手动触发取消或等待超时,观察未完成任务是否快速退出而非运行到自然结束。日志中应看到 ctx.Canceled 或 ctx.DeadlineExceeded 错误。
验证错误捕获:故意让其中一个任务返回错误,确认 eg.Wait() 能捕获该错误且程序未挂起。若 Wait() 返回 nil 但业务逻辑未执行,检查是否有 panic 未被 recover。
确认变量独立:在闭包内打印迭代变量值,确认每个任务处理的是正确的数据而非最后一个值。
常见坑
以下场景容易导致并发失控或错误被掩盖,需特别谨慎。
忽略 ctx.Err() 检查:子任务中若不调用支持 context 的 IO 接口且不检查 ctx.Done(),即使主 context 已取消,goroutine 仍会继续运行直到完成。
误以为 Wait 返回 nil 即全成功:eg.Wait() 返回 nil 仅表示所有启动的任务都退出且未报告非 nil 错误,不保证没有 panic 发生。若任务内 panic 且未 recover,程序会直接崩溃。
混淆 Go 和 GoContext 用法:部分版本或扩展库提供 GoContext 方法,若使用标准库 Go 方法,需手动将 ctx 传入闭包。混用可能导致部分任务不响应取消信号。
错误收集误解:errgroup 设计目标是快速失败,只返回首个错误。若业务需要所有任务结果,需自行使用 sync.Mutex 保护错误切片收集。
常见问题
errgroup.Wait() 返回 nil 代表所有任务都成功了吗?
不一定,它只代表所有任务退出且未返回非 nil 错误,若有 panic 发生则不会捕获。
如何让一个任务失败后自动取消其他任务?
必须使用 errgroup.WithContext 初始化,且子任务需监听同一 context 的 Done 信号。
可以在 errgroup 中控制并发数量吗?
errgroup 本身不提供限流,需配合 semaphore 或带缓冲 channel 手动控制并发数。
参考来源
- golang.org/x/sync/errgroup 官方文档,https://pkg.go.dev/golang.org/x/sync/errgroup
- Go 语言并发最佳实践技术分析,内部知识库