recover必须在defer函数中调用才有效,普通函数体中调用恒返回nil;它仅对当前goroutine的panic生效,无法跨goroutine捕获;recover后状态可能已损坏,需谨慎处理副作用。

recover 必须在 defer 函数里调用才有效
直接在普通函数体里写 recover() 永远返回 nil,它不会捕获任何 panic。这是最常被忽略的前提条件。
原因在于:Go 的 recover 设计为仅在 panic 正在“展开”(unwinding)过程中、且当前 goroutine 尚未退出时生效;而只有 defer 函数能保证在 panic 触发后、函数返回前执行,从而获得这个窗口期。
- 错误写法:
if r := recover(); r != nil { ... }放在函数开头或中间 - 正确写法:必须包在
defer func() { ... }()里,且该defer要在可能 panic 的代码之前注册 - 顺序很重要:如果
defer写在panic之后,它根本不会被执行
HTTP handler 中 recover 不起作用的典型场景
你在主 handler 里加了 defer + recover,但子 goroutine 里 panic 了,日志没报错、连接却断了——这说明 panic 发生在另一个 goroutine,而你的 recover 只对当前 goroutine 有效。
Go 的 panic/recover 是 goroutine 局部机制,跨 goroutine 无法传递或捕获。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 常见翻车点:
go func() { riskyWork() }()里 panic,外层 handler 的 defer 完全无感 - 正确做法:每个可能 panic 的 goroutine 内部都得有自己的
defer func() { recover() }() - 别指望“全局 recover”,Go 没有这种东西;框架层(如 Gin、Echo)的 recovery 中间件,本质也是在每个请求 goroutine 入口处插 defer
recover 后程序能继续运行,但状态可能已损坏
recover 确实能阻止程序崩溃,但它不回滚内存、不关闭已打开的文件、不重置变量值。你只是“跳过”了 panic 后的代码,但前面的副作用还在。
比如在 panic 前已写入部分数据到缓存、已发送半条 HTTP 响应、已修改了共享 map —— 这些都不会自动撤销。
- recover 后不要盲目继续业务逻辑,尤其涉及状态变更时
- 建议只做清理(close file, unlock mutex)、记录日志、返回错误响应
- 避免在 recover 块里再调用可能 panic 的函数,容易二次崩溃
- 如果 panic 来自空指针解引用或数组越界,说明代码有严重缺陷,不应靠 recover 掩盖,而应修复根本问题
panic 参数类型和 recover 类型断言的实际处理
panic() 接收 interface{},所以你可以传 string、error、甚至自定义结构体。但 recover() 返回的也是 interface{},不做类型断言就只能当 interface{} 用,没法直接打印或判断。
线上服务通常需要区分 panic 类型,避免把 error 当 string 打印出乱码,或漏掉关键错误上下文。
- 推荐写法:
switch r := recover().(type) { case string: log.Println("panic string:", r) case error: log.Println("panic error:", r) default: log.Printf("panic unknown type %T: %v", r, r) } - 不要直接
fmt.Println(recover()),遇到 error 类型会输出<nil>或地址 - 第三方库抛出的 panic 往往是
error,比如encoding/json内部 panic 的就是fmt.Errorf
defer + recover 的语法,而是判断该不该 recover、在哪一层 recover、recover 之后敢不敢继续跑业务逻辑。多数 panic 本不该发生,强行 recover 只会让问题更隐蔽。

















