《Go语言圣经》defer

发布于:2025-06-21 ⋅ 阅读:(20) ⋅ 点赞:(0)

《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)) 
    }
}
执行流程分析:
  1. 进入bigSlowOperation函数

    • 遇到defer trace("bigSlowOperation")()语句
    • 首先执行trace("bigSlowOperation")函数调用
    • trace函数的执行过程:
      • 记录开始时间start := time.Now()
      • 打印日志enter bigSlowOperation
      • 返回一个匿名函数func() { ... }
  2. defer语句的处理

    • defer会将trace函数返回的匿名函数放入延迟调用栈,但不会立即执行
    • 注意这里的两层括号:
      • 第一层():调用trace函数
      • 第二层():调用trace返回的函数值
  3. 执行函数主体

    • 执行其他操作(示例中是睡眠10秒模拟耗时操作)
  4. 函数返回前

    • 从延迟调用栈中取出匿名函数并执行
    • 匿名函数打印日志exit bigSlowOperation (10s)
    • 这里使用time.Since(start)计算函数执行耗时

为什么需要两层括号?

这是理解这段代码的关键:

  1. trace("bigSlowOperation"):这是调用trace函数,返回一个func()类型的值(即一个函数)。

  2. 第二层():表示调用这个返回的函数。如果没有这层括号:

    defer trace("bigSlowOperation") // 错误!
    

    这会将trace函数本身放入延迟调用栈,而不是调用它返回的函数。这会导致:

    • trace函数会在bigSlowOperation返回时才被调用(而不是进入时)
    • 永远不会执行记录退出的逻辑

类比理解:餐厅服务的比喻

可以将这个过程类比为餐厅服务:

  1. bigSlowOperation是"用餐过程"
  2. 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 程序结束

总结

这段代码的核心技巧在于:

  1. 利用defer在函数退出时执行的特性
  2. 通过函数返回函数值(高阶函数)的方式,将"进入记录"和"退出记录"绑定在一起
  3. 使用两层括号分别完成"调用追踪函数"和"调用返回的记录函数"

这种模式在实际开发中非常有用,可以用于:

  • 记录函数执行日志
  • 测量函数执行性能
  • 资源释放(如关闭文件、数据库连接)
  • 错误处理上下文管理

理解这种模式的关键是记住:defer语句中的函数调用会在包围函数返回时执行,而trace函数的作用是生成这个延迟执行的函数。


网站公告

今日签到

点亮在社区的每一天
去签到