引入

打开文件要关、获取锁要释放、数据库连接要断开——这些”善后”操作如果忘了做,程序就会出问题。Go 的 defer 让你在执行操作的同时就把”善后”安排好,不管函数怎么返回,defer 的语句都会被执行。

正文

定义

定义

defer 语句延迟执行某个函数调用,被延迟的调用不会立即执行,而是在包含 defer 的函数返回之前按后进先出(LIFO)的顺序执行。常用于释放资源、关闭文件、解锁互斥量等善后操作。

语法

defer 函数调用()
 
// 例子
defer file.Close()
defer mu.Unlock()

defer 后面跟一个函数调用,这个调用会在当前函数 return 之前执行。

例子

基本用法

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}
// 输出:
// hello
// world

defer 的语句不是马上执行,而是函数结束前才执行。

多个 defer(后进先出)

func main() {
    defer fmt.Println("first")
    defer fmt.Println("second")
    defer fmt.Println("third")
}
// 输出:
// third
// second
// first

多个 defer的方式执行,最后声明的最先执行。

defer 的参数在声明时求值

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

defer 的参数在声明时就确定了,之后修改 x 不影响 defer 里的值。

defer 访问修改后的变量(闭包)

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

用闭包的方式,defer 的匿名函数在执行时才读取 x,拿到的是修改后的值。

关闭文件

func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close()
 
    data, err := io.ReadAll(file)
    return data, err
}

defer file.Close() 保证文件在函数返回前一定被关闭,不管正常返回还是提前 return

解锁互斥量

var mu sync.Mutex
var count int
 
func increment() {
    mu.Lock()
    defer mu.Unlock()
    count++
}

加锁后立刻 defer 解锁,不管函数中间有没有 return,锁都会被释放。

defer 修改返回值

func double() (result int) {
    defer func() {
        result *= 2
    }()
    return 5
}
 
func main() {
    fmt.Println(double()) // 10
}

命名返回值配合 defer 闭包,可以在 return 之后、函数真正退出之前修改返回值。

常见写法

写法说明
defer f.Close()延迟关闭资源
defer mu.Unlock()延迟解锁
defer func() { … }()延迟执行匿名函数
defer fmt.Println(x)参数在声明时求值
defer func() { fmt.Println(x) }()闭包方式,执行时求值

特点

  • defer函数返回前执行,不是代码块结束前
  • 多个 deferLIFO(后进先出)顺序执行
  • defer 的参数在声明时求值,不是执行时
  • defer 闭包可以在执行时读取变量的最新值
  • defer 配合命名返回值可以修改返回值
  • defer 有少量性能开销,极高性能场景可手动管理
  • 不管函数是 returnpanic 还是正常结束,defer 都会执行

理解

defer 就是 Go 版的”保险”——操作做完立刻把善后安排好,不用担心忘了释放资源。记住 LIFO 顺序和参数求值时机这两个细节,就能在文件操作、锁管理、日志记录等场景里用得得心应手。

引出

defer 还有一个重要搭档:当程序遇到不可恢复的错误时会 panic,而 recover 可以在 defer 里把程序救回来。看看 Go panic 和 recover 怎么控制程序的崩溃与恢复。