defer是golang语言中的关键字,用于资源的释放,会在函数返回之前进行调用。一般采用如下模式:
<pre style="white-space:pre-wrap; word-wrap:normal; overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; margin-top:0px; margin-bottom:0px; line-height:1.45; padding:16px; word-break:normal; background-color:rgb(247,247,247)">f,err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()</pre>
如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。
不过如果对defer的了解不够深入,使用起来可能会踩到一些坑,尤其是跟带命名的返回参数一起使用时。在讲解defer的实现之前先看一看使用defer容易遇到的问题。
先来看看几个例子。例1:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">func f() (result int) {
defer func() {
result
}()
return 0
}</pre>
例2:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">func f() (r int) {
t := 5
defer func() {
t = t 5
}()
return t
}</pre>
例3:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">func f() (r int) {
defer func(r int) {
r = r 5
}(r)
return 1
}</pre>
请读者先不要运行代码,在心里跑一遍结果,然后去验证。
例1的正确答案不是0,例2的正确答案不是10,如果例3的正确答案不是6......
defer是在return之前执行的。这个在 官方文档中是明确说明了的。要使用defer时不踩坑,最重要的一点就是要明白,<span style="">return xxx这一条语句并不是一条原子指令!</span>
函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。
defer表达式可能会在设置函数返回值之后,在返回到调用函数之前,修改返回值,使最终的函数返回值与你想象的不一致。
其实使用defer时,用一个简单的转换规则改写一下,就不会迷糊了。改写规则是将return语句拆成两句写,return xxx会被改写成:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">返回值 = xxx
调用defer函数
空的return</pre>
先看例1,它可以改写成这样:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">func f() (result int) {
result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令
func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
result
}()
return
}</pre>
所以这个返回值是1。
再看例2,它可以改写成这样:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">func f() (r int) {
t := 5
r = t //赋值指令
func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
t = t 5
}
return //空的return指令
}</pre>
所以这个的结果是5。
最后看例3,它改写后变成:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">func f() (r int) {
r = 1 //给返回值赋值
func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
r = r 5
}(r)
return //空的return
}</pre>
所以这个例子的结果是1。
defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是return xxx语句并不是一条原子指令,defer被插入到了赋值 与 ret之间,因此可能有机会改变最终的返回值。
<h2 style="font-family:'Helvetica Neue',Helvetica,'Segoe UI',Arial,freesans,sans-serif; font-weight:300; line-height:1.225; color:rgb(51,51,51); margin-top:1em; margin-bottom:16px; font-size:1.75em; position:relative; padding-bottom:0.3em; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(238,238,238)">
defer的实现</h2>
defer关键字的实现跟go关键字很类似,不同的是它调用的是runtime.deferproc而不是runtime.newproc。
在defer出现的地方,插入了指令call runtime.deferproc,然后在函数返回之前的地方,插入指令call runtime.deferreturn。
普通的函数返回时,汇编代码类似:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">add xx SP
return</pre>
如果其中包含了defer语句,则汇编代码是:
<pre style="overflow:auto; font-family:Consolas,'Liberation Mono',Menlo,Courier,monospace; font-size:13.6px; padding:16px; margin-top:0px; margin-bottom:0px; line-height:1.45; word-break:normal; word-wrap:normal; border:1px solid rgb(204,204,204); background-color:rgb(247,247,247)">call runtime.deferreturn,
add xx SP
return</pre>
goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从defer表中出栈并执行。
<blockquote class="layui-elem-quote" style="width: 100%;overflow:hidden">
作者: shenlanzifa
链接: http://blog.csdn.net
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
</blockquote>
到此这篇关于“Golang defer 使用时的坑”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!
您可能感兴趣的文章:
Go语言中defer的一些坑
Defer
Golang中defer、return、返回值之间执行顺序的坑
Golang中defer关键字实现原理
golang中defer
Golang defer 使用时的坑
Go 中 defer 的 5 个坑 - 第一部分
关于 Golang 开发中遇到的坑
Go 关键字 defer 的一些坑你需要踩踩
16.defer让代码更清晰