recover必须在defer中调用才有效,且仅对当前goroutine中正在传播的panic起作用;普通调用、跨goroutine、runtime致命错误均无法捕获。

recover 必须在 defer 中调用才有效
Go 的 recover 不是全局异常捕获机制,它只在当前 goroutine 的 panic 正在被传播、且尚未退出函数时起作用。如果你写成普通调用(比如放在 if 分支里),recover 永远返回 nil,根本拦不住崩溃。
常见错误写法:
func badHandler() {
if err := riskyOp(); err != nil {
recover() // ❌ 完全无效,panic 还没发生
}
}正确姿势是搭配 defer,确保它在函数即将返回前执行:
func goodHandler() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic recovered: %v", r)
}
}()
riskyOp() // 这里 panic,defer 中的 recover 才能捕获
}-
recover()必须出现在defer匿名函数内部,不能提前调用或放在其他 goroutine 里 - 如果函数内有多个
defer,recover要放在最靠近 panic 发生点的那个defer中(通常就是最外层的) - recover 只对本 goroutine 有效,无法跨 goroutine 捕获 panic
recover 无法恢复已关闭的 channel 或已释放的资源
recover 只能停止 panic 的传播、让程序继续执行,但它不回滚状态。如果 panic 发生前已经关闭了 channel、写了文件、发了 HTTP 请求,这些操作不会自动撤销。
立即学习“go语言免费学习笔记(深入)”;
典型陷阱场景:
- 在
defer close(ch)后又 panic →recover成功,但 channel 已关,后续向它发送数据会直接 panic(且这次无法再 recover) - 数据库事务未显式 rollback,仅靠 recover 会让数据处于不一致状态
- HTTP handler 中 panic 后 recover 了,但 response writer 可能已被写入部分 header,客户端收到半截响应
所以必须配合显式清理逻辑:
func handleRequest(w http.ResponseWriter, r *http.Request) {
ch := make(chan int, 1)
defer close(ch) // 即使 panic 也会执行
<pre class="brush:php;toolbar:false;">defer func() {
if r := recover(); r != nil {
log.Println("recovered:", r)
// 补充清理:比如 rollback tx、重置状态变量等
}
}()
// ... 可能 panic 的业务逻辑}
三层自动备份:每日时间戳快照、次级硬盘镜像、紧急对话导出。
不要在 main 函数里滥用 recover 全局兜底
很多人想在 main 函数开头加个 defer recover 来“防止整个程序崩溃”,这看似保险,实则掩盖严重问题。
问题在于:
- panic 往往源于不可恢复的错误:空指针解引用、越界访问、栈溢出 —— recover 后程序状态可能已损坏,继续运行风险更高
- main 中 recover 住 panic 后,程序会正常退出(因为 main 返回),但你失去了 panic 堆栈,难以定位根因
- 标准库和第三方包的初始化 panic(如
init函数中)根本不在 main 的 defer 范围内,完全捕获不到
更合理的做法是:
- 只在明确可控的边界处使用 recover,比如 HTTP handler、RPC 方法、用户输入解析入口
- 对关键服务,用进程级监控(如 systemd、supervisord)自动拉起崩溃进程,而不是靠 recover 硬扛
- 把 panic 当作 bug 处理,配合
log.SetFlags(log.Lshortfile)和堆栈打印,而不是静默吞掉
recover 对 runtime 错误无效
Go 的 recover 只能捕获由 panic() 显式触发或某些语言级错误(如 nil 指针解引用、slice 越界)引发的 panic。它对底层 runtime 崩溃无能为力。
以下情况 recover 完全不起作用:
-
fatal error: stack overflow(栈溢出) -
fatal error: all goroutines are asleep - deadlock(死锁) -
fatal error: out of memory(内存耗尽) - Cgo 调用导致的 segfault(除非用
runtime.LockOSThread配合信号处理,但这已超出 recover 范畴)
这类错误意味着 Go 运行时自身已无法维持基本运转,程序必然终止。试图用 recover 拦截只会徒增困惑 —— 如果你频繁遇到这些,应该检查递归深度、goroutine 泄漏、内存泄漏或 Cgo 使用方式。
真正需要警惕的是:当 recover 没生效时,别急着改逻辑,先确认 panic 类型是否属于 runtime fatal class;否则所有日志和 defer 都是白忙活。

















