Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言defer机制

深度解析Go语言defer机制

作者:初心未改HD

文章详细介绍了Go语言中defer关键字的使用及其特性,涵盖defer的基本用法、执行时机、LIFO机制、与返回值的关系、在panic中的行为、参数求值时机、典型应用场景及性能考量等,并通过示例代码帮助理解,感兴趣的朋友跟随小编一起看看吧

前言

defer是Go语言中极具特色的关键字,用于注册延迟调用。当函数执行到defer语句时,不会立即执行被延迟的函数调用,而是将调用压入一个栈中,在函数即将返回时(LIFO顺序)执行。理解defer的执行时机和机制,对于写出健壮的Go代码至关重要。

一、defer基础

1.1 defer的基本用法

func before() {
    fmt.Println("before main")
}
​
func after() {
    fmt.Println("after main")
}
​
func main() {
    defer after()
    before()
    fmt.Println("main body")
}

输出:

before main
main body
after main

1.2 defer的执行时机

defer在return语句之后、函数退出之前执行:

func test() int {
    fmt.Println("1. 函数体执行")
    ret := 0
    defer func() {
        fmt.Println("4. defer执行,ret被修改")
        ret = 100
    }()
    fmt.Println("2. defer注册完毕,继续执行")
    return ret  // 3. return执行,ret=0
}
​
func main() {
    result := test()
    fmt.Printf("5. 最终返回值: %d\n", result)  // 注意:返回值不是100
}

关键发现: defer修改的是命名返回值,但返回的是之前的值副本

1.3 LIFO执行顺序

多个defer按后进先出顺序执行:

func main() {
    fmt.Println("start")
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")
    fmt.Println("middle")
    defer fmt.Println("defer 4")
    defer fmt.Println("defer 5")
    fmt.Println("end")
}

输出:

start
middle
end
defer 5      ← LIFO:最后注册的先执行
defer 4
defer 3
defer 2
defer 1

二、defer与返回值

2.1 匿名返回值 vs 命名返回值

匿名返回值:

func匿名() int {
    var result int
    defer func() {
        result = 100
        fmt.Println("defer修改:", result)
    }()
    return result  // 返回0(result的副本)
}
​
func main() {
    fmt.Println("匿名返回值:", 匿名())  // 打印100
}

命名返回值:

func命名() (result int) {
    defer func() {
        result = 100
        fmt.Println("defer修改:", result)
    }()
    return result  // 返回100(与result是同一变量)
}
​
func main() {
    fmt.Println("命名返回值:", 命名())  // 打印100
}

2.2 图解defer执行时机

return 执行过程:
​
    return xxx
       │
       ▼
┌──────────────────┐
│ 1. 计算返回值     │  ← 返回值已确定
├──────────────────┤
│ 2. 调用defer函数  │  ← defer在这里执行
├──────────────────┤
│ 3. 返回调用者     │
└──────────────────┘
​
注意:步骤1和步骤2之间,命名返回值已经被赋值

三、defer与panic

3.1 defer在panic时的执行

func main() {
    fmt.Println("start")
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")
    panic("something went wrong")
    defer fmt.Println("never reached")
}

输出:

start
defer 3        ← panic前的defer倒序执行
defer 2
defer 1
panic: something went wrong

3.2 recover拦截panic

func safeCall(f func()) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("捕获panic: %v\n", r)
        }
    }()
    f()
}
​
func mayPanic() {
    fmt.Println("mayPanic 开始")
    panic("boom!")
    fmt.Println("mayPanic 结束")  // 不会执行
}
​
func main() {
    fmt.Println("main 开始")
    safeCall(mayPanic)
    fmt.Println("main 继续执行")
}

输出:

main 开始
mayPanic 开始
捕获panic: boom!
main 继续执行

3.3 defer中panic的传递

func main() {
    defer func() {
        fmt.Println("outer defer start")
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("inner recover: %v\n", r)
            }
        }()
        defer fmt.Println("inner defer")
        panic("inner panic")
    }()
    panic("outer panic")
}

输出:

inner defer
inner recover: inner panic
outer defer start

分析:

四、defer参数求值时机

4.1 参数是立即求值的

func main() {
    i := 0
    defer fmt.Println("defer i =", i)  // 参数立即求值,i=0
    i = 100
    fmt.Println("main i =", i)  // i=100
}

输出:

main i = 100
defer i = 0          ← defer注册时i=0被保存

4.2 闭包捕获的是变量引用

func main() {
    i := 0
    defer func() {
        fmt.Println("闭包 i =", i)  // 闭包捕获i的引用
    }()
    i = 100
    fmt.Println("main i =", i)
}

输出:

main i = 100
闭包 i = 100        ← defer执行时,i已经是100

4.3 对比分析

