【导读】go 项目中出现了死锁如何排查问题、解决问题?本文做了详细介绍。
发现了死锁怎么办?自然是要解之。。最近刚好解决了两个死锁,分享一下心得,希望能帮助到大家~
我把死锁分为两种,一种为简单死锁,另一种为并发场景下的死锁,接下来我们分别来说一说。
所谓简单死锁,就是写代码的时候粗心大意写出来的,比如说我们写了一个这样的函数:
函数A会在获取锁之后调用函数B,而B也要获取这个锁,这时候显然就会发生死锁,而且是百分百出现的。
现在你看着我的这个图可能觉得,我怎么可能犯如此低级的错误,但实际上,如果函数A不是你写的,而且你在B里面又调用了其他函数,和A跨了好几层,这时候你就很有可能忽略A这个外层函数已经加了锁。
那么这种锁该如何避免呢?很简单,写单元测试,写单元测试是一个很好的习惯,实际上,如果单元测试写的好的话,很多逻辑问题不用通过实际测试就可以得到很好的验证。
回到图中的这种情况,如果函数A本身是有单元测试的,而你修改了B的逻辑,加上了一个外层已经加了的锁,那么这个时候,A的单元测试在运行的时候就会卡住,你也就可以及时发现问题。
当然还有一种可能是,你只修改了B的逻辑,那么虽然A是有单元测试,但是你并不会去测A,这个也没关系,如果你的项目配置了ci,那么提交到github或者gitlab上的时候就会帮你执行一遍所有的单元测试,如果有死锁,ci会卡住,直到超时,然后显示ci不通过,这个时候你就也可以知道,你写了个死锁~
我相信更多人打开这篇文章,想知道的就是高并发情况下,出现了死锁,要如何排查,这确实是一个令人头疼的问题,我第一次遇到这种死锁,是在一次修改了程序,压测的过程中,这个问题非常难复现,必须连续压测好几个小时才能复现,当时我真的是感受到了绝望,虽然我通过pprof定位到了是哪个锁卡住了,但问题是,这个锁有超多地方会调用,怎么定位是在哪里死锁了,又是为什么死锁的呢?
其实,死锁问题是有一套清晰的解决思路的,我们首先要做的是,莫慌,冷静下来,一步一步分析。
下面假设我们通过pprof看到的卡住的锁叫做Lock
首先,为什么会卡住?
因为获取不到Lock。
为什么获取不到Lock?
因为有其他goroutine获取到了Lock,但是一直没有释放。
但这里,你已经可以采取第一步行动了,那就是查看所有获取这个锁的地方,看看在释放之前,是否存在卡住的可能。
这个排查,其实往往是很快的,不要想着说,这个锁有几十个地方会获取,就望而却步了,事实上,大部分情况,你的锁只是用来锁住关键的资源,比如一个可能并发读写的map,而你上锁之后也只是读写了一下map,那么很显然,map的读写不可能会阻塞注,导致你释放不了这个锁的,所以这些获取锁的地方你都可以直接排除,肯定不是在这些地方出的问题。
经过这一轮排查,你会发现,可能有问题的地方已经所剩无几了,剩下的都是获取锁之后,释放锁之前之间的过程可能会卡住的地方,欧克,我们继续分析。
什么样的逻辑可能卡住?
1.可能是一个rpc调用,或者一个http调用,你没有设置超时时间,而和远端的交互又出了问题,一直拿不到回复
2.可能是在读写数据库,这时也有可能会由于数据量太大或者sql写的不够好或者和数据库之间的连接出现了问题,导致卡很久
如果是以上这些情况,你就要考虑一下了,这些操作有必要锁住吗?
如果没有,开一个新的goroutine让他慢慢去做就好了;
如果又必要锁住,那你一定要设置一个超时时间,不要让程序一直在这里傻傻的等待。。
另外一种情况,是我要着重介绍的,那就是交叉锁
何谓交叉锁,假设有两个锁,Lock1和Lock2,然后有两个goroutine,逻辑分别是这样的:
我想你应该已经看出问题了,那就是如果在某一时刻,goroutine1获取到了Lock1,而goroutine获取到了Lock2,那么接下来,两个goroutine就都无路可走了,后续如果又其他想获取Lock1和获取Lock2的goroutine,也都会卡死。不要觉得这种情况概率低就不可能出现,高并发的情况下,一切皆有可能!
我们回到排查的过程中,如果你发现Lock在某处可能卡住,而卡住的原因是因为这里在释放Lock之前,还申请了其他锁,假设叫otherLock吧,那么这个时候,你就可以开始第二轮排查了,排查对象,就是所有获取otherLock的地方,而排查目标就是,在释放otherLock之前,需要获取Lock的地方。
如果发现了这样的地方,那么你的死锁,十有八九就是这里导致的,接下来,你又该好好思考一下了,在锁住Lock的情况下,真的有必要再锁住otherLock吗?
如果没有必要,那你就把申请otherLock放在Lock释放后面去,或者开一个goroutine,在goroutine里再获取otherLock;
如果真的有必要,那么对不起,你的想法有问题,再好好想想吧。。
最后总结一下,三点,
第一,养成写单元测试的好习惯,简单的死锁往往可以通过单元测试解决~
第二,一定要避免交叉锁,这是一定会有问题的~
第三,不要慌张,遇到死锁,按我的思路,冷静排查,也许需要很久,但你一定可以查到的,加油吧!
如果以上思路还解决不了你的问题,也欢迎留言,欢迎吐槽,大家一起讨论嘛,哈哈
转自:
blog.csdn.net/u013536232/article/details/107868474
- EOF -
Go 开发大全
参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。
关注后获取
回复 Go 获取6万star的Go资源库
分享、点赞和在看
支持我们分享更多好文章,谢谢!