死锁的条件、原因以及场景分析
死锁可以称为进程死锁。那么是在多进程(并发)情况下可能会出现的。
指的是多个进程因为竞争资源而造成的僵局(互相等待),没有外力,那么所有进程都会无法向前推进。
所以是在操作系统和并发程序设计中需要特别考虑的问题。
因此,可以可以得出如下的场景和必备条件。
场景:
- 系统资源的竞争。只有资源不足时才会出现死锁可能,另外,可剥夺资源的竞争是不会引发死锁的;
- 进程推进顺序不对。多进程在运行时,请求和释放资源的顺序不当。
- 系统资源分配不当。
四大必要条件:
- 互斥:进程对分配到的资源排它性使用。独占资源,是由资源本身的属性决定的。
- 请求和保持:保持已有资源,同时请求新的资源,在请求过程中以及因为没有得到新资源而阻塞,已有资源仍然保持;
- 不可剥夺:进程已有的资源在使用完之前不能被剥夺,只能自己释放;
- 环路等待:必然存在一个进程资源环形请求链。
死锁预防:打破之前四个条件
- 打破互斥在实际中应用不大;
- 打破请求与保持,可以实行资源预先分配策略,即进程在运行前一次性申请所需要的全部资源,如果不能满足,则暂不运行。实际应用中,进程在执行时是动态的,不可预测的,并且资源利用率低,降低了进程并发性。
- 打破不可剥夺,当请求新资源不能满足,需要释放已有资源,系统性能受到很大降低
- 打破循环等待:实行资源有序分配策略。可以将资源事先分类编号,按号分配,使进程在申请、占用资源是不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。但是也有问题,合理编号困难,增大系统开销,另外也增加了进程对资源的占有时间。
死锁避免:
不限制进程有关申请资源的命令,而是对进程所发出的每一个申请资源命令加以动态地检查,并根据检查结果决定是否进行资源分配。就是说,在资源分配过程中若预测有发生死锁的可能性,则加以避免。这种方法的关键是确定资源分配的安全性。
银行家算法(1968年):允许进程动态地申请资源,系统在每次实施资源分配之前,先计算资源分配的安全性,若此次资源分配安全(即资源分配后,系统能按某种顺序来为每个进程分配其所需的资源,直至最大需求,使每个进程都可以顺利地完成),便将资源分配给进程,否则不分配资源,让进程等待。
死锁检测与修复:
预防和避免的手段达到排除死锁的目的是很困难的。一种简便的方法是系统为进程分配资源时,不采取任何限制性措施,但是提供了检测和解脱死锁的手段:能发现死锁并从死锁状态中恢复出来。因此,在实际的操作系统中往往采用死锁的检测与恢复方法来排除死锁。
----------------------------------------------------------------------------------------------------
死锁常见的场景如下:
忘记释放锁:
void data_process() { EnterCriticalSection(); if(/* error happens, forget LeaveCriticalSection */) return; LeaveCriticalSection(); }
单线程重复申请锁(所以单线程的时候也有可能进入死锁)
void sub_func() { EnterCriticalSection(); do_something(); LeaveCriticalSection(); } void data_process() { EnterCriticalSection(); sub_func(); LeaveCriticalSection(); }
多线程多锁申请
void data_process1() { EnterCriticalSection(&cs1); // 申请锁的顺序有依赖 EnterCriticalSection(&cs2); do_something1(); LeaveCriticalSection(&cs2); LeaveCriticalSection(&cs1); } void data_process2() { EnterCriticalSection(&cs2); // 申请锁的顺序有依赖 EnterCriticalSection(&cs1); do_something2(); LeaveCriticalSection(&cs1); LeaveCriticalSection(&cs2); }
多线程环形锁
编程中如何来避免:
- 检查有没有忘记释放锁
- 如果自己模块可能重复使用一个锁,建议使用嵌套锁
- 建议使用库里面的锁
- 如果某项业务需要获取多个锁,保证锁按某种顺序来获取
- 编写简单测试用例验证是否存在死锁
参考:https://www.cnblogs.com/kuliuheng/p/4071555.html