defer 延迟 - Go 流程控制
defer 是 Go 独特的延迟执行机制,用于在函数返回前执行清理代码。掌握 defer 是编写健壮 Go 代码的关键,特别适用于资源管理。
📌 核心概念
⏱️
延迟执行
函数返回前
defer
📚
LIFO 顺序
后进先出
栈顺序
🔒
资源清理
Close/Unlock
defer Close()
📝
参数求值
声明时求值
立即求值
基础用法
📝 defer 基本形式
package main
import "fmt"
func main() {
// defer: 延迟到函数返回前执行
defer fmt.Println("First defer")
defer fmt.Println("Second defer")
defer fmt.Println("Third defer")
fmt.Println("In main")
// 输出顺序:
// In main
// Third defer (后进先出)
// Second defer
// First defer
}
// defer 要点:
// 1. 多个 defer 按 LIFO 顺序执行
// 2. defer 在声明时立即求值参数
// 3. defer 即使 panic 也会执行
💡 defer 要点
- LIFO 顺序: 多个 defer 按后进先出顺序执行
- 立即求值: 参数在 defer 声明时立即求值
- panic 安全: 即使 panic 也会执行 defer
- 函数调用: defer 后面必须是函数调用
资源管理
📝 defer 关闭资源
package main
import (
"fmt"
"os"
)
func main() {
// ✅ 推荐:defer 确保资源关闭
file, err := os.Open("file.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close() // 确保关闭
// 处理文件...
// 即使中间有 return,defer 也会执行
}
// 多个资源管理
func copyFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
dest, err := os.Create(dst)
if err != nil {
return err
}
defer dest.Close()
// defer 按 LIFO 顺序执行:
// 1. dest.Close()
// 2. source.Close()
return nil
}
参数求值时机
📝 defer 参数立即求值
package main
import "fmt"
func main() {
// defer 参数在声明时立即求值
x := 10
defer fmt.Println("x =", x) // 输出 x = 10
x = 20 // 这个修改不影响 defer 的参数
// 输出:x = 10 (不是 20)
// 如果需要延迟求值,使用函数
defer func() {
fmt.Println("x (delayed) =", x) // 输出 x = 20
}()
}
// 常见陷阱
func badExample() {
var err error
defer handleError(err) // err 在这里是 nil!
err = doSomething() // defer 已经记录了 err=nil
}
// 正确做法
func goodExample() {
defer func() {
if err := recover(); err != nil {
handleError(err)
}
}()
doSomething()
}
recover 捕获 panic
📝 defer + recover
package main
import "fmt"
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Before panic")
panic("something went wrong")
// 这行不会执行
fmt.Println("After panic")
}
// recover 要点:
// 1. 必须在 defer 函数中调用
// 2. 只能在 panic 的 goroutine 中捕获
// 3. 捕获后程序继续执行
实用模式
1. 计时函数
📝 函数执行时间
func timedFunction() {
defer func(start time.Time) {
elapsed := time.Since(start)
fmt.Printf("Function took: %v\n", elapsed)
}(time.Now())
// 函数逻辑...
time.Sleep(time.Millisecond * 100)
}
2. 解锁互斥锁
📝 defer 解锁
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock() // 确保解锁
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
3. 数据库事务
📝 事务回滚
func processTransaction(db *sql.DB) error {
tx, err := db.Begin()
if err != nil {
return err
}
// 如果出错则回滚,成功则提交
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p) // 重新抛出 panic
} else if err != nil {
tx.Rollback()
}
}()
// 执行事务操作...
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
return err
}
return tx.Commit()
}
最佳实践
✅ defer 使用建议
- 资源清理: 文件、连接、锁用 defer 关闭
- 紧跟获取: defer 紧跟在资源获取之后
- 避免复杂: defer 中避免复杂逻辑
- 注意求值: 记住参数立即求值
- recover 谨慎: 不要滥用 recover 吞掉 panic
🚨 常见陷阱
- 参数求值: defer 参数立即求值,不是延迟
- 循环内 defer: 循环内多次 defer 会累积
- recover 位置: recover 必须在 defer 内调用
- 返回值修改: 命名返回值可在 defer 中修改