func compare() {
    i := 0
    // 方式1:参数求值
    defer fmt.Println("参数方式:", i)
    // 方式2:闭包方式
    defer func() {
        fmt.Println("闭包方式:", i)
    }()
    i = 100
}
​
func main() {
    compare()
}

输出:

闭包方式: 100
参数方式: 0

五、defer的典型应用

5.1 资源释放

func readFile(filename string) {
    // 打开文件
    file, err := os.Open(filename)
    if err != nil {
        fmt.Printf("打开文件失败: %v\n", err)
        return
    }
    // 确保关闭文件
    defer file.Close()
    // 读取文件内容
    data := make([]byte, 1024)
    for {
        n, err := file.Read(data)
        if n == 0 || err != nil {
            break
        }
        fmt.Print(string(data[:n]))
    }
    // defer会在函数结束时自动关闭文件
}

5.2 解锁Mutex

import "sync"
​
type Counter struct {
    mu    sync.Mutex
    count int
}
​
func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()  // 函数结束自动解锁
    c.count++
}
​
func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

5.3 释放数据库连接

type DB struct {
    conn interface{}
}
​
func query(db *DB, sql string) {
    // 获取连接
    conn := db.getConn()
    defer db.releaseConn(conn)  // 确保释放
    // 使用连接执行查询
    results := conn.Query(sql)
    // 处理结果...
    // defer自动释放连接
}

5.4 打印函数执行时间

func trackExecution(name string) {
    start := time.Now()
    fmt.Printf("开始执行 %s...\n", name)
    defer func() {
        elapsed := time.Since(start)
        fmt.Printf("%s 执行耗时: %v\n", name, elapsed)
    }()
    // 模拟执行
    time.Sleep(100 * time.Millisecond)
}
​
func main() {
    trackExecution("task1")
    trackExecution("task2")
}

5.5 统一错误处理

func process() (err error) {
    // 使用命名返回值,确保defer能访问到err
    defer func() {
        if err != nil {
            fmt.Printf("最终错误: %v\n", err)
        }
    }()
    // 步骤1
    if err = step1(); err != nil {
        return fmt.Errorf("step1 failed: %w", err)
    }
    // 步骤2
    if err = step2(); err != nil {
        return fmt.Errorf("step2 failed: %w", err)
    }
    // 步骤3
    if err = step3(); err != nil {
        return fmt.Errorf("step3 failed: %w", err)
    }
    return nil
}
​
func step1() error { return nil }
func step2() error { return errors.New("step2 error") }
func step3() error { return nil }

六、defer的性能

6.1 defer的性能开销

defer比直接调用有一定的性能开销:

import (
    "testing"
)
​
func withoutDefer() {
    // 直接调用
}
​
func withDefer() {
    defer func() {
        // 空defer
    }()
}
​
func BenchmarkWithoutDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        withoutDefer()
    }
}
​
func BenchmarkWithDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        withDefer()
    }
}
​
// 典型结果:
// BenchmarkWithoutDefer    1000000000   0.25 ns/op
// BenchmarkWithDefer       1000000000   35 ns/op
// 
// defer大约有100倍的开销,但在大多数场景下可忽略

6.2 何时应该避免使用defer

// 场景:循环中大量创建资源
// 不推荐:每次循环都注册defer
func processItems(items []Item) {
    for _, item := range items {
        file, _ := os.Open(item.Path)
        defer file.Close()  // 问题:defer累积,资源无法及时释放
        // 处理...
    }
}
​
// 推荐:使用代码块限制作用域
func processItemsFixed(items []Item) {
    for _, item := range items {
        file, _ := os.Open(item.Path)
        // 处理完后立即关闭,或使用errgroup批量处理
        file.Close()
    }
}

七、常见面试题

Q1: 下面代码的输出是什么?

func main() {
    defer_call()
}
​
func defer_call() {
    defer func() { fmt.Println("1") }()
    defer func() { fmt.Println("2") }()
    defer func() { fmt.Println("3") }()
    panic("panic error")
}

答案:

3
2
1
panic: panic error

Q2: defer的值捕获问题

func main() {
    var fs = make([]func(), 3)
    for i := 0; i < 3; i++ {
        fs[i] = func() {
            fmt.Print(i, " ")
        }
    }
    for _, f := range fs {
        f()
    }
}

答案: 2 2 2(闭包捕获循环变量i的引用)

修正:

for i := 0; i < 3; i++ {
    v := i  // 创建副本
    fs[i] = func() {
        fmt.Print(v, " ")
    }
}
// 输出:0 1 2

Q3: defer在return之后的执行

func test() (i int) {
    defer func() { i++ }()
    return 5
}
​
func main() {
    fmt.Println(test())  // 输出 6
}

答案: 6。return 5先将i设为5,然后defer执行i++,最终返回6。

总结

最佳实践:

到此这篇关于深度解析Go语言defer机制的文章就介绍到这了,更多相关Go语言defer机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文