Go语言中的延迟函数defer示例详解!
前言
大家都知道go语言的defer功能很强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦。Go语言中延迟函数defer充当着try...catch的重任,使用起来也非常简便,然而在实际应用中,很多gopher并没有真正搞明白defer、return、返回值、panic之间的执行顺序,从而掉进坑中,今天我们就来揭开它的神秘面纱!话不多说了,来一起看看详细的介绍吧。
先来运行下面两段代码:
A.匿名返回值的情况
packagemain
import(
"fmt"
)
funcmain(){
fmt.Println("areturn:",a())//打印结果为areturn:0
}
funca()int{
variint
deferfunc(){
i++
fmt.Println("adefer2:",i)//打印结果为adefer2:2
}()
deferfunc(){
i++
fmt.Println("adefer1:",i)//打印结果为adefer1:1
}()
returni
}
B.有名返回值的情况
packagemain
import(
"fmt"
)
funcmain(){
fmt.Println("breturn:",b())//打印结果为breturn:2
}
funcb()(iint){
deferfunc(){
i++
fmt.Println("bdefer2:",i)//打印结果为bdefer2:2
}()
deferfunc(){
i++
fmt.Println("bdefer1:",i)//打印结果为bdefer1:1
}()
returni//或者直接return效果相同
}
先来假设出结论(这是正确结论),帮助大家理解原因:
- 多个defer的执行顺序为“后进先出/先进后出”;
- 所有函数在执行RET返回指令之前,都会先检查是否存在defer语句,若存在则先逆序调用defer语句进行收尾工作再退出返回;
- 匿名返回值是在return执行时被声明,有名返回值则是在函数声明的同时被声明,因此在defer语句中只能访问有名返回值,而不能直接访问匿名返回值;
- return其实应该包含前后两个步骤:第一步是给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值);第二步是调用RET返回指令并传入返回值,而RET则会检查defer是否存在,若存在就先逆序插播defer语句,最后RET携带返回值退出函数;
因此,defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着defer开始执行一些收尾工作;最后RET指令携带返回值退出函数。
如何解释两种结果的不同:
上面两段代码的返回结果之所以不同,其实从上面的结论中已经很好理解了。
- a()int函数的返回值没有被提前声名,其值来自于其他变量的赋值,而defer中修改的也是其他变量(其实该defer根本无法直接访问到返回值),因此函数退出时返回值并没有被修改。
- b()(iint)函数的返回值被提前声名,这使得defer可以访问该返回值,因此在return赋值返回值i之后,defer调用返回值i并进行了修改,最后致使return调用RET退出函数后的返回值才会是defer修改过的值。
C.下面我们再来看第三个例子,验证上面的结论:
packagemain
import(
"fmt"
)
funcmain(){
c:=c()
fmt.Println("creturn:",*c,c)//打印结果为creturn:20xc082008340
}
funcc()*int{
variint
deferfunc(){
i++
fmt.Println("cdefer2:",i,&i)//打印结果为cdefer2:20xc082008340
}()
deferfunc(){
i++
fmt.Println("cdefer1:",i,&i)//打印结果为cdefer1:10xc082008340
}()
return&i
}
虽然c()int的返回值没有被提前声明,但是由于c()int的返回值是指针变量,那么在return将变量i的地址赋给返回值后,defer再次修改了i在内存中的实际值,因此return调用RET退出函数时返回值虽然依旧是原来的指针地址,但是其指向的内存实际值已经被成功修改了。
即,我们假设的结论是正确的!
D.补充一条,defer声明时会先计算确定参数的值,defer推迟执行的仅是其函数体。
packagemain
import(
"fmt"
"time"
)
funcmain(){
deferP(time.Now())
time.Sleep(5e9)
fmt.Println("main",time.Now())
}
funcP(ttime.Time){
fmt.Println("defer",t)
fmt.Println("P",time.Now())
}
//输出结果:
//main2017-08-0114:59:47.547597041+0800CST
//defer2017-08-0114:59:42.545136374+0800CST
//P2017-08-0114:59:47.548833586+0800CST
E.defer的作用域
- defer只对当前协程有效(main可以看作是主协程);
- 当任意一条(主)协程发生panic时,会执行当前协程中panic之前已声明的defer;
- 在发生panic的(主)协程中,如果没有一个defer调用recover()进行恢复,则会在执行完最后一个已声明的defer后,引发整个进程崩溃;
- 主动调用os.Exit(int)退出进程时,defer将不再被执行。
packagemain
import(
"errors"
"fmt"
"time"
//"os"
)
funcmain(){
e:=errors.New("error")
fmt.Println(e)
//(3)panic(e)//defer不会执行
//(4)os.Exit(1)//defer不会执行
deferfmt.Println("defer")
//(1)gofunc(){panic(e)}()//会导致defer不会执行
//(2)panic(e)//defer会执行
time.Sleep(1e9)
fmt.Println("over.")
//(5)os.Exit(1)//defer不会执行
}
F.defer表达式的调用顺序是按照先进后出的方式执行
defer表达式会被放入一个类似于栈(stack)的结构,所以调用的顺序是先进后出/后进先出的。
下面这段代码输出的结果是4321而不是1234。
packagemain
import(
"fmt"
)
funcmain(){
deferfmt.Print(1)
deferfmt.Print(2)
deferfmt.Print(3)
deferfmt.Print(4)
}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。