必须用 errgroup.withcontext 初始化,因其返回派生上下文并自动传播取消信号;直接 new 或字面量初始化会丢失超时与取消能力,导致任务卡住、资源泄漏。

直接用 errgroup.WithContext,别用 &errgroup.Group{} —— 后者无法响应超时或取消,任务失败后可能永远卡住。
为什么必须用 WithContext 初始化?
不用 WithContext,errgroup.Group 就退化成带错误收集的 sync.WaitGroup:某个 goroutine 出错或超时,其余仍在运行,资源不释放,ctx.Done() 永远不会触发。
-
errgroup.WithContext(ctx)返回的ctx是派生上下文,所有子任务共享同一取消信号 - 手动传入的
ctx必须带超时或可取消能力,比如context.WithTimeout(context.Background(), 5*time.Second) - 别复用已取消的
ctx创建新errgroup,否则g.Go()可能 paniccontext.Canceled - HTTP 请求、数据库查询等 IO 操作,必须用支持 context 的版本(如
http.NewRequestWithContext)
Go() 和 GoContext() 怎么选?
看任务是否需要响应取消信号。二者行为完全不同,混用会导致部分 goroutine “失联”。
-
GoContext()显式接收ctx参数,内部自动监听ctx.Done(),适合所有可取消的 IO 任务(如http.Get、db.Query) -
Go()只接受无参函数,完全不感知ctx,仅适用于纯 CPU 计算、无阻塞、执行极快的任务(如 base64 编码、简单字符串处理) - 常见错误:对 HTTP 请求用
Go(),但没在函数内手动检查ctx.Err()→ 超时后仍继续阻塞 - 正确写法:
g.GoContext(func(ctx context.Context) error { return http.GetWithContext(ctx, url) })
Wait() 返回 nil 就代表全成功?
不是。它只说明「所有已启动的 goroutine 都退出了,且没报告非 nil 错误」——但不保证逻辑真执行完,也不捕获 panic。
- 某个
Go()闭包里发生 panic 且未 recover,整个程序崩溃,根本走不到g.Wait() - 多个任务都出错时,
g.Wait()只返回第一个非 nil 错误(按完成顺序),其余被丢弃 - 若需判断关键路径是否执行,得在闭包开头加日志(如
log.Printf("start: %s", url)),确认每条都输出 - 依赖错误类型做分支逻辑(如重试
context.DeadlineExceeded)时,注意返回的可能是更早发生的其他错误
最易被忽略的一点:errgroup 不提供并发数控制。哪怕你设了 3 秒超时,如果 http.Transport 连接池太小,大量请求会卡在 net/http.transport.roundTrip,看起来像“没响应”,实则是底层阻塞——这时要调大 MaxIdleConns,而不是怪 errgroup。










