recover必须在defer中调用才有效,直接调用永远返回nil;仅对当前goroutine的panic生效,需在panic发生前注册defer,且必须在defer函数体内调用,捕获后函数立即return,无法恢复执行流或回滚副作用。

recover必须在defer中调用才有效
Go里recover本身不捕获panic,它只在当前goroutine的defer函数执行期间有作用。如果写成普通函数调用,比如recover()放在if err != nil分支里,它永远返回nil,起不到恢复效果。
常见错误是把recover写在顶层逻辑里,或者试图在子函数里单独调用:
func badExample() {
recover() // 这里完全无效
panic("boom")
}
正确做法是立即配合defer,且defer语句必须在panic发生前注册:
-
defer要写在可能触发panic的代码之前(比如在函数开头) - 不能把
recover封装进另一个函数再调用,必须直接出现在defer语句中 - 如果用了匿名函数做defer,注意闭包变量捕获时机——别误用外部已变更的变量
recover返回值为interface{},需类型断言才能获取原始panic值
recover()返回的是interface{},不是error,也不是字符串。直接打印或比较会出问题,比如recover() == "oops"永远为false。
典型场景是想区分不同panic原因做不同处理:
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
log.Printf("panic as string: %s", x)
case error:
log.Printf("panic as error: %v", x)
default:
log.Printf("unknown panic type: %T, value: %v", x, x)
}
}
}()
注意点:
- 不要忽略
default分支,panic可以是任意类型(包括自定义结构体) - 如果panic是
nil,recover()也返回nil,所以第一层判断r != nil不能省 - 避免对
recover()结果做非安全类型转换,比如r.(error).Error()可能panic
HTTP handler中recover要包裹整个handler逻辑,而非仅业务函数
Web服务里最常漏掉的是recover范围太小。比如只在某个数据库操作外加defer,但HTTP handler里还有日志、header设置、JSON序列化等环节,任一环节panic都会导致连接中断。
正确姿势是在handler函数最外层统一recover:
func myHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("Panic recovered: %v", r)
}
}()
// 整个业务逻辑放在这里,包括DB、JSON、模板等
data := riskyDBQuery()
json.NewEncoder(w).Encode(data)
}
关键细节:
- 别在中间件里只recover一次就完事——每个handler仍需独立defer,除非你用统一中间件包装所有路由
- recover后记得设置正确的HTTP状态码,否则默认200会误导客户端
- 不要在recover里尝试重试或修复数据,panic说明程序状态已不可信,只做记录和降级响应
goroutine泄漏时recover无法阻止进程崩溃
很多人以为加了recover就能“兜住”所有panic,但goroutine里未捕获的panic只会终止该goroutine,不会让主进程退出;可如果主goroutine(比如main函数)panic且没recover,整个程序就结束了。
更隐蔽的问题是:启动了新goroutine但忘了加recover,比如:
go func() {
panic("in goroutine") // 这里没recover,会打印stacktrace但不终止进程
}()
这时候看似“没事”,实则可能掩盖严重问题:
- panic信息只打到stderr,容易被日志系统忽略
- goroutine退出后,它持有的资源(如文件句柄、DB连接)未必被释放
- 如果大量goroutine反复panic,会堆积死goroutine,最终OOM
真正需要关注的,是那些本该由主流程控制、却意外脱离生命周期的goroutine——它们的panic往往意味着设计缺陷,而不是靠recover能掩盖的。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











