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

recover必须在defer中调用才有效
直接调用 recover() 永远返回 nil,它只在 panic 正在被传播、且当前 goroutine 还没退出时起作用。唯一可靠的方式是把它包在 defer 函数里——因为 defer 会在函数 return 前执行,此时 panic 尚未终止当前栈帧。
常见错误是写成这样:
func bad() {
recover() // 永远无效
panic("boom")
}
正确写法必须是:
func good() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到 panic:", r)
}
}()
panic("boom")
}
-
recover()只对当前 goroutine 的 panic 生效,不能跨 goroutine 捕获 - 多个 defer 按后进先出顺序执行,
recover()必须在 panic 发生后的第一个(或最内层) defer 中调用 - 如果 panic 被 recover 捕获,程序不会崩溃,但该函数会立即 return(不执行后续语句)
recover只能捕获当前函数的panic,不能“吞掉”并继续执行原逻辑
recover 不是 try-catch 那种可恢复执行流的机制。它只是让程序避免崩溃,并把控制权交还给 defer 所在函数的上层调用者——原 panic 发生点之后的代码不会再运行。
立即学习“go语言免费学习笔记(深入)”;
三层自动备份:每日时间戳快照、次级硬盘镜像、紧急对话导出。
比如下面这段代码,fmt.Println("never printed") 永远不会输出:
func example() {
defer func() {
recover()
}()
panic("stop here")
fmt.Println("never printed") // 这行被跳过
}
- recover 后无法“重试”或“回滚”已发生的副作用(如文件写入、channel 发送、map 修改)
- 若想实现类似“失败后降级”的逻辑,得靠显式错误返回 + if 判断,而不是依赖 recover
- 不要用 recover 替代
error返回——它只该用于处理真正意外的、无法预判的编程错误(如空指针解引用、越界访问)
recover捕获的是interface{},类型断言需谨慎
recover() 返回 interface{},实际值通常是 string(直接 panic 字符串)或 error(panic 错误对象),但也可能是任意类型(比如自定义 struct)。直接打印可能看不出问题,但做类型判断时容易 panic 二次崩溃。
安全做法是用 type switch 或 ok-idiom:
defer func() {
if r := recover(); r != nil {
switch v := r.(type) {
case string:
log.Printf("panic string: %s", v)
case error:
log.Printf("panic error: %v", v)
default:
log.Printf("unknown panic type: %T, value: %+v", v, v)
}
}
}()
- 避免直接用
r.(error).Error()——如果 r 不是 error 类型,会再次 panic - 不要假设 panic 一定来自
panic("msg"),生产环境里常有第三方库 panic 自定义类型 - 日志中建议同时记录
%T和%+v,方便排查未知 panic 来源
HTTP handler 中 recover 的典型误用与正解
很多人在 HTTP handler 里加 defer+recover,以为能兜住所有错误。但要注意:recover 只捕获当前 goroutine 的 panic,而 Go 的 http.Server 默认为每个请求启一个新 goroutine,所以 handler 里的 defer 是有效的;但若你在 handler 内部另起 goroutine(比如 go func(){...}()),那个 goroutine 的 panic 就完全逃逸了。
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, "Internal Error", http.StatusInternalServerError)
log.Printf("handler panic: %v", r)
}
}()
// ✅ 这个 panic 能被捕获
panic("in main goroutine")
// ❌ 这个 panic 完全丢失,server 可能 crash 或静默失败
go func() {
panic("in new goroutine")
}()
}
- 跨 goroutine panic 必须靠
recover放在那个 goroutine 内部,或统一用sync.Pool+ 日志上报兜底 - 不要在 recover 后继续 write header/body——万一之前已部分写出,再写会触发
http: response.WriteHeader on hijacked connection - recover 后应明确返回,避免后续逻辑误执行(比如数据库事务没 rollback)

















