errgroup.withcontext 是对 context.withcancel 的封装,用于协程错误聚合与终局取消;需手动调用 cancel 实现实时中断,协程须监听传入的子 ctx,panic 需显式 recover 转为 error。

errgroup.WithContext 本质是把 context.WithCancel 套了一层壳
它不是新发明的取消机制,而是对 context.WithCancel 的封装 + 协程错误聚合。调用 errgroup.WithContext(ctx) 后,内部会调用 context.WithCancel(ctx) 创建子上下文,并在所有协程退出(无论成功或失败)后自动调用 cancel() —— 但这个 cancel 只在 eg.Wait() 返回时才触发,不是一有错误就立刻 cancel。
这意味着:如果你希望某个协程出错时立即中断其他协程,不能只靠 eg.Wait(),必须手动在出错路径里调用 eg.Go() 外层的 cancel()。
- 常见错误:写成
if err != nil { return err }就结束协程,却不通知其他协程停止,导致资源泄漏或重复执行 - 正确做法:在出错分支显式调用传入的
cancel()函数(来自context.WithCancel),再 return -
errgroup.WithContext自带的 cancel 是“终局式”的,适合收尾清理;实时响应需自己加一层控制
协程里怎么安全读取和传递 context.Done()
每个协程必须用传入的 ctx(不是原始父 ctx)监听取消信号,否则可能漏掉 cancel。注意 eg.Go() 传入的函数签名是 func() error,不带参数,所以你要自己把需要的 ctx 捕获进闭包,或者改用 eg.Go(func(ctx context.Context) error { ... }) 形式(需自行包装)。
更稳妥的做法是:在 eg.Go() 外提前派生带超时/取消的子 ctx,并传入协程内部。
- 别直接在闭包里用外部定义的
ctx变量,容易被循环覆盖(比如 for 循环中启动多个eg.Go()) - 推荐写法:
childCtx, cancel := context.WithTimeout(ctx, 5*time.Second),然后在协程里 defercancel()或按需调用 - 如果协程本身要调用下游 HTTP 请求,务必把
childCtx传给http.NewRequestWithContext(childCtx, ...),否则 cancel 不生效
为什么 eg.Wait() 有时不返回,即使有协程 panic 了
errgroup.Group 默认不会捕获 panic,一旦某个 eg.Go() 协程 panic,整个程序会崩溃,eg.Wait() 根本没机会返回。这不是 bug,是设计使然 —— Go 不鼓励隐式 recover。
若需容错,必须手动在每个 eg.Go() 的函数体内加 defer func(){...}() 捕获 panic 并转为 error 返回。
- panic 不等于 error,
errgroup只收集error类型返回值 - 常见陷阱:忘记 recover,结果一个协程 panic 导致整个服务挂掉,且无日志提示
- 简单 recover 模板:
defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }()
取消后资源没释放?检查 defer 和 channel 关闭时机
context 取消只是发信号,不自动关闭文件、网络连接或 channel。很多问题表面是 “取消不生效”,实际是协程没响应 Done,或 defer 语句没执行到。
典型场景:协程阻塞在 ch (channel 已满且无人接收),即使 ctx.Done() 触发,也不会自动退出 —— 必须用 <code>select 配合 ctx.Done() 主动退出。
- 写 channel 前必加 select:
select { case ch - 所有 defer 关闭操作(如
file.Close()、rows.Close())应放在协程最外层,确保无论 return 还是 panic 都执行 - 不要依赖
eg.Wait()返回后再统一关资源,那已经太晚了
真正难的不是写 errgroup.WithContext,是判断哪些地方该用 ctx.Done() 做主动退出,哪些资源必须在协程内立刻释放。cancel 函数只是扳机,扣扳机的手得你自己长。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











