重要
defer语句是Go中一个非常有用的特性,可以将一个方法延迟到包裹defer
的方法返回时执行,在实际应用中,defer
可以充当其他语言中try…catch…
的角色,
也可以用来处理关闭文件句柄等收尾操作。
1. defer使用
1.1 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.
官方文档指出,执行defer的时机为:
- 包裹
defer
的函数返回时 - 包裹
defer
的函数执行到末尾时 - 所在的
goroutine
发生panic
时
1.2 defer执行顺序
当一个方法中有多个defer时, defer会将要延迟执行的方法压栈
,当defer被触发时,将所有压栈
的方法出栈
并执行。
所以defer的执行顺序是LIFO的。
例如
|
|
结果为:
|
|
2. 坑
2.1 返回值是否匿名表现不同
|
|
匿名返回执行顺序为:
将result赋值给返回值(相当于, returnValue=result=0);
检查到
defer
,执行defer的语句,变量result值+1(result=1);返回函数返回值,returnValue(returnValue=0)。
命名返回值执行顺序为:
- 返回值被声明为
result
; - 检查到
defer
,执行defer的语句,变量result值+1(result=1); - 返回函数返回值,result(result=1)。
注意与python的浅拷贝区分开
2.2 for循环中使用可能导致的性能问题
比如:
|
|
此处defer f.Close()
在创建文件对象后声明,看起来没有什么问题。但是和直接调用f.Close()
相比,defer的执行存在着额外的开销。
defer
会存在两方面的资源开销:
defer
对其后需要的参数进行内存拷贝;- 还需要对
defer
结构进行压栈出栈操作。
可以将f.Close()
语句前的defer
去掉,直接调用f.close()
来减少大量defer
导致的额外资源消耗。
2.3 必须判断执行成功,再defer释放资源
如果获取资源的操作返回err参数:
不使用
defer
, 可以选择忽略返回的err参数;使用defer进行延迟释放, 在使用defer之前先判断是否存在err。
资源没有获取成功,对资源执行释放操作会导致释放方法执行报错。必须判断是否获取成功,再释放资源。
正确写法:
|
|
2.4 调用os.Exit时defer不会被执行
当发生panic时,所在goroutine的所有defer会被执行,但是当调用os.Exit()方法退出程序时,defer不会执行。
下面例子中defer
不会输出:
|
|