recover必须在每个goroutine的defer中单独设置,主goroutine的recover无法捕获子协程panic;defer需在panic前注册;recover后不可继续使用已损坏对象;资源清理必须通过defer完成,而非recover分支。

recover 必须写在每个 goroutine 的 defer 匿名函数里
主 goroutine 里加 defer func() { recover() }(),对子协程里的 panic("index out of range") 完全无效。Go 的 panic 是 goroutine 隔离的,recover() 没有跨协程传播能力。你启动一个 go doWork(),就得在 doWork 函数开头、或该 goroutine 的入口匿名函数里自己配 defer。
- 错误写法:
go doWork()外层 main 函数里 defer recover —— 不会捕获,进程照样崩溃 - 正确写法:
go func() { defer func() { if r := recover(); r != nil { log.Printf("panic: %v", r) } }(); doWork() }() - 更干净的做法:封装成
safeGo(func() { ... })工具函数,内部统一包一层defer
defer 注册必须在 panic 发生前完成
defer 是按语法顺序注册的,不是运行时动态绑定。如果 panic() 出现在 defer 声明之前,那这个 defer 根本不会被执行,recover() 更无从谈起。
- 错误写法:
if cond { panic("bad") }; defer func() { recover() }()——panic先触发,defer还没注册 - 正确顺序:必须先写
defer func() { recover() }(),再调用可能 panic 的逻辑 - 注意:多个
defer时,只有 panic 触发后、函数退出前正在执行的那个defer里的recover()才可能生效
recover 后不能继续使用已破坏的对象状态
recover() 只是让 goroutine 软着陆,不修复底层损坏。比如 panic 是由并发写 map 引起的,recover 成功后那个 map 依然处于损坏状态,后续读写大概率二次 panic。
- 别在
recover分支里调用close(ch)、向刚 panic 的map写入、或解引用空指针字段 - 资源清理(如
file.Close()、mu.Unlock())应放在defer链中,而不是recover()后的业务逻辑里 - 推荐模式:
if r := recover(); r != nil { log.Printf("panic: %v", r); return }—— 记录完就退出当前函数
recover 返回值类型不确定,断言前必须检查
recover() 返回 interface{},实际类型取决于 panic() 传入的参数:可能是 string、*errors.errorString、*runtime.Error,甚至自定义 struct。直接 r.(error).Error() 会再次 panic。
- 永远先判断
r != nil,再做类型处理 - 安全打印用
fmt.Sprintf("%v", r);结构化日志建议用switch r := r.(type)分支处理 - 特别注意:Go 1.21+ 中
panic(nil)会返回*runtime.PanicNilError,不是nil,但也不实现error接口
recover() 成功了,如果没在 defer 里显式关文件、断连接、解锁,这些资源就真泄漏了。高并发服务里反复 spawn panic 的 goroutine,几小时后可能耗尽文件描述符或数据库连接池。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











