← switch 分支 | 结构体 →

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 中修改

📖 延伸阅读