recover只能在同goroutine的defer匿名函数中直接调用才有效;跨goroutine、非defer场景或写成defer recover()均无效,必须用defer func(){if r:=recover();r!=nil{}}()形式。

recover 只能在同 goroutine 的 defer 匿名函数中调用才有效,跨 goroutine、非 defer 场景、或直接写 defer recover() 都捕不到 panic,程序照样崩溃。
recover 必须在 defer 匿名函数里调用
这是最常踩的坑:把 recover() 写成普通语句,或者写成 defer recover()。前者永远返回 nil,后者会在 defer 注册时就执行(还没 panic 就调了),根本起不到捕获作用。
正确写法只有一种:用 defer func() { recover() }() 包裹。
-
defer func() { if r := recover(); r != nil { /* 处理 r */ } }()✅ -
defer recover()❌(注册即执行,返回nil) -
if r := recover(); r != nil { ... }❌(不在 defer 中,永远为nil) -
go func() { defer func() { recover() }() }()✅(但仅对这个 goroutine 有效)
子 goroutine 的 panic 必须自己 recover
主 goroutine 里写的 defer + recover 对任何 go func() { panic(...) }() 完全无效。Go 明确规定 recover 是 goroutine 局部的,不跨协程传递。
立即学习“go语言免费学习笔记(深入)”;
典型现象:加了 recover 还是看到完整 stack trace 并退出——因为 panic 发生在子 goroutine,它自己没设 recover,runtime 直接终止进程。
- 每个可能出错的子 goroutine 都得自己写
defer func() { recover() }() - HTTP handler、定时任务、连接保活等长期运行的 goroutine 是高频漏点
- 不要依赖
sync.WaitGroup或chan同步来“拦住”未捕获的 panic
recover 返回值必须安全处理
recover() 返回 interface{},直接类型断言(如 err.(error).Error())极易二次 panic——比如 panic 传的是字符串,断言成 *os.PathError 就崩了。
推荐做法是先判断再分支,或统一转字符串输出:
- 用
fmt.Sprint(r)输出,避免类型断言失败 - 真要区分类型,先
if r != nil,再switch r.(type) - 生产环境建议搭配
debug.Stack()记录堆栈,别只打一行日志 - recover 后不能继续原逻辑——状态已不可信,应立即
return或转入清理分支
panic 不是 error 替代品
数组越界、空指针解引用、并发写 map 这类 panic,本该在开发/测试阶段暴露,而不是靠 recover 挽救。它们表示程序状态已损坏,无法安全继续。
真正适合用 panic/recover 的场景很窄:
- 第三方库内部抛出的不可控 panic(如
json.Unmarshal遇到非法结构) - HTTP server 中防止单个请求崩溃整个服务
- 关键路径上做最后兜底(如配置加载失败后 fallback 到默认值)
别把它当 try-catch 用;一旦 recover 后还继续跑业务逻辑,大概率埋下数据不一致或资源泄漏的坑。


















