首页 科技正文

约搏对战棋牌平台:程序员的踩坑经验总结(四):死锁

admin 科技 2020-05-23 37 0

死锁也是程序员最常见的问题之一了,然则死锁跟内存泄露差异,原理和缘故原由都相对简朴,简朴说就是你等我,我也等你,就这么耗着!

但死锁的影响有时比内存泄露更严重。内存泄露主要是渐进式的,可能重启一下就可以从头最先了。而死锁是重启不了,这只是直接影响而已。死锁一样平常会泛起某个功效或者操作无反映,可能进一步没有了心跳而下线,服务住手。而一样平常的看门狗也发现不了,历程还在。一样平常都需要手动杀历程。以是对于绝大多数的营业都是不可以接受的。

而造成死锁的缘故原由差异也比较大,有的可能只是程序员的一时疏忽,可有的也会让你头痛。

我们以前平台的死锁也是屡见不鲜,我记得的常见的有两种情形。

(一)锁跨度很大,代码的跨度,看上去两个不怎么相关的类,竟然在相互挪用!还带着锁。我印象中我们的流媒体泛起过的一次死锁,就是有两个TCP session各自的两个函数在嵌套挪用。

(二)一把锁,涉及局限很大,锁定一个工具的操作可能已经有四五种,然则涉及使用到的函数却是翻倍甚至几十个都有可能。虽然也在一个类内里,然则类很长,带有统一把锁的函数之间就可能泛起相互挪用。

一看就知道都是设计的问题,不出问题才怪。可是问题要解决啊,针对这些问题,后面我琢磨出了一套方式。

 

案例剖析

案例有点久远,那时没有留下文档,所幸代码还在,针对上面第二种情形的。以是只能是稍微形貌下那时的情形和截图看看最后是若何解决的。

首先我们看下这个类有多长:

    

有没有傻眼。这又要勾起我若干痛苦的回忆。也好吧,让你们开心一下。不外你们也开心不了多久,我都有解决之道:)

看看我留下的痕迹:

     

改动了31行,这还只是关于关键字的搜索。有若干个函数,你猜,哈。我们主要看后面的注释,有两次提到“可能同时”挪用或者进来。你也可以看到,我的解决方式是使用了位运算

这一招又是从上一家学来的。实在现在看许多开源库和内核都是大量使用了位运算,许多文档也提到了,像Redis、文件系统、虚拟内存等。

我们再来看看界说:

    

老的锁已经放注释内里的了,锁的工具是一个链表list。新添加了一个整型变量,把变量的几个值界说成一个枚举类型。

以是这几个情形就代表了几种功效,这里是四种情形,可是实现类内里却有31处!你说能不死锁吗?

 

我们再次还原下那时的情景。

这个list是文件列表,而它的营业无非是增删改查。若是设计简朴的话,一把锁也够了。然则真正简朴设计有这么容易吗?

我们又回到这个类,第一个截图显示2500行,凭据设计基本原则,一样平常一个类不能超过1000行。这里早就可以划分至少三个类了。

怎么划分,有人会建议把这个list单独拿出去,是,我也想过。然则关系庞大了,以是我们又到了第二张图,你看涉及到的函数只会有增删改查吗?

和其他的工具和方式交织在一起了!要想抽丝剥离,只能重构!事实上,后面都重构了。

然则问题要解决。重构是后面的事,一旦泛起这种严重问题,当下就是解决问题。以是我后面去掉了锁,重现界说了新的变量。详细怎么弄? 

见最后这张图,一个变量四个值,然则这四个值可不是延续的,看到了吗,0、1、2、4,为什么?

由于要实现二进制运算,以是他们的的二进制位对应就是,0000、0001、0010、0100。每个值用一位示意一种操作,互不滋扰。该位为1示意占用,若是是0示意未占用。代表了以前的锁状态。

以是虽然锁没有了,然则(锁的)功效照样有的。这是一个方面,不能影响原有的功效,原来的样子(虽然不好看,然则不能再引发其他问题了)。另一方面,问题也要解决,仍然是利用了这几个位!

上面的四个值,对应的不完全是增删改查,详细对应了:初始化、查、删、删而且加四个状态,但现实上操作是后三种。事实上初始化值0也可以说没有占位。

最先我们提到了每个位互不滋扰,现在确定是三个位互不滋扰。以是在进入某种操作时,首先判断当前状态,是可重入照样需要守候

