前言
最近写着写着遇到些稀奇古怪的问题,想着与defer、GOTO以及锁有关,于是整理了一下。
切入正题
首先来看看本文的主角们
十分好用的defer
1 | func main() { |
这是一个defer的用例,很简单,就是在doSomething
结束之后,再执行finishDOsomething
(println
输出到stderr
,fmt.Println
输出到stdout
,我绝对不是为了偷懒少打点才用的),以上代码的输出如下:println
1 | Do something |
没有任何问题,十分正常且符合预期的输出。
掺和一下goto,顺便加了点料
现在笔者想在doSomething
结束后,从头来过,再来一遍,为了便于区分,顺便加了点其他料:
1 | func main() { |
此时程序的输出为
1 | Do something 1 time |
前五行看下来似乎都没什么问题,着重看最后两行,为什么runningTimes
变成2之后仍旧打出了Finish do something 1 time
呢?
这里其实与goto
没有任何关系,是defer
的特性,当程序经过defer
时,会进行参数预计算,也就是说,当runTimes
是1时,加载到defer finishDoSomething(runningTimes)
的时候,就会把finishDoSomething(1)
丢到栈里,所以最后可以打印出来Finish do something 1 time
。
然后是打印次序及顺序的问题,把刚刚的执行过程用代码重新梳理一下可能就会好理解地多:
1 | doSomething(1) |
所以,goto
之前有defer
的照样会执行,很显然,在这个例子里执行了两次defer
(当然预计算的值不一样)。
此时一个人畜无害的Mutex混入其中
此时,出于某种原因,在doSomething
的时候,必须对locker
进行锁定,好让其他人不能同时一起doSomething
,稍加修改:
1 | func main() { |
此时执行此程序,似乎出现了不得了的问题(经典故弄玄虚环节)
1 | Do something 1 time |
竟然出现了死锁!(似乎也不必这么惊讶)
这里面的死锁其实是由于锁重入导致的,Golang是不支持重入锁的,这就会导致死锁。
已知了原因似乎也很好解决了,大不了goto
前解锁呗:
1 | func main() { |
于是梅开二度:
1 | Do something 1 time |
确实,解决了重入问题,但是没有解决Unlock
两次的问题。
从程序跑通的角度来看,利用defer
的特性再加一个defer locker.Lock()
便可解君愁了:
1 | func main() { |
成功了!它跑起来了!开心!(棒读)
原本加locker
的目的在于执行本段代码的时候,有且只有一个goroutine
在执行,但是在上述的修改中,更加积重难返,为了Unlock
特意Lock
这种操作其实大可不必,到这种程度了,修改原来代码的逻辑其实是更好的选择,比如:
1 | func main(){ |
又或者您坚持需要一个可以重入的锁建议自己写一个,可以参考一下以下解决方案:
Go语言如何实现可重入锁? - SegmentFault 思否