errgroup不收集所有错误,只传播首个非nil错误;需汇总全部错误须自行用chan error配合sync.WaitGroup;Wait()返回nil仅表示已启动goroutine均退出且未报告非nil错误,不代表全部成功。

errgroup 不是“错误分组”,它不收集所有错误,只传播第一个非 nil 错误;想汇总全部错误,得自己用 chan error 配合 sync.WaitGroup。
errgroup.Wait() 返回 nil 时,不代表所有任务都成功执行了
它只表示:所有已启动的 goroutine 都退出了,且没返回非 nil 错误。但以下情况仍可能出问题:
- 某个
Go()闭包里发生panic且未recover→ 程序直接崩溃,根本走不到Wait() - 任务逻辑被中断(比如 HTTP 请求超时),但函数没显式返回错误 →
Wait()认为“成功” - IO 操作用了不支持 context 的老接口(如
http.Get而非http.GetWithContext)→ 即使上下文已取消,goroutine 仍卡住不退出
验证是否真执行完,最简单办法是在每个任务开头加日志:log.Printf("start: %s", url),确认每条都输出。
必须用 errgroup.WithContext 初始化,别用 &errgroup.Group{}
直接 new 或字面量初始化会丢失上下文取消能力,导致:
立即学习“go语言免费学习笔记(深入)”;
- 任一任务出错或超时后,其余 goroutine 继续运行,资源不释放
-
ctx.Done()永远不会触发,http.GetWithContext等操作无法响应取消 - 整个 group 卡死,
Wait()永不返回
正确写法是:g, ctx := errgroup.WithContext(context.WithTimeout(context.Background(), 5*time.Second))。注意传入的原始 ctx 必须带超时或可取消能力;复用已取消的 ctx 创建新 group,g.Go() 可能 panic context.Canceled。
Go() 和 GoContext() 别混用,行为完全不同
二者适用场景严格区分:
-
Go(func() error):只适合纯 CPU 计算、无阻塞、执行极快的任务(如 base64 编码、字符串 trim);它完全不感知上下文,也不检查ctx.Err() -
GoContext(func(ctx context.Context) error):用于所有需响应取消的 IO 任务(HTTP、DB 查询、文件读写等);内部自动监听ctx.Done(),并在取消时退出
常见错误:对 HTTP 请求用 Go(),却没在函数内手动检查 ctx.Err() → 超时后仍阻塞。正确写法是:g.GoContext(func(ctx context.Context) error { return http.GetWithContext(ctx, url) })。
并发数控制不在 errgroup 职责范围内
errgroup.Group 本身不提供限流能力。即使你设了 3 秒超时,如果底层 http.Transport 的连接池太小(比如默认 MaxIdleConns=100),大量请求会卡在 net/http.Transport.roundTrip,看起来像“没响应”,实际是网络层阻塞。
这时该调大 transport 配置,而不是怪 errgroup:
http.DefaultTransport.(*http.Transport).MaxIdleConns = 200- 或自定义 client 并设置
Transport.MaxIdleConnsPerHost
真正需要并发限制时,得靠外部机制,比如用带缓冲的 chan struct{} 控制 goroutine 启动节奏,或改用 semaphore 类库。


















