defer参数在注册时立即求值而非执行时;循环中直接写defer fmt.println(i)会全部输出循环结束后的i值(如3、3、3),正确做法是显式传参defer func(v int){fmt.println(v)}(i)或在循环内重声明变量。

defer 的参数在注册时就求值,不是执行时
这是最常被忽略的细节。写 defer fmt.Println(i) 时,i 的值在 defer 语句执行那一刻就被捕获,不是等真正打印时才取值。
常见错误现象:循环中 defer 多次调用,结果全输出最后一个值。
- 错例:
for i := 0; i → 输出 <code>2 2 2 - 对例:用闭包捕获当前值:
for i := 0; i → 输出 <code>2 1 0 - 更安全写法:直接在 defer 中声明新变量:
for i := 0; i
这个行为直接影响资源清理逻辑——比如 defer f.Close() 没问题,但 defer log.Printf("closed %s", name) 中的 name 若后续被修改,日志会记录错误名称。
recover 只在 defer 函数体内直接调用才有效
recover() 不是“ anywhere 调用都能抓 panic”,它必须出现在由 defer 注册的函数内部,且不能隔着一层普通函数调用。
常见错误现象:程序 panic 后没被捕获,直接崩溃;或 recover 返回 nil,误以为没发生 panic。
- ❌ 无效:
defer func() { helper() }(); func helper() { recover() } - ✅ 有效:
defer func() { if r := recover(); r != nil { /* 处理 */ } }() - ⚠️ 注意:recover 一次后,该 panic 的
recovered字段被置为 true,再次调用 recover 返回 nil
Go 运行时靠 goroutine 的 _panic 链表和当前 defer 帧的绑定来识别“是否在 panic 展开过程中”。跨函数调用会丢失上下文,recover 就失效了。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
panic 会触发所有已注册的 defer,但只传播到第一个 recover
panic 不是“跳过 defer 执行”,恰恰相反:它会立刻开始执行当前 goroutine 中所有尚未执行的 defer(LIFO 顺序),并在每个 defer 中检查是否调用了 recover。
使用场景:多层函数调用中做统一兜底,比如 HTTP handler 入口加一层 defer-recover,避免整个服务因单个请求 panic 而挂掉。
- 若 A → B → C → panic,则 C、B、A 中所有 defer 都会按 LIFO 执行(C 的先,A 的后)
- 只要任意一个 defer 里
recover()成功,panic 就终止传播,后续 defer 继续执行,函数返回 - 若无 recover,最终 runtime 调用
fatalpanic,打印 stack trace 并退出 goroutine
性能影响:defer 调用本身有开销(创建 runtime._defer 结构体),高频路径慎用;但 panic/recover 是重量级操作,绝不该用于控制流(比如代替 if/else)。
goroutine 隔离性导致 recover 无法跨协程捕获
每个 goroutine 有自己的 panic 链表和 defer 栈。recover() 只能捕获**当前 goroutine 内部**触发的 panic,对其他 goroutine 完全无感知。
容易踩的坑:在 go func() 里 panic,却指望 main 里的 defer-recover 捕获。
- ❌ 错误假设:
go func() { panic("oops") }(); defer func() { recover() }()→ main 的 recover 不生效 - ✅ 正确做法:每个 goroutine 自己处理,或通过 channel 通知主 goroutine:
errCh := make(chan error, 1); go func() { defer func() { if r := recover(); r != nil { errCh - ⚠️ 注意:recover 不会恢复其他 goroutine 的状态,仅阻止当前 goroutine 崩溃
这也是为什么标准库 net/http 的 Serve 方法自己封装了 recover —— 因为每个请求都在独立 goroutine 里跑,必须就地拦截,否则一个请求 panic 就会让整个 server 进程退出。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










