recover 只在 defer 匿名函数中调用才有效,且仅能捕获同 goroutine 的 panic;它不跨协程、不修复状态,仅实现软着陆。

recover 只在 defer 函数里调用才有效,写在普通代码流中永远返回 nil;它不兜底、不跨协程、不修复状态,只是让当前 goroutine 软着陆。
recover 必须写在 defer 匿名函数里
这是最常翻车的点:把 recover() 直接写在函数体里,或者塞进普通 if 块、提前 return 分支里,它根本不会触发,更别说捕获了。
-
defer必须在panic发生前注册(语法顺序上要“先声明、后 panic”) - 必须是
defer func() { ... recover() ... }()这种形式;defer recover()是错的——那会在注册时就执行recover(),此时还没 panic - 如果函数里有多个
defer,只有在 panic 触发后、函数退出前正在执行的那个defer里的recover()才可能生效
recover 只能捕获本 goroutine 的 panic
主线程里加了 defer+recover,对子协程里 panic("index out of range") 完全没反应。进程照样崩溃,控制台打出完整堆栈后退出。
- 每个 goroutine 的 panic 是隔离的,
recover()没有跨协程传播能力 - 启动新协程时,必须自己包一层
defer func() { if r := recover(); r != nil { ... } }() - 别指望“全局 recover 中间件”——HTTP handler 里要防止单个请求崩掉整个服务,就得在每个 handler 函数开头写
defer;worker 协程也得各自防护
recover 后不能继续用已破坏的状态
数组越界后 recover() 成功了,不代表那个 slice 还安全;map 已被并发写入导致内部结构损坏,recover() 后再读它可能二次 panic。
- 不要在
recover()后继续调用close(ch)、向已 panic 的 map 写入、或访问刚解引用的空指针字段 - 推荐模式:
if r := recover(); r != nil { log.Printf("panic: %v", r); return }—— 记录完就退出当前函数 - 若需清理资源(如关闭文件、释放锁),放在
defer链里,而不是recover()后的业务逻辑中
recover 返回值是 interface{},类型断言前必须检查
panic("oops") 和 panic(errors.New("db fail")) 返回的类型完全不同,直接 r.(error).Error() 会再次 panic。
- 永远先判断
r != nil,再做类型处理 - 安全打印用
fmt.Sprintf("%v", r);结构化日志建议用类型 switch:
switch x := r.(type) {
case string:
log.Printf("panic string: %s", x)
case error:
log.Printf("panic error: %v", x)
default:
log.Printf("panic unknown type %T: %v", x, x)
}
真正难的不是写出那三行 defer func() { recover() }(),而是 panic 发生后,你得清楚哪些变量还可信、哪些操作绝对不能再碰——这没法靠语法检查,只能靠对代码路径和数据状态的理解。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











