不能直接用 sync.WaitGroup 做错误传播,因为它只等待 goroutine 结束而不处理错误;errgroup.Group 内置错误传播与自动取消机制,配合 WithContext 和信号量可安全限并发。

为什么不能直接用 sync.WaitGroup 做错误传播
因为 sync.WaitGroup 只管“等所有 goroutine 结束”,不关心它们是否出错。一旦某个任务返回 error,你得自己设共享变量、加锁判断、提前取消其余任务——容易漏处理、竞态难调试。errgroup.Group 就是为解决这个而生:它内置错误传播机制,首次非 nil 错误会自动取消其余 goroutine(如果用了 WithContext),且阻塞在 Wait() 直到全部完成或出错。
如何正确初始化 errgroup.Group 并控制并发数
默认的 errgroup.Group 没有并发限制,所有任务会立刻启动。若要限速(比如防止 HTTP 请求打爆后端),得用带上下文的构造方式,并配合 semaphore 或手动调度。更常用的是:errgroup.WithContext + 外层控制 goroutine 数量:
g, ctx := errgroup.WithContext(context.Background())
sem := make(chan struct{}, 5) // 并发上限 5
<p>for _, task := range tasks {
task := task // 防止闭包引用同一变量
g.Go(func() error {
sem <- struct{}{} // 获取信号量
defer func() { <-sem }() // 释放信号量
return doWork(ctx, task)
})
}注意:sem 必须是带缓冲通道;defer 里的 `
errgroup.Wait 返回 error 后还能继续调用吗
不能。errgroup.Group 的状态是一次性的:Wait() 返回后,内部状态已终结。再次调用 Wait() 会立即返回上次的错误(或 nil),但不会重新等待。如果想复用,必须新建 errgroup.Group 实例。
立即学习“go语言免费学习笔记(深入)”;
- 常见误用:把同一个
g实例反复用于多个任务批次 - 正确做法:每个任务批次都 new 一个
errgroup.Group - 如果任务之间有依赖,别指望靠重用
g实现“链式执行”——那是context或状态机的事
ctx.Cancel() 被触发时,goroutine 真的会立刻退出吗
不会自动退出,得你自己检查 ctx.Err() 并主动返回。errgroup 只负责传递 cancel 信号,不强制终止 goroutine。比如下面这段代码就危险:
g.Go(func() error {
time.Sleep(10 * time.Second) // 忽略 ctx,超时也不会停
return nil
})正确写法是周期性检测上下文:
g.Go(func() error {
select {
case <-time.After(10 * time.Second):
return nil
case <-ctx.Done():
return ctx.Err() // 返回 context.Canceled 或 context.DeadlineExceeded
}
})所有 I/O 操作(如 http.Client.Do、time.Sleep、chan 收发)都应该接受 ctx 参数或响应 ctx.Done(),否则 errgroup 的取消机制形同虚设。
实际用的时候,最常被忽略的是:goroutine 内部没做上下文感知,或者复用 errgroup.Group 实例。这两点一出,程序要么卡死,要么错误不传播,查起来特别绕。


















