应使用 errgroup 实现快速失败,适用于任一出错即终止的场景;需配合 context 控制超时与取消;recover 无法跨 goroutine 捕获 panic;汇总全部错误应选带缓冲 error channel + WaitGroup。

Go 并发错误处理没有“银弹”,但有明确的模式选择逻辑:用 errgroup 快速失败,用带缓冲的 error channel 汇总全部结果,绝不用 recover() 跨 goroutine 捕错。
什么时候该用 errgroup?
适用于“任一出错即终止整体流程”的场景,比如接口聚合、资源预检、关键路径调用。它本质是带错误传播的 WaitGroup + context 取消联动。
- 只要任意一个 goroutine 返回非
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 的设计约束。
- 子 goroutine 发生
panic,只会导致该 goroutine 终止,并打印堆栈,不会影响其他 goroutine 或主流程 - 试图在每个 goroutine 里都写
defer recover()是反模式:掩盖真正问题、丢失上下文、无法统一处理 - 正确做法是把 panic 转为 error:比如在关键函数入口加
defer捕获并 send 到 error channel,但更推荐从源头避免 panic(如校验参数、用errors.Is()判断已知错误)
如何安全汇总所有并发任务的错误?
当业务需要知道“哪几个失败了、失败原因分别是什么”(例如批量导入、健康检查),就得放弃 errgroup 的短路机制,改用带缓冲的 error channel + sync.WaitGroup。
使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888
立即学习“go语言免费学习笔记(深入)”;
- channel 缓冲区大小必须等于任务总数,否则某个 goroutine 写错误时会阻塞,导致死锁
- 主 goroutine 必须在
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)完成,没有捷径。









