内容简介:defer语句是Go中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,在实际应用中,defer语句可以充当其他语言中try…catch…的角色,也可以用来处理关闭文件句柄等收尾操作。Go官方文档中对defer的执行时机做了阐述,分别是。当一个方法中有多个defer时, defer会将要延迟执行的方法“压栈”,当defer被触发时,将所有“压栈”的方法“出栈”并执行。所以defer的执行顺序是LIFO的。
defer语句是 Go 中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,在实际应用中,defer语句可以充当其他语言中try…catch…的角色,也可以用来处理关闭文件句柄等收尾操作。
defer触发时机
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
Go官方文档中对defer的执行时机做了阐述,分别是。
- 包裹defer的函数返回时
- 包裹defer的函数执行到末尾时
- 所在的goroutine发生panic时
defer执行顺序
当一个方法中有多个defer时, defer会将要延迟执行的方法“压栈”,当defer被触发时,将所有“压栈”的方法“出栈”并执行。所以defer的执行顺序是LIFO的。
所以下面这段代码的输出不是1 2 3,而是3 2 1。
func stackingDefers() { defer func() { fmt.Println("1") }() defer func() { fmt.Println("2") }() defer func() { fmt.Println("3") }() }
坑1:defer在匿名返回值和命名返回值函数中的不同表现
先看下面两个方法执行的结果。
func returnValues() int { var result int defer func() { result++ fmt.Println("defer") }() return result } func namedReturnValues() (result int) { defer func() { result++ fmt.Println("defer") }() return result }
上面的方法会输出0,下面的方法输出1。上面的方法使用了匿名返回值,下面的使用了命名返回值,除此之外其他的逻辑均相同,为什么输出的结果会有区别呢?
要搞清这个问题首先需要了解defer的执行逻辑,文档中说defer语句在方法返回“时”触发,也就是说return和defer是“同时”执行的。以匿名返回值方法举例,过程如下。
- 将result赋值给返回值(可以理解成Go自动创建了一个返回值retValue,相当于执行retValue = result)
- 然后检查是否有defer,如果有则执行
- 返回刚才创建的返回值(retValue)
在这种情况下,defer中的修改是对result执行的,而不是retValue,所以defer返回的依然是retValue。在命名返回值方法中,由于返回值在方法定义时已经被定义,所以没有创建retValue的过程,result就是retValue,defer对于result的修改也会被直接返回。
坑2:在for循环中使用defer可能导致的性能问题
看下面的代码
func deferInLoops() { for i := 0; i < 100; i++ { f, _ := os.Open("/etc/hosts") defer f.Close() } }
defer在紧邻创建资源的语句后生命力,看上去逻辑没有什么问题。但是和直接调用相比,defer的执行存在着额外的开销,例如defer会对其后需要的参数进行内存拷贝,还需要对defer结构进行压栈出栈操作。所以在循环中定义defer可能导致大量的资源开销,在本例中,可以将f.Close()语句前的defer去掉,来减少大量defer导致的额外资源消耗。
坑3:判断执行没有err之后,再defer释放资源
一些获取资源的操作可能会返回err参数,我们可以选择忽略返回的err参数,但是如果要使用defer进行延迟释放的的话,需要在使用defer之前先判断是否存在err,如果资源没有获取成功,即没有必要也不应该再对资源执行释放操作。如果不判断获取资源是否成功就执行释放操作的话,还有可能导致释放方法执行错误。
正确写法如下。
resp, err := http.Get(url) // 先判断操作是否成功 if err != nil { return err } // 如果操作成功,再进行Close操作 defer resp.Body.Close()
坑4:调用os.Exit时defer不会被执行
当发生panic时,所在goroutine的所有defer会被执行,但是当调用os.Exit()方法退出程序时,defer并不会被执行。
func deferExit() { defer func() { fmt.Println("defer") }() os.Exit(0) }
上面的defer并不会输出。
点击关注知乎专栏 Golang私房菜
以上所述就是小编给大家介绍的《Go语言中defer的一些坑》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 编译型语言、解释型语言、静态类型语言、动态类型语言概念与区别
- 计算机语言发展的三个阶段:机器语言、汇编语言与高级语言
- 凹 (“Wa”) 语言:可以嵌入 Go 语言环境的脚本语言
- Rust语言恰巧是一门解决了Go语言所有问题的语言
- 获取系统语言/当前 App支持语言
- 【Go 语言教程】Go 语言简介
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web软件用户界面设计指南
林锐、唐勇、石志强 / 电子工业出版社 / 2005-5-1 / 20.00元
Web软件用户界面设计指南,ISBN:9787121010163,作者:林锐等编著一起来看看 《Web软件用户界面设计指南》 这本书的介绍吧!