errgroup.Group 必须用 errgroup.WithContext 初始化,不能直接 struct 初始化;其 Go 方法启动 func() error 任务,Wait 返回首个非 nil 错误,未完成任务需 context 控制取消。

errgroup.Group 的基本用法和初始化
直接用 errgroup.Group{} 初始化是错的——它内部有未导出字段,必须用 errgroup.WithContext 创建。否则调用 Go 或 Wait 会 panic。
正确方式是传入一个 context.Context,哪怕只是 context.Background():
g, _ := errgroup.WithContext(context.Background())
注意:返回的第二个值是 error,但 WithContext 实际上不会返回错误,可以安全忽略;不过不能省略接收,否则编译失败。
并发执行任务并收集第一个非nil错误
errgroup.Group 默认行为是“只要有一个 goroutine 返回非 nil error,Wait 就立刻返回该错误”,且其他 goroutine 不会自动取消——除非你显式用了带 cancel 的 context。
立即学习“go语言免费学习笔记(深入)”;
- 每个任务函数签名必须是
func() error,不能带参数,需靠闭包捕获变量 - 如果多个 goroutine 同时返回错误,
Wait只返回**第一个被发现的**(不是最先发生的),顺序取决于调度 - 未完成的 goroutine 会继续运行,直到自然结束或被 context 取消
示例:
g, ctx := errgroup.WithContext(context.Background())
for i := 0; i < 3; i++ {
i := i // 避免循环变量复用
g.Go(func() error {
select {
case <-time.After(time.Duration(i+1) * time.Second):
if i == 1 {
return fmt.Errorf("task %d failed", i)
}
return nil
case <-ctx.Done():
return ctx.Err()
}
})
}
if err := g.Wait(); err != nil {
log.Printf("got error: %v", err) // 输出 "task 1 failed"
}
如何让失败任务自动取消其余 goroutine
靠 errgroup.Group 自身做不到——它不主动控制 context。必须把同一个 context.Context 传给所有子任务,并在 goroutine 内监听 ctx.Done()。
关键点:
- 用
context.WithCancel包裹原始 context,得到可取消的子 context - 把子 context 传进每个 goroutine,在 I/O 或阻塞操作前加
select { case - 当任意 goroutine 报错,
Wait返回后,记得调用cancel()(但errgroup不帮你做这事)
所以典型模式是:
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 防止泄漏,但实际取消时机要手动控制
<p>g, <em> := errgroup.WithContext(ctx)
for </em>, url := range urls {
url := url
g.Go(func() error {
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// ...
return nil
})
}</p><p>if err := g.Wait(); err != nil {
cancel() // 主动触发取消,让其余 goroutine 尽快退出
return err
}
常见错误:误用无 context 版本或忽略 goroutine 泄漏
Go 标准库没有提供无 context 的 errgroup.Group 构造函数——网上有些旧代码写 var g errgroup.Group 是无效的,因为 Go 方法会 panic。
另一个高发问题是:没对耗时操作加 context 超时或取消,导致即使某个 goroutine 失败了,其余还在死等网络、文件读取或 sleep,整个 Wait 被拖住。
- 永远不要裸用
time.Sleep,改用select { case - HTTP 请求必须用
http.NewRequestWithContext,数据库查询也要传 context - 如果任务本身不支持 context(比如纯计算),就无法真正中断,只能等它跑完
最易被忽略的一点:errgroup 不负责资源清理,goroutine 里打开的文件、连接、goroutine 自身都得自己关、自己停、自己回收。


















