会执行,但只执行 panic 发生前已注册的 defer;go 在栈展开前强制按 lifo 顺序执行已入栈的 defer;defer 是注册行为而非监听行为,后续 defer 语句因 panic 中断而不执行。

defer 在 panic 后到底执不执行?
会执行,但只执行 panic 发生前已注册的 defer。这是最常被误读的一点——不是“panic 一发生就跳过所有 defer”,而是 Go 运行时在栈展开前,**强制按 LIFO 顺序执行所有已入栈的 defer 函数**。
常见错误现象:panic("boom") 写在函数开头,后面才写 defer fmt.Println("cleanup"),结果 cleanup 完全没输出。原因很简单:那行 defer 根本没机会执行,也就没注册进 defer 栈。
-
defer是“注册行为”,不是“监听行为”;注册发生在语句执行那一刻 - panic 触发后,控制流立即中断,后续代码(包括后续的
defer语句)永不执行 - 已注册的
defer不管 panic 是否被recover捕获,都照常执行
recover 必须在 defer 函数里直接调用
recover() 只有在 defer 函数体内**直接调用**才有效。写成独立函数再 defer 调用,等于白写。
典型误用:defer handlePanic() → handlePanic() 执行时 panic 已结束,recover() 返回 nil,毫无作用。
- ✅ 正确写法:
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - ❌ 错误写法:
func handlePanic() { recover() }; defer handlePanic() - recover 只对当前 goroutine 生效;子 goroutine panic,必须在它内部写
defer func() { recover() }() - recover 返回的是
panic的参数值(interface{}),类型断言前建议先判空
多个 defer 的执行顺序和 panic 覆盖问题
多个 defer 按后进先出(LIFO)执行,这和函数调用栈一致。但如果某个 defer 里又触发了 panic,它会覆盖前面未被捕获的 panic。
示例:defer func() { panic("in defer") }() 和 panic("outer") 同时存在,最终只看到 "in defer" 的 panic 堆栈,"outer" 的信息丢失。
- defer 执行期间再 panic,原 panic 被丢弃,无法恢复
- 若想保留原始 panic,必须在 defer 里先
recover()拿到它,再做处理或重新panic - 资源清理类 defer(如
file.Close())应放在靠前位置注册,避免被 panic 中断执行 - recover 类 defer 建议放在函数最开头,确保它在 defer 栈底(即最后执行),能捕获所有中间 panic
defer 参数求值时机与闭包陷阱
defer 的参数在语句执行时就求值并固化,不是 defer 函数真正运行时才取值。这个细节在循环中极易踩坑。
比如:for i := 0; i ,输出全是 <code>3,因为 i 在循环结束时是 3,而每个 defer 都捕获了同一个变量的最终值。
- 要捕获每次迭代的值,得用闭包传参:
defer func(n int) { fmt.Println(n) }(i) - 命名返回值可被 defer 修改,匿名返回值不行 —— 因为前者是变量,后者是寄存器值
- defer 中修改命名返回值,是在 return 写入返回值之后、函数退出之前发生的,所以能生效
实际写业务逻辑时,最容易忽略的是:recover 只终止 panic 传播,不重置程序状态。文件可能已部分写入、DB 事务可能已提交一半、网络连接可能已断开——这些都不是 recover 能自动回滚的。资源清理必须靠 defer 显式保障,而业务一致性得靠设计兜底,不是靠 recover “救回来”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











