recover必须在defer匿名函数中调用,直接调用或在panic前调用均无效;仅能捕获同goroutine内panic;recover后不应继续使用可能已损坏的对象;返回值需类型检查再处理。

recover 必须在 defer 匿名函数里调用
直接写 recover() 永远返回 nil,哪怕它紧挨着 panic()。Go 不会“扫描上下文”去匹配 recover 和 panic,只看执行时的 goroutine 状态和调用栈位置。
常见错误写法:
-
func f() { recover(); panic("x") }——recover()在 panic 前就执行了,此时没 panic,返回 nil -
defer recover()—— 这是立即调用recover(),不是注册延迟行为 -
if err != nil { recover() }—— 条件分支里调用,依然无效
正确写法只有一种模式:defer func() { if r := recover(); r != nil { /* 处理 */ } }()。注意括号:defer func() { ... }() 是注册并执行闭包;defer func() { ... }(缺最后括号)根本不会运行。
recover 不能跨 goroutine 捕获 panic
主线程里加了 defer + recover,对子协程里的 panic("index out of range") 完全没反应。子 goroutine 崩溃后静默终止,主 goroutine 毫无感知,进程也不会崩溃,但资源(如文件、连接、锁)可能泄漏。
必须每个可能 panic 的 goroutine 自己配防护:
- HTTP handler 启动异步任务?在 goroutine 内部第一行就写
defer func() { recover() }() - 不要试图在
main()里统一加 recover——它只管自己那条调用链 - 封装工具函数可减少重复,例如:
safeGo(func() { ... }),但内部仍要为每个 goroutine 单独 defer
没有“全局 recover 中间件”这回事,框架(如 Gin)自带的 recover 中间件,也只是在每个 handler 函数入口处插了一层 defer。
recover 后不能继续使用已破坏的对象
recover() 成功只代表“没崩溃”,不代表程序状态安全。数组越界后,那个 slice 的底层数组可能已被 runtime 标记为不可访问;map 被并发写入后内部哈希表结构损坏,recover() 后再读它大概率二次 panic。
典型危险操作:
- 在
recover()分支里继续调用close(ch)(channel 可能已关闭或 nil) - 对刚触发空指针 panic 的 struct 字段再次解引用
- 向已处于 panic 状态的 map 写入新 key
推荐做法:记录日志后立即 return,不往下走业务逻辑;清理资源(如 file.Close())应放在独立的 defer 链里,而非 recover() 的 if 分支中。
recover 返回值必须做类型检查再处理
recover() 返回 interface{},实际类型完全取决于 panic() 传入什么:可能是 string、*errors.errorString、自定义 error、甚至 int 或未导出的 runtime 类型。直接 r.(error).Error() 会 panic。
安全处理方式:
- 先判断
r != nil,再决定是否处理 - 日志打印用
fmt.Sprintf("%v", r)最稳妥,不依赖类型断言 - 需结构化区分错误类型时,用
switch r := r.(type),并覆盖default:分支防漏 - 别假设所有 panic 都是
error接口——runtime 抛的index out of range就不是
最常被忽略的是:recover 后继续执行的代码,可能依赖一个本该由 panic 前逻辑初始化的变量,而那个变量根本没来得及赋值——这不是 recover 的问题,是控制流误判。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











