recover必须在defer中调用才有效,仅能捕获当前goroutine的panic,且需在panic前注册;它返回interface{},须类型断言处理,无法捕获runtime.fatalerror等致命错误。

recover 必须在 defer 中调用才有效
recover 不是全局错误处理器,它只在当前 goroutine 的 defer 函数中调用时才可能捕获到 panic。如果写成普通函数调用(比如直接在 main 里调 recover()),返回值永远是 nil,什么也抓不到。
常见错误写法:
func main() {
recover() // 永远无效
panic("boom")
}
正确姿势是把 recover 包在 defer 的匿名函数里:
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到 panic:", r)
}
}()
panic("boom") // 这里触发,defer 中的 recover 才能生效
}
-
defer必须在 panic 发生前注册,否则来不及执行 - 多个
defer按后进先出顺序执行,recover应该放在最靠近 panic 触发点的defer里(或至少确保它没被更早的defer提前“吃掉”) - 如果 panic 被上层函数的
defer捕获了,下层就再也收不到 ——recover是单次、不可重入的
recover 只能捕获当前 goroutine 的 panic
Go 的 panic 不跨 goroutine 传播。在一个新 goroutine 里 panic,主 goroutine 完全感知不到,也不会崩溃,但也不会被主 goroutine 的 defer + recover 捕获。
例如:
OpenClaw 基础设施心跳监控与自动恢复:部署/卸载心跳守护进程,配置主机监控(Gateway、磁盘、内存、CPU、进程),设置飞书告警,自动重启故障Gateway。适用关键词:心跳监控、heartbeat daemon、服务器告警、自动重启、健康检查、auto recovery。
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
go func() {
panic("in goroutine") // 主 goroutine 的 recover 看不见它
}()
time.Sleep(10 * time.Millisecond)
}
- 子 goroutine 的 panic 默认会导致整个程序 crash(除非它自己内部有
defer+recover) - 想统一处理,得在每个可能 panic 的 goroutine 入口手动加
defer块 - 第三方库启动的 goroutine(如
http.Server处理请求时)同样需要各自 recover,不能依赖外层
recover 返回值类型是 interface{},需小心断言
recover() 返回的是 interface{},不是 error。虽然大多数 panic 是用 panic(err) 或 panic(string) 触发的,但你不能直接当 error 用,否则会 panic(空接口转具体类型失败)。
安全做法是用类型断言 + 判断:
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 类型: %T, %v", x, x)
}
}
- 忽略类型直接强转
r.(error)在 panic 是字符串时会再次 panic - 不检查
r == nil就做断言,逻辑上没问题,但习惯上先判空更稳妥 - 有些 panic 是自定义结构体,比如
panic(&MyPanic{Code: 500}),需要对应断言
recover 无法捕获 runtime.FatalError 类型崩溃
像栈溢出(stack overflow)、内存耗尽(out of memory)、或者直接调用 runtime.Goexit() 以外的致命错误(如 throw 系统级 panic),recover 完全无效。这类 panic 会绕过 defer 链,直接终止程序。
典型例子:
func stackOverflow() {
stackOverflow() // 无限递归,最终触发 runtime: goroutine stack exceeds 1000000000-byte limit
}
- 这种 panic 不会进入任何
defer,recover根本没机会运行 - 日志里看到的是
fatal error:开头,而不是panic:,这是关键区分点 - 只能靠预防:限制递归深度、监控内存使用、用
runtime/debug.SetMaxStack(仅限调试)等
真正难缠的不是 recover 不会用,而是它根本没机会用 —— 很多崩溃发生在你完全没预料到的位置,比如 cgo 调用 segfault、或 syscall 返回异常 errno 后没检查就继续执行。这时候靠 recover 不如靠日志、pprof 和测试覆盖。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










