引入
打开文件要关、获取锁要释放、数据库连接要断开——这些”善后”操作如果忘了做,程序就会出问题。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
// worlddefer 的语句不是马上执行,而是函数结束前才执行。
多个 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
}
// 输出:1defer 的参数在声明时就确定了,之后修改 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在函数返回前执行,不是代码块结束前- 多个
defer按 LIFO(后进先出)顺序执行 defer的参数在声明时求值,不是执行时defer闭包可以在执行时读取变量的最新值defer配合命名返回值可以修改返回值defer有少量性能开销,极高性能场景可手动管理- 不管函数是
return、panic还是正常结束,defer都会执行
理解
defer 就是 Go 版的”保险”——操作做完立刻把善后安排好,不用担心忘了释放资源。记住 LIFO 顺序和参数求值时机这两个细节,就能在文件操作、锁管理、日志记录等场景里用得得心应手。
引出
defer 还有一个重要搭档:当程序遇到不可恢复的错误时会 panic,而 recover 可以在 defer 里把程序救回来。看看 Go panic 和 recover 怎么控制程序的崩溃与恢复。