You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 21:33:18