WaitGroup与ErrGroup在Context取消中的差异困惑及示例解析
WaitGroup 与 ErrGroup 在 Context 取消场景下的核心差异
1. 内置能力的本质区别
- ErrGroup:是
context.Context+协程等待+错误传播的一体化工具。它创建时会自动关联一个可取消的Context,当组内任意协程返回非nil错误时,ErrGroup会自动调用Context的cancel函数,让所有组内协程收到取消信号,实现“一处出错,全组终止”的效果。 - WaitGroup:只是一个单纯的计数等待工具,仅负责跟踪协程的启动/完成数量,和Context、错误处理没有任何内置绑定关系。它不会主动触发Context取消,所有和取消相关的逻辑都需要你手动实现。
2. 结合你的示例具体分析
ErrGroup 的 gRPC 服务场景
你写的示例中,当启动gRPC服务的协程报错(比如端口冲突、启动失败),ErrGroup会立刻触发关联Context的取消:
g, ctx := errgroup.WithContext(context.Background()) g.Go(func() error { // 启动gRPC服务,失败返回错误 return grpcServer.Serve(listener) }) g.Go(func() error { // 监听ctx取消,优雅关闭服务 <-ctx.Done() grpcServer.GracefulStop() return ctx.Err() }) // Wait会阻塞直到所有协程完成,或任意协程出错 if err := g.Wait(); err != nil { log.Fatal(err) }
这里不需要你手动调用cancel,ErrGroup在第一个协程返回错误时,已经帮你触发了ctx的取消,第二个协程会立刻收到信号并执行优雅关闭。
WaitGroup 的 HTTP 服务场景
你参考的示例里,虽然有监听Context取消的逻辑,但这个取消信号不是WaitGroup触发的:
var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) wg.Add(1) go func() { defer wg.Done() // 启动HTTP服务,失败时需要手动调用cancel if err := http.ListenAndServe(":8080", router); err != nil && err != http.ErrServerClosed { cancel() // 手动触发取消 log.Fatal(err) } }() wg.Add(1) go func() { defer wg.Done() <-ctx.Done() // 优雅关闭HTTP服务 server.Shutdown(context.Background()) }() // Wait只会等待所有协程计数归0,不会主动取消 wg.Wait()
如果HTTP服务启动出错,你必须**手动调用cancel()**才能让第二个协程收到取消信号。如果忘了写这行代码,WaitGroup只会一直等待,Context不会被取消,优雅关闭的逻辑也不会执行——这就是所谓的“需手动停止协程”的核心原因:WaitGroup不会帮你完成“出错→触发取消”的链路,一切都要自己写。
3. 适用场景总结
- 用ErrGroup:当你需要一组协程联动终止(一个出错全停),且希望自动处理取消逻辑时,比如多服务启动、批量任务执行。
- 用WaitGroup:当你只需要等待所有协程完成,不需要联动取消,或者取消逻辑由外部控制(比如收到系统信号)时,比如批量数据处理、无关联的异步任务。
内容的提问来源于stack exchange,提问作者Tarta
相关产品推荐
相关产品推荐

