recover必须在defer函数中调用才有效,否则返回nil;它仅在panic发生且执行到defer时捕获当前goroutine的异常,无法跨协程传播,且recover后函数立即终止,不执行后续语句。

recover必须在defer函数里调用,否则返回nil
这是最常踩的坑:直接在普通函数体里写recover(),结果永远拿不到panic值。因为recover()只在当前goroutine处于panic状态、且调用栈正在展开、同时执行到某个defer函数时才有效。一旦离开defer上下文,它就退化成一个“什么也不做、只返回nil”的函数。
常见错误写法:
func badExample() {
if r := recover(); r != nil { // ❌ 永远不进这个分支
log.Println("never reached")
}
panic("boom")
}
正确姿势是把recover()包在defer注册的匿名函数里:
-
defer必须出现在可能触发panic的代码之前(顺序很重要) - 匿名函数内部才能安全调用
recover() - 如果函数里有多个
defer,只有最靠近panic位置的那个(按LIFO顺序最后执行的)有机会捕获
recover只能捕获当前goroutine的panic
Go没有跨goroutine异常传播机制。recover()对其他goroutine里发生的panic完全无感。如果你启动了一个go f(),而f()里panic了,主goroutine里的recover()不会起作用——那个goroutine会直接崩溃,除非它自己配了defer+recover()。
立即学习“go语言免费学习笔记(深入)”;
典型误判场景:
- HTTP handler里写了全局
defer recover(),但子goroutine中panic导致连接中断却没日志 - worker pool中某个goroutine panic,整个pool静默退出
- 用
time.AfterFunc或http.TimeoutHandler触发的回调panic,主流程无法感知
解决方案很简单:每个可能独立运行的goroutine入口都得自带保护:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("worker panic: %v", r)
}
}()
doWork()
}()
recover后原函数立即终止,不会继续执行后续语句
很多人以为recover能“续上”panic前的执行流,比如在panic("x")之后还能打印日志或返回值——这是错的。一旦panic发生,当前函数从panic点开始向上回溯,所有未执行的语句(包括panic行之后的代码)全部跳过。recover只是让defer函数能跑完,并让调用者继续往下走。
三层自动备份:每日时间戳快照、次级硬盘镜像、紧急对话导出。
看这个例子:
func example() string {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
panic("fail now")
fmt.Println("this line is never reached") // ❌
return "done" // ❌
}
所以别指望recover能帮你“修复错误然后重试”,它的角色是“止损+清理+降级”,不是“事务回滚并重入”。
关键点:
- recover成功后,函数立刻return(隐式或显式),不会执行panic之后的任何逻辑
- 想带出错误信息?得靠
recover()返回值,再手动构造error或写入日志 - 资源清理必须放在defer里,不能依赖panic后的代码段
recover拿到的是interface{},类型断言容易panic
recover()返回interface{},实际值可能是string、error、自定义结构体,甚至nil(比如recover被调用两次)。直接fmt.Printf("%s", r)或r.Error()会触发新的panic。
安全做法永远先判断类型:
- 用
if r, ok := recover().(error); ok捕获error类型 - 用
switch v := recover().(type)做多类型分发 - 兜底用
fmt.Sprintf("%v", r),别用%s或.Error()
更稳妥的模板:
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
log.Printf("panic: %s", x)
case error:
log.Printf("panic: %v", x)
default:
log.Printf("panic: unknown type %T, value %+v", x, x)
}
}
}()
真正难处理的从来不是recover本身,而是panic发生时那些已经半途而废的资源状态——文件没关、锁没放、连接没断。recover只是给你一个关门机会,门后面有没有漏风,得靠你自己的defer逻辑兜住。

















