点击上方蓝色字体关注我们
golang 中的延迟调用函数
延迟调用函数(Deferred Function Calls)的调用方式如下:
defer func_name(param-list)
1 执行的时机
func f1() (ret int) {
defer func() {
ret++
}()
return 0
}
func f2() (ret int) {
tmp := 1
defer func() {
tmp = tmp + 10
}()
return tmp
}
func f3() (ret int) {
defer func(ret int) {
ret = ret + 10
}(ret)
return 1
}
先不要运行程序,心里记下自己认为分别运行这三个函数的结果,然后我们慢慢分析。
这里先说结论:
网上有篇博文讲的defer的实现方式就是在defer出现的地方插入指令:
CALL runtime.deferproc
然后在函数返回之前的地方插入指令:
CALL runtime.deferreturn
同时由于 return ret这一句并不是原子操作,它是分为两步来执行的:
(1)先在栈中写一个值,这个值被当作返回值;
(2)然后调用空的return语句。
那么有了defer之后,defer的执行是被插入在return之前赋值指令之后的。
有了这个结论,我们来分析前面的代码的结果:
f1():先给返回的值ret=0赋值,然后执行defer,把ret++,这样最后返回出来的结果就是 1 ;
f2():return之前 tmp的值是1,执行ret = 1,然后执行defer,把tmp+=10,此时在返回,不影响返回结果,最终结果还是1;
f3():return之前,先执行ret=1的赋值操作,然后,将ret值传递给匿名函数,执行的结果不影响之前的值,所以ret函数最终的返回值是1。
2 执行的顺序
func f4() {
defer func() {
fmt.Println("1-1")
defer func() {
fmt.Println("1-1-1")
}()
}()
defer func() {
fmt.Println("2-2")
defer func() {
fmt.Println("2-2-2")
}()
}()
}
猜猜看,调用f4之后出现的打印是什么:
结论是
可以看出它是基于先进后出的方式存储defer函数的,也就是逆序执行的。
在goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式写入表中,而在调用runtime.deferreturn的时候,则会依次从defer表中取出并执行。
3 使用defer值得注意的地方
1 经常在加锁的地方紧接着使用 defer 去解锁,这样写是非常方便,不用在加锁之后,每个return的地方都要去解锁,但是别忘了加锁之后要是有阻塞的操作,导致函数一直没有办法返回,那么就出现了问题。
func f5() {
lc.Lock()
defer lc.Unlock()
for { // 长期执行某个操作
if 0 { // 达到某种条件之后退出
break
}
}
return
}
如上f5(),如果lc这把锁有其他的地方在用,那么就导致了死锁的问题。
扫描二维码关注我们
查看原文:http://www.zoues.com/2016/10/20/golang%e4%b9%8bdefer/
有疑问加站长微信联系(非本文作者)