recover必须在defer函数内部直接调用才有效,仅能捕获同goroutine内未被处理的panic;写在普通代码中、跨goroutine或通过中间函数间接调用均返回nil。

recover必须在defer中调用才有效
Go里recover只能捕获当前goroutine中由panic触发的异常,而且**必须在defer函数内部调用才有意义**。如果写成普通函数调用,或在defer外提前调用,recover会返回nil,起不到任何作用。
常见错误是把recover写在业务逻辑中间,比如:
func handleTask() {
recover() // ❌ 无效:不在defer中,且panic还没发生
doWork()
}
正确姿势是:
func handleTask() {
defer func() {
if r := recover(); r != nil {
log.Printf("task panicked: %v", r)
}
}()
doWork() // 可能panic的地方
}
-
defer注册的函数会在函数返回前执行,此时若发生了panic,recover()才能拿到它 - 必须用匿名函数包裹
recover(),否则函数字面量会被立即求值 - 不要在
defer里直接写recover()(如defer recover()),那只是调用并丢弃返回值
延迟任务中recover无法跨goroutine生效
如果你用go handleTask()启动一个新goroutine执行任务,那么主goroutine里的defer+recover对它完全无效——每个goroutine有独立的panic/recover机制。
立即学习“go语言免费学习笔记(深入)”;
典型误用场景:
func startDelayedJob() {
defer func() {
if r := recover(); r != nil {
log.Println("caught in main goroutine") // ✅ 永远不会触发
}
}()
go func() {
panic("job failed") // ❌ 主goroutine收不到
}()
}
解决办法只有一个:**每个可能panic的goroutine内部都要有自己的defer+recover**。
- 延迟任务(如定时器、消息队列消费者)通常运行在独立goroutine中,必须在该goroutine入口处加保护
- 如果用
time.AfterFunc或worker pool模式,recover要放在实际执行函数里,而不是调度层 - 别指望上层统一recover——Go不支持跨goroutine异常传递
recover后程序继续运行但状态可能已损坏
recover能阻止panic崩溃,但**不会回滚变量修改、关闭打开的文件、释放锁或重置channel状态**。如果panic发生在资源操作中途,recover后很可能留下不一致状态。
例如:
func processFile(path string) {
f, _ := os.Open(path)
defer f.Close()
defer func() {
if r := recover(); r != nil {
log.Printf("recovered: %v", r)
}
}()
parseHeader(f) // panic here → f.Close()没执行
parseBody(f) // 不会执行
}
这里f.Close()因defer绑定在函数退出时执行,而panic导致函数提前终止,但defer仍会运行——不过顺序是“先defer后recover”,所以f.Close()其实会执行。但更危险的是像下面这种:
- 手动加了
mu.Lock()但没配defer mu.Unlock(),panic后锁永远不释放 - 往无缓冲channel发送数据失败panic,接收方可能永远阻塞
- 数据库事务已提交部分语句,recover后无法自动回滚
结论:recover不是try-catch,它只是“止血”,不是“修复”。关键资源操作必须配对管理,不能依赖recover兜底。
日志和错误分类比recover更重要
很多团队花大量时间优化recover逻辑,却忽略真正关键的问题:panic是否可预期?是否应该被recover?
Go官方明确建议:只对可预期的、局部的、非致命的错误做recover,比如HTTP handler里模板渲染失败、JSON解析出错;而空指针解引用、数组越界、channel已关闭还读写等,属于程序bug,应该让panic暴露出来,而不是静默吞掉。
- 在延迟任务中,网络超时、第三方API返回异常码,适合用error处理,而非触发panic再recover
- 如果某段代码频繁panic又recover,大概率说明设计有问题:要么该用error返回,要么缺少前置校验
- recover后务必记录完整堆栈(用
debug.PrintStack()或runtime/debug.Stack()),否则很难定位根本原因
真正难的从来不是怎么写recover,而是判断哪些panic值得recover、哪些该立刻停服修复——这需要结合业务语义,不是套个defer就能解决的。


















