defer 严格按 LIFO 顺序执行,仅 panic 前已注册的 defer 会运行;recover 必须在 defer 函数体内直接调用且同 goroutine 才有效;defer 参数在注册时求值,非执行时。

会执行,且严格按 LIFO(后进先出)顺序执行所有已注册的 defer —— 无论 panic 是否被 recover 捕获,只要 defer 语句已执行(即注册成功),就一定会运行。
panic 时哪些 defer 会被执行?
只有在 panic 发生前已执行过的 defer 语句才会入栈并被执行;panic 后面的 defer 永远不会注册,属于不可达代码。
- 错误写法:
panic("boom"); defer func() { recover() }()→recover()根本不执行,因为 defer 还没注册 - 正确写法:函数开头就写
defer func() { recover() }(),确保它最早注册、最晚执行(即在 defer 栈底) - IDE 通常会标出
defer print(666)这类 panic 后的语句为Unreachable code
多个 defer 的执行顺序为什么是倒着的?
Go 把每个 defer 当作一次压栈操作,函数返回(含 panic)时统一出栈。这不是书写顺序,而是注册顺序的逆序。
-
defer fmt.Println("A")defer fmt.Println("B")defer fmt.Println("C")→ 实际输出顺序是C→B→A - 资源释放场景下,这符合“后申请、先释放”的逻辑:比如
db.Begin()后开tx.Commit(),应让tx.Commit()的 defer 写在db.Close()的 defer 之前,才能保证事务结束再关连接
recover 必须在 defer 函数体内调用才有效
recover() 不是全局拦截器,它只在当前 goroutine 处于 panic 状态、且调用位置满足两个条件时才非 nil:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 必须在
defer匿名函数内部直接调用(不能包在子函数里,也不能提前赋值给变量) - 必须和
panic在同一个 goroutine 中;跨 goroutine 的 panic(如go func() { panic("x") }())无法被外部defer捕获 - 常见误用:
defer recover()→ 这是在注册时就执行了recover(),此时无 panic,必然返回nil
defer 参数在注册时就求值,不是执行时
这是最容易踩坑的一点:变量值锁定在 defer 语句执行那一刻,而非 defer 函数真正运行时。
-
i := 1; defer fmt.Println(i); i = 2→ 输出1,不是2 - 若想捕获执行时的值,得显式传参:
defer func(v int) { fmt.Println(v) }(i) - 闭包引用外部变量时尤其危险:循环中写
for i := range items { defer func() { use(i) }() },所有 defer 共享同一个i,最终都用最后一次迭代的值
真正难处理的不是 defer 执行与否,而是参数求值时机和 goroutine 边界——这两点一旦错位,bug 往往延迟暴露、难以复现。

















