golang的defer语句用于延迟调用。defer会在当前函数返回之前执行defer注册的函数。比如deferfunc_defer()这样语句会让你注册一个函数变量到defer的全局链表中,在defer语句所在的函数退出之前调用。
defer可以代替其它语言中try…catch…语句,也可以用来处理释放资源等收尾操作,比如关闭文件句柄、关闭数据库连接等。defer还能用于panic的recovery。
我们先深入的剖析下defer具有的特性,知其然也。这些特性是需要我们记住的特点,才能更好的理解defer使用的场景。
运行结果:
---end--- ---defer---defer会在main函数所有语句之后,return之前时候调用。核心要点:
延迟调用:defer语句本身虽然是main的第一行,但是println("---end---")先打印的;
defer关键字一定是处于函数上下文:defer必须放在函数内部;
一个函数中含有有多个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-->1defer只会和defer语句所在的特定函数绑定在一起,作用域也只在这个函数。从语法上来讲,defer语句也一定要在函数内,否则会报告语法错误。
packagemain funcmain(){ func(){ deferprintln("---defer---") }() println("---end---") }如上,defer处于一个匿名函数中,就main函数本身来讲,匿名函数fun(){}()先调用且返回,然后再调用println("---end---"),所以程序输出自然是:
---defer--- ---end---这个是非常重要的特性: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结合才有意义;
defer其实并不是golang独创,是多种高级语言的共同选择;
defer最重要的一个特点就是无视异常可执行,这个是golang在提供了panic-recover机制之后必须做的补偿机制;
defer的作用域存在于函数,defer也只有和函数结合才有意义;
defer允许你把配套的两个行为代码放在最近相邻的两行,比如创建&释放资源、加锁&释放锁等,使得代码更易读,编程体验优秀。
原文链接: