目录

go 关键字 defer 使用详解(使用场景易错分析)

go 关键字 defer 使用详解(使用场景易错分析)

核心原理

defer 是 Go 语言的一大特性,defer的字面意思是“延迟”,但很多人只知道它“延迟执行”,却不懂背后的逻辑,这是踩坑的根源。

先记住3个核心原理:

  1. 执行时机:defer修饰的函数会在“所在函数执行完return语句后、真正返回前”执行,不是在函数结束时才执行,也不是在defer定义时执行;
  2. 参数预计算:defer修饰的函数的参数,在定义defer时就会计算出具体值,而不是在执行defer时才计算;
  3. 栈式执行:多个defer同时存在时,遵循“后进先出”(LIFO)的栈式顺序,最后定义的defer最先执行。

用 defer 可以来声明一些延迟函数,这些延迟函数会被放进队列里,在外层函数执行完成后会依次执行延迟函数:

  • 在函数、方法返回 return 之前执行
  • 可以同时设置多个 defer 函数,执行顺序遵循 FILO 先进后出 顺序
  • defer 会将运行结果存在栈中,等到 return 之后再推出来执行
  • 一般常用于资源清理、锁释放、文件资源关闭、关闭数据库连接、与 panic recover 结合使用等

场景一、基础使用

简单使用

func main() {
    defer fmt.Println(1)  // 输出1
    x = 2
    fmt.Println(x)
    return
}

2

1

场景二:先进后出

多个 defer 的执行顺序

func main() {
    defer fmt.Println(1)  // 输出1
    defer fmt.Println(2)  // 输出1
    x = 3
    fmt.Println(x)
    return
}

3

2

1

场景三:闭包使用

在闭包调用下 defer 的执行情况,包含参数的引用结果

func main() {
  fmt.Println("res:", testDefer()) // res:3
}

func testDefer() int {
    var i = 1
    defer func() { // 将函数存储在栈上,最后执行的时候调用该函数,打印出i,所以i的值在后面的改动生效
        fmt.Println("defer2:", i) // i=3
    }()

    i++ // i=2
  defer fmt.Println("defer3:", i) // 将结果“2” 存储在栈上,执行时输出
  defer fmt.Println("defer4:", i+1) // 结果 3,但是不会影响 i 的值
    i++ //i=3
    return i                 // 可以看作 var rsp=i,return rsp,所以defer里改了i的值时没用的
}

结果: defer4: 3 defer3: 2 defer2: 3 res: 3

四、生产场景

defer 在生成环境中有最常见的需求:打开文件、建立数据库连接、创建锁后,必须确保关闭/释放,否则会导致资源泄漏。defer能保证“无论函数正常执行还是因错误返回,都会释放资源”,比手动在每个return前写释放代码高效且不易漏。

  • 打印耗时
func ProcessJob() {
    start := time.Now()
    defer func() {
        fmt.Printf("耗时: %v", time.Since(start))
    }()

    // 复杂业务逻辑...
}
  • 资源关闭
func ProcessFileFixed() {
    f, _ := os.Open("data.log")
         defer func(){
       _=f.Close() // 确保资源释放
    }
    // 业务逻辑...
}
  • 锁关闭
func ProcessJob() {
    var mu=sync.Mutex
    mu.Lock()
         defer func(){
      mu.Unlock() // 解锁,避免死锁
    }
    // 业务逻辑...
}
  • 事务提交
func TransferMoney(db *sql.DB) error {
    var err error
    tx, err := db.Begin()
    if err!=nil{
      return err
    }
    defer func() {
      if err!=nil{
        tx.Rollback()
      }else{
        tx.Commit()
      }
    }()

    // 业务逻辑...
    return nil
}
  • panic 捕获
func ProcessJob() {
    start := time.Now()
    defer func() {
        if p := recover(); p != nil {
           // 打印堆栈日志
        }
    }()

    // 复杂业务逻辑...触发 panic

    return
}

总结

defer的核心价值是“保证延迟执行的可靠性”,掌握以下4个法则,就能避开绝大部分的坑:

  1. 资源释放必用defer:打开文件、连接、锁后,立刻defer释放,且要处理释放错误;
  2. 参数求值看定义:defer的参数在定义时就确定,要动态计算值就用匿名函数包裹;
  3. 执行顺序栈先进:多个defer按“后进先出”执行,定义顺序要和资源依赖相反;
  4. 循环defer要包裹:循环中使用defer时,用匿名函数包裹,避免资源累积。

defer看似简单,但用好它能极大提升代码的健壮性,这也是Go语言“简洁而强大”的体现。

如果大家有defer的特殊使用场景或踩坑经历,欢迎在评论区交流!

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/go-defer/

备用原文链接: https://blog.fiveyoboy.com/articles/go-defer/