出现线程死锁缺陷一般有那些原因?该怎么解决? 您所在的位置:网站首页 多线程循环打印123 出现线程死锁缺陷一般有那些原因?该怎么解决?

出现线程死锁缺陷一般有那些原因?该怎么解决?

2023-04-07 18:51| 来源: 网络整理| 查看: 265

死锁就是死锁,不要扯线程。搅合到一起还有法讨论吗?

死锁说白了,就是“多个进程/线程争用若干资源、却陷入了某种‘逻辑陷阱’,无法自发脱离”这么一种状态。

典型的死锁案例如:

小张需要借30块钱去理发,去找老板小王打工小王说理发店还欠我30块钱,他给了我才有钱雇你理发店说我没生意怎么还得上小王的钱

于是,整个系统就卡死了。

说的抽象点,计算机系统的死锁总是:

多个进程/线程(以后统称为进程)请求访问多个资源进程A拿到某个资源的访问权之后,继续访问下一个资源下一个资源被进程B占有,同时进程B请求访问另一个资源如此循环依赖,最后的一个进程占有着其他进程需要的某个资源、却去请求进程A占有的资源

最终,这些进程相互依赖、又相互阻止,陷入逻辑上的“死锁”状态,永远无法推进到下一步。

“死锁”的出现依赖如下四个条件,任何一个条件被破坏,死锁就不可能出现:

互斥访问:资源只能被少数进程占有,无法共享访问保持和请求:进程可以在持有资源的同时,申请访问其他资源无法剥夺:进程持有的资源,除非自愿出让,否则无法剥夺下来、交给其他进程使用环路等待:进程和资源持有/请求存在环路,比如,以 进程1(A)->B表示进程1持有资源A、请求资源B的话,死锁就是:进程1(A)->B 同时 进程2(B)->A

那么,为了避免死锁,我们可以针对性的破坏以上四个条件之一或者全部:

破坏“互斥访问”条件:把资源从“独占式”设计修改为“共享访问”模式但有些资源可能天生就无法共享使用,比如打印机我们可以把打印机变通为“打印队列”,但这需要占用更多内存队列式使用使得资源访问模式从“同步”变成了“异步”,这会增加程序设计的复杂性破坏“保持和请求”条件:禁止进程在持有资源时继续申请其他资源,要么一次申请到所有资源,要么马上归还申请到的部分资源、从头开始申请这会严重降低系统性能破坏“无法剥夺”条件:典型如“抢占式多进程”,我们可以探测每个进程占有某个资源的时间,超时就强制剥夺这会严重增加系统的复杂性资源本身要允许“间断使用”,也就是允许当前任务暂时挂起和恢复,这可能是无法做到的进程本身要能够识别自身被剥夺了资源使用权、设置逻辑恢复资源状态并借着上次的中断点继续使用,这显然是有极大的技术难度的破坏“环路等待”条件:比如给资源编号,只能在占有序号靠前的资源时申请序号靠后的,不能在占有序号靠后的资源时申请序号靠前的(此时要主动放弃资源)管理复杂,影响性能

综上,我们的确可以想办法、通过破坏死锁产生的四个条件之一(或者全部)来预防死锁;但这需要付出很大的代价、有的时候甚至是做不到的。

无法预防死锁,那么还可以走另一条路,就是自动识别死锁、撤销死锁。

比如,我们可以把所有的进程、资源以及它们的占用情况视为“有向图”,进程1占有资源A、申请资源B可以看作有向图的一条“边(A->B)”;那么识别死锁就是识别图中是否出现了环路;一旦发现了死锁、且在一定时间内没有解除,我们就可以强行杀死环路中较为不重要的一个进程、从而破坏环路。

当然,这也要求进程对资源的使用是“非破坏性的”,或者更严格的,满足事务性要求——当我们杀死一个进场时,资源要恢复原状、不能影响后续逻辑。

同时,图相关算法的消耗总是非常大的。对于一个复杂系统,频繁的检查死锁需要的资源可能是大的无法接受的。

另外,这里也体现了线程和进程的差别:进程是可以强行杀死而不影响其他进程的;但线程嘛……

换句话说,最后这个“杀死陷入死锁的进程”的思路可用于进程,却无法应用于线程(除非你搞一大堆复杂的消息通知机制,比如通过某个信号让线程自行退避……到处嵌入这玩意儿带来的复杂度很可能会抵消它带来的好处)。

缺乏隔离性,这是线程的固有缺陷

除了上面提到的“真死锁”之外,实现中,由于认识不足、技术水平不到,还可能搞出很多的“伪死锁”。

比如,这是初学者经常犯的设计错误:

红色的线程A和绿色的线程B争用黑色的资源C;当它们获得资源时,显示为粗线;而当它们释放了资源时,显示为细线。

明显可以看出,线程A和B都倾向于长期持有资源C,只有很小的“窗口期”可以“交接资源”。

这就是所谓的“锁粒度过大”问题。

这会使得另一个线程长期得不到执行权、大幅降低系统吞吐率。

正确的设计是这样:

细线表示线程A和B处理其他逻辑(使用其他资源);粗线是占有并使用C。

很显然,这样可以极大的提高资源C的利用率。这就是降低锁粒度的必要性。

这个技术发展到极致,就是所谓的“无锁编程”——无锁编程并不是真的不用锁,而是想办法把锁定资源C的时间缩减到一条CPU指令的水平,从而极大幅度的提高执行效率。

当然,实践中情况往往会更加复杂;但我们必须清楚的知道,“并行”究竟出现在哪里、在哪几个资源之间,然后通过精心的设计,才能使得“并行”出现的尽可能多而“资源争夺/等待”出现的尽可能少。

如果搞反了,那么多进程/线程的实际表现就可能是“CPU、磁盘、网络极度繁忙,但正事一点没干,全都消耗在争抢资源上面”——此时,外在表现也是程序进度无法推进,似乎陷入了“死锁”;但这并不是死锁,而是设计不良。找死锁要答案是拜错了菩萨。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有