defer 是Go语言中一个独特而强大的特性,它允许你在函数返回之前延迟执行某个函数调用。无论函数是正常返回还是发生panic,被defer的调用都会被执行。这使得defer成为资源释放(如关闭文件、解锁互斥锁)、错误处理、日志记录等场景的理想选择。
在函数调用前加上defer关键字,该调用会被推迟到所在函数返回之前执行。
package main
import "fmt"
func main() {
defer fmt.Println("world") // 延迟执行
fmt.Println("hello")
}
// 输出: hello\nworld
多个defer语句会形成一个栈,按照后进先出(LIFO)的顺序执行。
func main() {
defer fmt.Println("第一个defer")
defer fmt.Println("第二个defer")
defer fmt.Println("第三个defer")
fmt.Println("正常语句")
}
// 输出:
// 正常语句
// 第三个defer
// 第二个defer
// 第一个defer
这一特性非常适用于成对操作(如加锁/解锁),确保资源按正确顺序释放。
defer中的代码在return语句之后、函数真正返回之前执行。对于带命名返回值的函数,defer可以修改返回值。
func foo() int {
i := 0
defer func() {
i++ // 操作的是局部变量,不影响返回值
}()
return i // 返回值是0,defer对i的修改不影响返回值
}
func bar() (result int) {
defer func() {
result++ // 修改命名返回值
}()
return 0 // 返回值为1
}
defer语句的参数(包括接收者)在defer注册时就被求值,而不是在调用时。这意味着传递给defer函数的参数值会被立即“快照”。
func main() {
i := 0
defer fmt.Println(i) // 此时i=0,即使后面i改变,输出仍为0
i++
return
}
// 输出: 0
若希望使用defer调用时的最新值,应使用闭包(匿名函数捕获变量)。
i := 0
defer func() {
fmt.Println(i) // 闭包捕获的是变量引用,输出1
}()
i++
确保文件、网络连接、互斥锁等资源被正确关闭或释放。
func readFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close() // 保证文件被关闭
return io.ReadAll(f)
}
var mu sync.Mutex
func safeUpdate() {
mu.Lock()
defer mu.Unlock()
// 临界区代码,即使panic也会解锁
}
recover必须配合defer才能生效,用于捕获panic,使程序恢复执行。
func safeDivision(a, b int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到panic:", r)
}
}()
fmt.Println(a / b)
}
func main() {
safeDivision(10, 2) // 5
safeDivision(4, 0) // 捕获到panic: runtime error: integer divide by zero
fmt.Println("程序继续执行")
}
虽然defer增加少量开销,但在大多数场景下可以忽略。对于极端性能敏感的热路径,可评估是否使用defer。通常推荐使用defer保证代码的健壮性。
os.Exit会直接终止程序,不会执行defer。package main
import (
"errors"
"fmt"
)
type Transaction struct {
committed bool
}
func (tx *Transaction) Commit() {
tx.committed = true
fmt.Println("事务已提交")
}
func (tx *Transaction) Rollback() {
if !tx.committed {
fmt.Println("事务已回滚")
}
}
func runTransaction(update func(tx *Transaction) error) error {
tx := &Transaction{}
defer func() {
if err := recover(); err != nil {
tx.Rollback()
panic(err) // 重新抛出,或处理
}
if !tx.committed {
tx.Rollback()
}
}()
if err := update(tx); err != nil {
return err
}
tx.Commit()
return nil
}
func main() {
err := runTransaction(func(tx *Transaction) error {
// 模拟操作
fmt.Println("执行更新1")
// 模拟错误
return errors.New("发生错误")
})
if err != nil {
fmt.Println("事务失败:", err)
}
}
// 输出:
// 执行更新1
// 事务已回滚
// 事务失败: 发生错误