如何避免多线程死锁?多线程死锁的原因和幸免策略

在多线程编程过程中 , 不仅仅要解决数据访问的同步与互斥 , 还要注意多线程的死锁问题 , 而造成多线程死锁的原因大概可以归咎于系统资源不足、进程推进顺序不当或者资源分配不当等问题 。有什么方法可以避免多线程死锁的形成吗?请看下文 。

如何避免多线程死锁?多线程死锁的原因和幸免策略

文章插图
一、死锁原因与形成条件:
死锁形成的原因:
1、系统资源不足;
2、进程(线程)推进的顺序不恰当;
3、资源分配不当 。
从编程经验上来讲 , 形成死锁的一般原因有以下几种:
1、个人使用锁的经验差异 。
2、程序模块使用锁的差异 。
3、工程代码版本之间的差异 。
4、工程代码分支之间的差异 。
5、修改代码和重构代码带来的差异 。
死锁形成的条件:
1、互斥条件:所谓互斥就是进程在某一时间内独占资源 。
2、请求与保持条件:一个进程因请求资源而阻塞时 , 对已获得的资源保持不放 。
3、不剥夺条件:进程已获得资源 , 在末使用完之前 , 不能强行剥夺 。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系 。
二、常见死锁形成的场景
死锁形成的常见情况有以下几种:
1、忘记释放锁:
01void data_process()02{03EnterCriticalSection();04if(/* error happens, forget LeaveCriticalSection */)05return;06LeaveCriticalSection();07}复制代码void data_process(){EnterCriticalSection();if(/* error happens, forget LeaveCriticalSection */)return;LeaveCriticalSection();}2、单线程重复申请锁:
01void sub_func()02{03EnterCriticalSection();04do_something();05LeaveCriticalSection();06}07void data_process()08{09EnterCriticalSection();10sub_func();11LeaveCriticalSection();12}复制代码void sub_func(){EnterCriticalSection();do_something();LeaveCriticalSection();}void data_process(){EnterCriticalSection();sub_func();LeaveCriticalSection();}3、多线程多锁申请:
01void data_process1()02{03EnterCriticalSection(&cs1);// 申请锁的顺序有依赖04EnterCriticalSection(&cs2);05do_something1();06LeaveCriticalSection(&cs2);07LeaveCriticalSection(&cs1);08}09void data_process2()10{11EnterCriticalSection(&cs2);// 申请锁的顺序有依赖12EnterCriticalSection(&cs1);13do_something2();14LeaveCriticalSection(&cs1);15LeaveCriticalSection(&cs2);16}复制代码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);}4、环形锁申请:
01/* 多个线程申请锁的顺序形成相互依赖的环形:02*A - B03*||04*C - D05*/复制代码/* 多个线程申请锁的顺序形成相互依赖的环形:*A - B*||*C - D*/三、死锁的避免策略
死锁的代价是非常大的 , 有时候很难检测排查 , 因此需要在编程过程中尽可能的避免发生死锁 。编程中为了避免死锁应该遵循如下策略:
1、在编写多线程程序之前 , 首先编写正确的程序 , 然后再移植到多线程 。
2、时刻检查自己写的程序有没有在跳出时忘记释放锁 。
3、如果自己的模块可能重复使用一个锁 , 建议使用嵌套锁 。
4、对于某些锁代码 , 不要临时重新编写 , 建议使用库里面的锁 , 或者自己曾经编写的锁 。
5、如果某项业务需要获取多个锁 , 必须保证锁的按某种顺序获取 , 否则必定死锁 。
6、编写简单的测试用例 , 验证有没有死锁 。
7、编写验证死锁的程序 , 从源头避免死锁 。

推荐阅读