死锁
定义:
是指多个进程或线程在执行过程中,由于竞争资源或因通信的需要而产生的相互等待的状态,使得它们无法继续执行下去(单线程中使用不恰当也会导致死锁问题)。
如下为常见的死锁原因:
a. 互斥条件:
至少有一个资源必须处于不可共享的状态,即某个资源在同一时刻只能被一个线程或进程占用。如果其他线程或进程请求该资源,它们必须等待该资源被释放。
b. 请求与保持条件:
一个线程已经持有至少一个资源,但又请求其他线程占有的资源,并且在等待的过程中保持对已占有资源的控制。
c. 不剥夺条件:
已经分配给一个线程的资源,在该线程使用完之前不能被强制剥夺。资源只能由线程自己释放。
d. 循环等待条件:
在一个线程的等待链中,存在一个环路,使得每个线程都在等待另一个线程释放资源。即形成了一种“循环等待”状态。
如下为常见的死锁原因对应代码分析:
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
std::mutex mtxAB;
std::recursive_mutex rmtxAB;
std::mutex mtx_C;
std::mutex mtx_D;
void B(void);
void B1(void);
// 死锁1——demo1
void A(void)
{
lock_guard<std::mutex> lock(mtxAB);
// 保护资源
B();
}
void B(void)
{
A();
}
// 死锁2——demo2
void A1(void)
{
// lock_guard<std::mutex> lock(mtxAB);
// 解决这种死锁方法
lock_guard<std::recursive_mutex> lock(rmtxAB);
// 保护资源
B1();
}
void B1(void)
{
// lock_guard<std::mutex> lock(mtxAB);
// 解决这种死锁方法
lock_guard<std::recursive_mutex> lock(rmtxAB);
// 保护资源
}
// 死锁3——demo1
void C(void)
{
// 先获取mtx_C
std::lock_guard<std::mutex> lock1(mtx_C);
// 保护资源
printf("C: operate something\n");
// 再获取mtx_D
std::lock_guard<std::mutex> lock2(mtx_D);
}
void D(void)
{
// 先获取mtx_D
std::lock_guard<std::mutex> lock1(mtx_D);
// 保护资源
printf("D: operate something\n");
// 再获取mtx_C
std::lock_guard<std::mutex> lock2(mtx_C);
}
int main()
{
// test死锁1——demo1
// A();
// test死锁2——demo2
A1();
// test死锁3——demo3
// std::thread thread[] = {
// std::thread(C),
// std::thread(D)
// };
// for(auto& t : thread){
// t.join();
// }
// 死锁4...
printf("the main normally exit!\n");
return 0;
}
那么如何解决死锁呢?
1. 避免嵌套锁(或锁的顺序)
最常见的避免死锁的方法之一是确保线程按固定的顺序获取锁。比如,规定所有线程首先获得 MutexA
锁,然后再获得 MutexB
锁,而不允许线程先获得 MutexB
锁再去获得 MutexA
锁。
(可自行尝试解决如上代码中demo3)
2. 使用超时机制
设置锁的超时时间。如果线程在获取锁时超时了,就主动释放已持有的锁,并重新尝试或返回失败,这样就可以避免死锁的发生。
3. 死锁检测与恢复
某些系统会周期性检查是否存在死锁。如果发现死锁状态,可以通过终止一个或多个线程、回滚某些操作或强制释放锁来恢复。
4. 使用递归锁
在一些情况下,如果锁定的资源是递归锁,同一线程可以多次获取锁,而不会导致死锁。这对于某些设计中需要递归调用的情况有效。
end!
各位大佬有什么补充,或者需要更正的欢迎指出哈~