goroutine 内 panic 无法被外层 recover 捕获,必须在同 goroutine 内 defer recover;safego 封装可统一兜底,errgroup.group 仅捕获首个 panic,recover 后须立即 return 并手动清理资源。

goroutine 内 panic 无法被外层 recover 捕获
直接在 main 或 HTTP handler 里写 defer recover(),对子 goroutine 的 panic 完全无效。这是最常踩的坑——你以为兜住了,其实 panic 的 goroutine 已静默退出,任务丢、日志漏、channel 卡死、资源不释放。
根本原因是:Go 的 panic 只在当前 goroutine 内传播,recover() 必须在同 goroutine 的 defer 中调用,且必须在 panic 发生前注册(即 defer 语句要写在可能 panic 的代码之前)。
- 错误写法:
go func() { panic("boom") }()外面包defer recover() - 正确写法:每个 goroutine 自己处理,例如
go func() { defer func() { if r := recover(); r != nil { log.Printf("panic: %v", r) } }(); doWork() }() - recover 后不要继续执行业务逻辑——栈已损坏,变量可能部分初始化,继续跑容易引发数据竞争或状态不一致
用 safeGo 封装实现统一 panic 隔离
手动给每个 go 前加 defer/recover 容易遗漏、难维护。封装一个 safeGo 函数能强制兜底,同时保留堆栈和上下文。
推荐实现(带日志和完整堆栈):
func safeGo(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 64
- 闭包变量需显式捕获:传
safeGo(func() { handle(ctx, reqID) }),而不是safeGo(handle),避免引用外部变量导致状态错乱 - 不建议在
safeGo里做错误上报或重试——那是业务逻辑,应由调用方决定;safeGo只负责“不崩、不丢、可查” - 如果需要反馈 panic,可用带缓冲的
chan error,但别让主 goroutine 阻塞等待它
errgroup.Group 的 panic 隔离能力与陷阱
errgroup.Group 确实内置了 recover,但它只记录第一个 panic,并在 g.Wait() 时重新抛出——这意味着你仍需在调用 g.Wait() 处加 defer recover(),否则整个服务会崩溃。
-
g.Go(func() error { ... })内部已包裹 recover,但仅保存首次 panic;后续 goroutine panic 被忽略,静默丢失 - 必须这样用:
defer func() { if r := recover(); r != nil { log.Printf("errgroup panic: %v", r) } }(); g.Wait() - 它不会帮你清理资源或通知其他 goroutine;panic 后该 goroutine 已退出,
g.Wait()仍会等完所有未 panic 的任务 - 如果你依赖
g.Wait()返回 error 来判断失败,记得:panic 不等于 error,它绕过 error 返回路径
并发 panic 隔离后怎么安全收尾
recover 只是阻止 panic 向上冒泡,不代表 goroutine 能继续执行。真正难的是“收尾”:如何确保资源释放、状态一致、不卡死等待。
- 务必在 recover 后立即
return,或通过done := make(chan error, 1)发送错误并 return - 若用了
sync.WaitGroup,recover 后必须调用wg.Done(),否则wg.Wait()永远阻塞 - 若用了 context,recover 后应检查
ctx.Err()并响应取消,避免孤儿 goroutine - 切勿在 recover 后继续使用 panic 路径中涉及的变量(如未关闭的
*os.File、未 unlock 的sync.Mutex)
复杂点在于:panic 可能发生在任意深度的函数调用中,而 recover 只能捕获顶层 goroutine 入口处的 panic。所以隔离不是终点,而是起点——你要设计好每个并发任务的“最小可恢复单元”,并在边界处明确切断、记录、退出。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











