defer 在 panic 后仍执行,但仅限 panic 发生前已注册的 defer;它们按后进先出顺序执行,且 recover 必须在 defer 函数内直接调用才有效。

defer 在 panic 后到底还执行不执行
执行,但只执行到 panic 发生位置之前已注册的 defer。这是最常被误解的一点——很多人以为 panic 会直接跳过所有 defer,其实 Go 的 runtime 会在 panic 触发后,按栈逆序执行当前 goroutine 中已入栈但尚未执行的 defer 函数。
注意:defer 是“注册时捕获当前变量值或引用”,不是“执行时动态取值”。比如 defer fmt.Println(i) 中的 i 在 defer 语句执行那一刻就被求值(如果是值类型);而 defer func(){ fmt.Println(i) }() 这种闭包形式,i 是在真正执行 defer 时读取,可能已被修改。
- panic 前注册的 defer 一定会执行,哪怕 panic 在 main 函数第一行
- panic 后注册的 defer 永远不会执行(因为控制流已中断)
- 多个 defer 按后进先出(LIFO)顺序执行,和函数调用栈一致
- 如果 defer 中再 panic,会覆盖前一个 panic(除非 recover)
recover 必须在 defer 函数里调用才有效
recover 只有在 defer 函数中直接调用才起作用;在普通函数、嵌套 goroutine 或 recover 被包裹在另一层函数里,都会返回 nil 且无效果。
常见错误是把 recover 写成独立函数然后 defer 调用它:defer handlePanic() —— 这不行,因为 handlePanic() 执行时 panic 已经结束,栈已展开完毕。
立即学习“go语言免费学习笔记(深入)”;
- 正确写法必须是:
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - recover 只能捕获当前 goroutine 的 panic,跨 goroutine 无效
- recover 返回的是 panic 参数(interface{}),类型断言需谨慎,避免 panic 嵌套
- recover 后程序从 panic 点之后继续执行,但 defer 链已清空,不会重跑
defer + recover 无法捕获 runtime panic(如 nil pointer dereference)
可以捕获,但仅限于当前 goroutine 中未被其他 recover 拦截的 panic。像 nil pointer dereference、index out of range 这类 panic 是 Go 运行时主动抛出的,和 panic("xxx") 行为一致,都能被 defer+recover 捕获。
真正不能捕获的是:进程级崩溃(如 stack overflow、SIGSEGV 未被 runtime 转换)、CGO 崩溃、或者 panic 发生在 init 阶段且没被包级 defer 拦截。
- 测试
nil pointer dereference是否可 recover:在 defer 闭包里写*(*int)(nil),它会触发 panic 并被 recover 捕获 - recover 对
fatal error: all goroutines are asleep - deadlock无效,因为这不是 panic,是调度器终止程序 - Go 1.22+ 对某些极端栈溢出场景做了优化,但依然无法 recover
嵌套 defer 和多层 recover 的行为容易误判
recover 只对“最近一次未被处理的 panic”生效,且只能调用一次。如果外层 defer 已 recover,内层 defer 即使也调用了 recover,也会得到 nil。
更隐蔽的问题是:recover 不会“清除”panic 状态,只是让当前 goroutine 继续运行;如果 defer 链中某个 recover 后又 panic,后续 defer 仍会执行,并可能再次 recover。
- 不要依赖多次 recover 来“兜底”,逻辑应尽量扁平
- recover 返回非 nil 后,最好显式 return,避免后续代码意外触发新 panic
- 日志中打印
recover()结果时,建议用fmt.Printf("recovered: %+v", r),避免接口值格式丢失 - 测试多层 recover 行为时,用
go test -run=TestRecoverNest比手动跑更快验证
真正麻烦的是 panic 发生在 defer 函数内部——这时候 recover 失效,而且你很难提前预判哪些资源清理操作本身会 panic(比如关闭一个已被关闭的 net.Conn)。这种 case 得靠防御性编程,而不是指望 recover 救场。










