errgroup.WithContext必须传入带cancel的context,因其仅通过ctx传播取消信号;若用context.Background(),goroutine无法响应中断,错误发生后其余任务仍继续运行,导致资源泄漏。

errgroup.WithContext 为什么必须传入带 cancel 的 context
直接用 context.Background() 启动 errgroup.Group,一旦某个 goroutine panic 或返回错误,其余还在跑的协程不会自动停止——errgroup 本身不干预执行逻辑,只负责收集错误和等待完成。只有当它内部使用的 ctx 被 cancel,后续调用 g.Go() 才会立即返回 context.Canceled 错误,且正在运行的函数若检查了 ctx.Done() 才能退出。
实操建议:
- 始终用
context.WithCancel(context.Background())创建父 ctx,把 cancel 函数交给主流程控制 - 每个传给
g.Go()的函数必须显式监听ctx.Done()并提前 return,否则 cancel 无效 - 不要在
g.Go()里再用context.Background(),应复用传入的 ctx 或派生子 ctx
g.Go() 里 recover 捕获 panic 有必要吗
有必要。因为 errgroup 不捕获 panic,一旦某个 goroutine panic,整个程序会崩溃,g.Wait() 根本没机会返回错误。
实操建议:
- 在每个传给
g.Go()的函数最外层加defer func() { if r := recover(); r != nil { /* 记录或转成 error */ } }() - recover 后建议返回一个非 nil error(比如
fmt.Errorf("panic: %v", r)),这样g.Wait()才能统一拿到 - 不要只 log 然后忽略——这会让
g.Wait()以为该 goroutine 正常结束,可能掩盖真实失败
多个函数共享同一资源时怎么避免竞态
errgroup 本身不提供同步机制,所有并发函数共享变量时,必须自行加锁或用 channel 协作。常见错误是:多个 goroutine 直接读写同一个 map 或 struct 字段,导致 panic: concurrent map writes。
实操建议:
- 优先用
sync.Mutex或sync.RWMutex保护可变共享状态,比如汇总结果的 slice 或 map - 如果只是往 slice 追加数据,可用
sync.Once配合初始化 + 无锁追加(但需确保追加操作本身线程安全) - 更推荐 channel 方式:每个 goroutine 发送结果到同一个
chan Result,主 goroutine range 接收并聚合——天然串行、无锁、清晰
g.Wait() 返回 error 后,怎么知道是哪个函数出错
errgroup 默认只返回第一个非 nil error,不附带来源信息。你无法单靠 g.Wait() 的返回值定位具体是哪个 g.Go() 出问题。
实操建议:
- 每个 goroutine 在返回 error 前,手动加上前缀,比如
fmt.Errorf("fetch-user: %w", err) - 用结构体封装结果:定义
type TaskResult struct { Name string; Err error; Data interface{} },通过 channel 或带名字的 map 收集各任务状态 - 配合日志打点:在每个
g.Go()开头打log.Printf("[task: upload] start"),出错时 log 包含 task 名,比事后查 error 更快
真正难的不是启动并发,而是让失败可追溯、资源可释放、状态可收敛——这些细节漏掉一个,线上就容易卡死或丢数据。

















