如何避免多线程死锁?多线程死锁的原因和幸免策略
在多线程编程过程中,不仅仅要解决数据访问的同步与互斥,还要注意多线程的死锁问题,而造成多线程死锁的原因大概可以归咎于系统资源不足、进程推进顺序不当或者资源分配不当等问题。有什么方法可以避免多线程死锁的形成吗?请看下文。
一、死锁原因与形成条件:
死锁形成的原因:
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 - B
- 03* | |
- 04* C - D
- 05*/
/* 多个线程申请锁的顺序形成相互依赖的环形: * A - B * | | * C - D */
三、死锁的避免策略
死锁的代价是非常大的,有时候很难检测排查,因此需要在编程过程中尽可能的避免发生死锁。编程中为了避免死锁应该遵循如下策略:
1、在编写多线程程序之前,首先编写正确的程序,然后再移植到多线程。
2、时刻检查自己写的程序有没有在跳出时忘记释放锁。
3、如果自己的模块可能重复使用一个锁,建议使用嵌套锁。
4、对于某些锁代码,不要临时重新编写,建议使用库里面的锁,或者自己曾经编写的锁。
5、如果某项业务需要获取多个锁,必须保证锁的按某种顺序获取,否则必定死锁。
6、编写简单的测试用例,验证有没有死锁。
7、编写验证死锁的程序,从源头避免死锁。
上文便是多线程死锁的原因和幸免策略,死锁在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态,在编程过程中根据线程逻辑进行操作,将可以有效果避免多线程死锁的发生。
{else}在多线程编程过程中,不仅仅要解决数据访问的同步与互斥,还要注意多线程的死锁问题,而造成多线程死锁的原因大概可以归咎于系统资源不足、进程推进顺序不当或者资源分配不当等问题。有什么方法可以避免多线程死锁的形成吗?请看下文。
一、死锁原因与形成条件:
死锁形成的原因:
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 - B
- 03* | |
- 04* C - D
- 05*/
/* 多个线程申请锁的顺序形成相互依赖的环形: * A - B * | | * C - D */
三、死锁的避免策略
死锁的代价是非常大的,有时候很难检测排查,因此需要在编程过程中尽可能的避免发生死锁。编程中为了避免死锁应该遵循如下策略:
1、在编写多线程程序之前,首先编写正确的程序,然后再移植到多线程。
2、时刻检查自己写的程序有没有在跳出时忘记释放锁。
3、如果自己的模块可能重复使用一个锁,建议使用嵌套锁。
4、对于某些锁代码,不要临时重新编写,建议使用库里面的锁,或者自己曾经编写的锁。
5、如果某项业务需要获取多个锁,必须保证锁的按某种顺序获取,否则必定死锁。
6、编写简单的测试用例,验证有没有死锁。
7、编写验证死锁的程序,从源头避免死锁。
上文便是多线程死锁的原因和幸免策略,死锁在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态,在编程过程中根据线程逻辑进行操作,将可以有效果避免多线程死锁的发生。