《Go语言圣经》defer
核心概念:defer语句的执行时机
defer
是Go语言的一个关键字,它的作用是:延迟执行一个函数调用,该调用会在包围它的函数返回前一刻执行。
关键点:
defer
语句会在函数即将返回时执行,无论函数是正常返回还是 panic 异常defer
语句按后进先出(LIFO)的顺序执行defer
语句中的函数参数会在defer
语句定义时计算,而非执行时计算
代码解析:trace函数与defer的配合
func bigSlowOperation() {
defer trace("bigSlowOperation")() // 关键语句
// ...lots of work...
time.Sleep(10 * time.Second)
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() {
log.Printf("exit %s (%s)", msg, time.Since(start))
}
}
执行流程分析:
进入bigSlowOperation函数:
- 遇到
defer trace("bigSlowOperation")()
语句 - 首先执行
trace("bigSlowOperation")
函数调用 trace
函数的执行过程:- 记录开始时间
start := time.Now()
- 打印日志
enter bigSlowOperation
- 返回一个匿名函数
func() { ... }
- 记录开始时间
- 遇到
defer语句的处理:
defer
会将trace
函数返回的匿名函数放入延迟调用栈,但不会立即执行- 注意这里的两层括号:
- 第一层
()
:调用trace
函数 - 第二层
()
:调用trace
返回的函数值
- 第一层
执行函数主体:
- 执行其他操作(示例中是睡眠10秒模拟耗时操作)
函数返回前:
- 从延迟调用栈中取出匿名函数并执行
- 匿名函数打印日志
exit bigSlowOperation (10s)
- 这里使用
time.Since(start)
计算函数执行耗时
为什么需要两层括号?
这是理解这段代码的关键:
trace("bigSlowOperation")
:这是调用trace
函数,返回一个func()
类型的值(即一个函数)。第二层
()
:表示调用这个返回的函数。如果没有这层括号:defer trace("bigSlowOperation") // 错误!
这会将
trace
函数本身放入延迟调用栈,而不是调用它返回的函数。这会导致:trace
函数会在bigSlowOperation
返回时才被调用(而不是进入时)- 永远不会执行记录退出的逻辑
类比理解:餐厅服务的比喻
可以将这个过程类比为餐厅服务:
bigSlowOperation
是"用餐过程"defer trace(...)()
的执行过程:- 进入餐厅时,服务员记录你开始用餐(
trace
函数的前半部分) - 你点了一道需要长时间烹饪的菜(
time.Sleep
) - 用餐结束时,服务员记录你离开的时间并计算用餐时长(
trace
返回的匿名函数)
- 进入餐厅时,服务员记录你开始用餐(
扩展:记录函数执行时间的完整示例
package main
import (
"log"
"time"
)
// trace 记录函数的进入和退出,并返回计算耗时的函数
func trace(msg string) func() {
start := time.Now()
log.Printf("进入函数: %s", msg)
return func() {
log.Printf("退出函数: %s (耗时: %v)", msg, time.Since(start))
}
}
// 模拟一个耗时操作
func complexCalculation() {
defer trace("复杂计算")() // 关键语句,不要忘记两层括号
log.Println("正在执行复杂计算...")
time.Sleep(2 * time.Second) // 模拟耗时操作
log.Println("计算完成")
}
func main() {
log.Println("程序开始")
complexCalculation()
log.Println("程序结束")
}
输出结果:
2025/06/18 15:00:00 程序开始
2025/06/18 15:00:00 进入函数: 复杂计算
2025/06/18 15:00:00 正在执行复杂计算...
2025/06/18 15:00:02 计算完成
2025/06/18 15:00:02 退出函数: 复杂计算 (耗时: 2s)
2025/06/18 15:00:02 程序结束
总结
这段代码的核心技巧在于:
- 利用
defer
在函数退出时执行的特性 - 通过函数返回函数值(高阶函数)的方式,将"进入记录"和"退出记录"绑定在一起
- 使用两层括号分别完成"调用追踪函数"和"调用返回的记录函数"
这种模式在实际开发中非常有用,可以用于:
- 记录函数执行日志
- 测量函数执行性能
- 资源释放(如关闭文件、数据库连接)
- 错误处理上下文管理
理解这种模式的关键是记住:defer
语句中的函数调用会在包围函数返回时执行,而trace
函数的作用是生成这个延迟执行的函数。