defer 是注册时入栈、退出前逆序执行,参数在注册时求值而非执行时;多个 defer 按 lifo 顺序执行;它在 return 赋值后、函数退出前运行,可修改命名返回值;panic 时也会触发,但 recover 必须在 defer 函数体内。

defer 不是“写在哪儿就执行哪儿”,它是注册时入栈、退出前逆序弹出——顺序错,资源就漏;参数错,值就固化。
defer 参数在注册时就求值,不是执行时
这是最常踩的坑:你以为 defer fmt.Println(x) 会在函数退出时读 x 的最新值,其实它在那行语句执行的瞬间就把 x 的当前值拷贝进去了。
- 值类型(
int、string)拷贝的是副本,后续改x = 100没影响 - 指针或切片(
*int、[]byte)拷贝的是地址,改内容会影响 defer 执行结果 - 循环里写
for _, f := range files { defer os.Remove(f) }→ 所有defer都删最后一个f,因为f是复用变量 - 想捕获“最后值”?用闭包:
defer func(n string) { os.Remove(n) }(f)或defer func() { os.Remove(f) }()
多个 defer 按 LIFO 逆序执行,顺序决定资源安全
你写 defer f1() 再写 defer f2(),实际先跑 f2(),再跑 f1()。这不是代码顺序,是栈顺序。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- HTTP 场景:
defer resp.Body.Close()必须写在defer client.Close()之前,否则可能触发use of closed network connection - 文件+解析+DB 场景:清理顺序应是 DB rollback → 解析器释放 →
file.Close(),所以注册顺序得倒着来:defer file.Close()要早于defer parser.Cleanup() - 锁操作:
mu.Lock()后立刻写defer mu.Unlock()是安全的,因为sync.Mutex允许零值调用;但自定义结构体若Unlock()里有指针解引用,就得先判空
defer 在 return 赋值之后、函数真正退出之前运行
Go 的 return 不是一步操作,而是三步:赋值返回值 → 执行所有已注册 defer → 跳出函数。这个“之间”阶段很关键。
- 命名返回值(如
func() (err error))能被defer修改,因为err是栈帧里的同一个变量 - 匿名返回值(如
func() int)不能被改,return x已把值拷贝走,defer改的是局部变量 -
panic也会触发defer,但recover()必须写在defer函数体内才有效,写在外面就收不到 - 别在每轮 for 循环里无节制
defer,尤其万级循环——每个defer都要分配_defer结构体,Go 1.14+ 虽做了栈上优化,但 GC 压力仍可能飙升
defer 不是语法糖,是 runtime 层级的栈帧管理
每次 defer 都会创建一个 _defer 结构体,存函数指针、参数、栈指针和 pc,并链入 goroutine 的 _defer 链表头部。函数退出时遍历链表逆序执行。
- 这个结构定义在
runtime2.go中,字段包括fn、sp、pc和link -
defer f.Close()前没检查err?若f是nil,调用直接 panic - goroutine 里启动
defer?它绑定的是该 goroutine 生命周期,不是外层函数——外层函数早返回,资源可能提前释放
真正容易被忽略的是:defer 的注册时机、参数求值时机、执行时机,三者完全分离;而资源依赖顺序、panic 恢复路径、返回值修改能力,全系于这三者的组合逻辑之上。写错一行 defer,可能让连接提前关、锁永远不放、panic 无法 recover。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