例如说,若是当前只是查,那么继续查(另一个查操作)一定没问题,而其他两种需要稍微等一下,这里的守候是20次sleep的20ms循环,只要查操作竣事,马上进入下一步。

然则若是循环已经完成,而状态依然没转变,那么这里不守候了,直接退出。下次再进来询问。

以是这里差异的操作对应了差异的方式,因情形而异。这样就不会导致死锁。同时,这些改变都需要加日志跟踪,可以发现守候了多久,哪个函数占用时间太长,若是能削减该函数占用的时间就是最好的了。在现实项目中,能优化的也有。但有的就只能惊讶了,有碰到过一个方式内里有挪用两个while嵌套循环,简朴的盘算也行了,有些循环内里还挪用多个方式。以是只能用这种方式了。

 

固然这个解决方式是有点抽象,以是为了说清楚这个方式,我想了良久,其他部门早写完了,剩下这里频频改,希望你能看明了。

我后面再看分布式的锁的实现,原理和庞大水平也不外云云:),由于我们这些代码早就把我给臣服了:(

 

总结和建议

(一)原理与依据

我们上面提到了解决方式,那么它的理论依据是什么?

我们稍微窥视一下锁的实现。linux 2.6 kernel的源码,互斥锁所使用的数据结构:

这里只是列出了内核中,锁的界说,实在它的实现另有许多。有兴趣的可以看源码。我们回到这个主题,不知人人发现没有,实在锁的本质也是一个整型变量

而我就是利用了这个特征,固然也有一点自旋锁的特征。你可以再往会看,第二张图,其中有三处for循环,就是说我会凭据情形举行判断和守候一会,但不是忙守候,就是说到了一定的时间后,我会强制改变状态和退出。以是和自旋锁又有差异

以是总结一下,原理很主要!

(二)死锁的预防

和内存泄露一样,死锁的预防也在于设计。以是代码的质量在于设计!这里同样只针对死锁的问题提几个建议。

1.削减锁定代码的局限

锁定的代码行数,一定用到的时刻才用,只将相关的变量括起来。而不是锁定整个函数。

写段伪代码说明下。

std::mutex  m_mutex;

int  g_diff = 3;

int funA()

{

unique_lock<mutex> lock(m_mutex);

int a = 5;

//中心省去若干

return a+g_diff; 

} 

int funB()

{

int a = 5;

int b = 0;

  {

    unique_lock<mutex> lock(m_mutex);

    b = a+g_diff; 

  }

//中心省去若干

return b;

} 

函数funB一定比函数funA更好。

2.降低锁的粒度

通常,一个变量一把锁,或者一个功效点一把锁,而不是一个类一把锁。

那有的人会说若是要锁住一个类,怎么办?

我见过的只有在一种情形下一个类才需要用到锁,就是把这个类当变量使用。以是这种情形也可以归纳到一个变量,或者说一个工具。而这种情形一样平常用在单例模式中,以是纵然锁住也不可能泛起方式的嵌套而导致死锁。若是你还没明了过来,我建议你看下单例模式的界说,我后面也还会有文章将会先容。很快,后面第二篇吧。

而且这里说的一个变量,或者一个功效点要职责单一。一个类何尝不是云云!

案例内里实在就是函数的功效模糊,类的职责模糊,估量那时都没有设计,横竖把相关的都放一起,一锅乱炖!

以是这是设计和开发内里的大忌!后面就是改不完的Bug、踩不完的坑。。

3.削减锁的使用

只管不用锁、少用锁。非用不可才用锁。

一方面由于多了容易造成死锁,另一方面锁有一定的消耗。上面提到的源码只是一个界说而已,而它的实现不仅仅有几处循环,另有回调函数。

固然,这一点说起来容易,做起来难!详细怎么少用,有没有好的方式?

我的回覆固然是有,请听下回分解。

 

,

Allbet<

www.ysmdfhk244.com欢迎进入欧博平台(Allbet Gaming),欧博平台开放欧博(Allbet)开户、欧博(Allbet)代理开户、欧博(Allbet)电脑客户端、欧博(Allbet)APP下载等业务。

|
版权声明

本文仅代表作者观点,
不代表本站dafa888的立场。
本文系作者授权发表,未经许可,不得转载。

评论