nav_path: best_practice
Go语言中的defer特性详解
在Go语言中,defer关键字用于预定函数或方法的执行,这常用于处理成对的操作如打开关闭文件、加解锁、记录时间等。defer的独特之处在于,无论包含它的函数通过何种路径返回,它都确保调用被defer的函数。
示例分析
示例1:defer在函数正常返回前执行
// defer1.gopackage mainimport ("fmt")func test1() {fmt.Println("test")}func main() {fmt.Println("main start")defer test1()fmt.Println("main end")}
执行结果:
main startmain endtest
这里defer test1()确保了test1()在main函数的最末尾执行。
示例2:defer在函数因panic结束前执行
// defer2.gopackage mainimport ("fmt")func test1() {fmt.Println("test")}func test2() {panic(1)}func main() {fmt.Println("main start")defer test1()test2()fmt.Println("main end")}
执行结果:
main starttestpanic: 1...
尽管test2()触发了panic,defer test1()依然得到了执行。
探究:defer是否总会执行?
现在考虑以下代码:
// defer3.gopackage mainimport ("fmt""os")func test1() {fmt.Println("test")}func main() {fmt.Println("main start")defer test1()fmt.Println("main end")os.Exit(0)}
执行结果是:
main startmain end
defer的test1()并未执行。为何会这样?
这是因为os.Exit直接退出当前程序,不会执行任何已经defer的函数。
defer的四个基本原则回顾
defer后必须是函数或方法调用,不能添加括号。defer的函数参数在执行defer表达式时固定。defer的执行顺序是后进先出(LIFO)。defer可以修改所属函数的命名返回值。
Go语言的defer实现原理
type _defer struct {siz int32 // 参数和返回值的内存大小started boolheap 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代码。
