logo

🎁 最佳实践

作者
Modified on
Reading time
2 分钟阅读:..评论:..

Go语言中的defer特性详解

在Go语言中,defer关键字用于预定函数或方法的执行,这常用于处理成对的操作如打开关闭文件、加解锁、记录时间等。defer的独特之处在于,无论包含它的函数通过何种路径返回,它都确保调用被defer的函数。

示例分析

示例1:defer在函数正常返回前执行

// defer1.go package main import ( "fmt" ) func test1() { fmt.Println("test") } func main() { fmt.Println("main start") defer test1() fmt.Println("main end") }

执行结果:

main start main end test

这里defer test1()确保了test1()main函数的最末尾执行。

示例2:defer在函数因panic结束前执行

// defer2.go package main import ( "fmt" ) func test1() { fmt.Println("test") } func test2() { panic(1) } func main() { fmt.Println("main start") defer test1() test2() fmt.Println("main end") }

执行结果:

main start test panic: 1 ...

尽管test2()触发了panicdefer test1()依然得到了执行。

探究:defer是否总会执行?

现在考虑以下代码:

// defer3.go package main import ( "fmt" "os" ) func test1() { fmt.Println("test") } func main() { fmt.Println("main start") defer test1() fmt.Println("main end") os.Exit(0) }

执行结果是:

main start main end

defer的test1()并未执行。为何会这样?

这是因为os.Exit直接退出当前程序,不会执行任何已经defer的函数。

defer的四个基本原则回顾

  1. defer后必须是函数或方法调用,不能添加括号。
  2. defer的函数参数在执行defer表达式时固定。
  3. defer的执行顺序是后进先出(LIFO)。
  4. defer可以修改所属函数的命名返回值。

Go语言的defer实现原理

type _defer struct { siz int32 // 参数和返回值的内存大小 started bool heap bool // 是否分配在堆上 openDefer bool // 是否进行了优化 sp uintptr // 栈指针 pc uintptr // 程序计数器 fn *funcval // 被defer的函数 _panic *_panic // defer的panic信息 link *_defer // defer链表 }

Go语言内部通过一个链表维护defer调用,这也解释了为何它们会按LIFO顺序执行。

总结

在Go中使用defer需要记住:并非所有情况下defer都会执行。例如,os.Exit会导致程序立即退出,此时defer不会被调用。了解defer的原理和限制能帮助我们更合理地编写Go代码。