

新闻资讯
技术学院应使用 errgroup 实现快速失败,适用于任一出错即终止的场景;需配合 context 控制超时与取消;recover 无法跨 goroutine 捕获 panic;汇总全部错误应选带缓冲 error channel + WaitGroup。
Go 并发错误处理没有“银弹”,但有明确的模式选择逻辑:用 errgroup 快速失败,用带缓冲的 error channel 汇总全部结果,绝不用 recover() 跨 goroutine 捕错。
errgroup?适用于“任一出错即终止整体流程”的场景,比如接口聚合、资源预检、关键路径调用。它本质是带错误传播的 WaitGroup + context 取消联动。
nil 错误,g.Wait() 立即返回该错误,其余仍在运行的任务会收到 ctx.Done() 信号(前提是传入了带 cancel 的 context)ctx, cancel := context.WithTimeout(r.Context(), 300*time.Millisecond),否则慢请求会拖垮整个响应周期g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
return fetchFromRedis(ctx, keys)
})
g.Go(func() error {
return fetchFromMySQL(ctx, ids)
})
if err := g.Wait(); err != nil {
// 这里拿到的是第一个发生的错误,不是所有错误
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}recover() 捕获子 goroutine 错误?因为 panic 是 goroutine 局部的,主 goroutine 的 defer + recover() 完全感知不到其他 goroutine 的 panic —— 这不是疏忽,是 Go 的设计约束。
panic,只会导致该 goroutine 终止,并打印堆栈,不会影响其他 goroutine 或主流程defer recover() 是反模式:掩盖真正问题、丢失上下文、无法统一处理defer 捕获并 send 到 error channel,但更推荐从源头避免 panic(如校验参数、用 errors.Is() 判断已知错误)当业务需要知道“哪几个失败了、失败原因分别是什么”(例如批量导入、健康检查),就得放弃 errgroup 的短路机制,改用带缓冲的 error channel + sync.WaitGroup。
wg.Wait() 后才关闭 channel,否则 range 可能提前退出,漏掉错误nil)也要 send 进去,否则无法区分“成功”
和“未执行”errs := make(chan error, len(tasks))
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1)
go func(t Task) {
defer wg.Done()
if err := t.Run(); err != nil {
errs <- err
} else {
errs <- nil // 显式标记成功
}
}(task)
}
go func() {
wg.Wait()
close(errs)
}()
for err := range errs {
if err != nil {
log.Warn("task failed", zap.Error(err))
}
}最常被忽略的一点:错误不是用来“吞掉”的,而是要决定“谁来处理、何时处理、是否重试”。并发错误处理的核心,其实是把错误从 goroutine 的局部状态,变成主流程可调度、可观测、可决策的值——而这个转变,必须靠显式传递(channel / errgroup)完成,没有捷径。