Go defer 特性和使用场景
golang的defer语句用于延迟调用。defer会在当前函数返回之前执行defer注册的函数。比如deferfunc_defer()这样语句会让你注册一个函数变量到defer的全局链表中,在defer语句所在的函数退出之前调用。
defer可以代替其它语言中try…catch…语句,也可以用来处理释放资源等收尾操作,比如关闭文件句柄、关闭数据库连接等。defer还能用于panic的recovery。
1.defer的特性
我们先深入的剖析下defer具有的特性,知其然也。这些特性是需要我们记住的特点,才能更好的理解defer使用的场景。
1)延迟调用
packagemain
funcmain(){
deferprintln("---defer---")
println("---end---")
}
运行结果:
---end---
---defer---
defer会在main函数所有语句之后,return之前时候调用。核心要点:
延迟调用:defer语句本身虽然是main的第一行,但是println("---end---")先打印的;
defer关键字一定是处于函数上下文:defer必须放在函数内部;
2)LIFO
一个函数中含有有多个defer,调用顺序采用压栈式执行,后入先出(LIFO)。
packagemain
import(
"strconv"
)
funcmain(){
fori:=1;i<=3;i++{
deferprintln("defer-->"+strconv.Itoa(i))
}
println("---end---")
}
压栈式执行,也就是说先注册的函数后调用。如上,我们注册的顺序式1,2,3,最后打印“---end---”,所以执行的结果自然是反着来的,程序输出:
---end---
defer-->3
defer-->2
defer-->1
3)作用域
defer只会和defer语句所在的特定函数绑定在一起,作用域也只在这个函数。从语法上来讲,defer语句也一定要在函数内,否则会报告语法错误。
packagemain
funcmain(){
func(){
deferprintln("---defer---")
}()
println("---end---")
}
如上,defer处于一个匿名函数中,就main函数本身来讲,匿名函数fun(){}()先调用且返回,然后再调用println("---end---"),所以程序输出自然是:
---defer---
---end---
4)异常场景
这个是非常重要的特性:panic也能执行。golang不鼓励异常的编程模式,但是却也留了panic-recover这个异常和捕捉异常的机制。所以defer机制就显得尤为重要,甚至可以说是必不可少的。因为你没有一个无视异常,永保调用的defer机制,很有可能就会发生各种资源泄露,死锁等场景。为什么?因为发生了panic却不代表进程一定会挂掉,很有可能被外层recover住。
packagemain
funcmain(){
deferfunc(){
ife:=recover();e!=nil{
println("---defer---")
}
}()
panic("throwpanic")
}
如上,main函数注册一个defer,且稍后主动触发panic,main函数退出之际就会调用defer注册的匿名函数。再提一点,这里其实有两个要点:
-
defer在panic异常场景也能确保调用;
-
recover必须和defer结合才有意义;
3.总结
-
defer其实并不是golang独创,是多种高级语言的共同选择;
-
defer最重要的一个特点就是无视异常可执行,这个是golang在提供了panic-recover机制之后必须做的补偿机制;
-
defer的作用域存在于函数,defer也只有和函数结合才有意义;
-
defer允许你把配套的两个行为代码放在最近相邻的两行,比如创建&释放资源、加锁&释放锁等,使得代码更易读,编程体验优秀。