什么是死锁?
死锁 (deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
我们举个例子来描述,如果此时有一个线程A,按照先锁a,再锁b,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:
就如图这种情况下,线程A在等待锁b,可是锁b被锁住了,所以此时不能往下进行,需要等待锁b释放,而线程B先是锁住了锁b,在等待锁a的释放,这样就造成了线程A等线程B,线程B等待线程A,从而出现了死锁。
产生死锁的原因?
1)系统资源不足;
2)进程(线程)推进的顺序不恰当;
3)资源分配不当。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁;其次,进程运行推进顺序与速度不同,也可能产生死锁。
死锁的形成场景:
1)忘记释放锁:在申请锁和释放锁之间直接return
2)单线程重复申请锁:一个线程,刚出临界区,又去申请资源。
3)多线程多锁申请:两个线程,两个锁,他们都已经申请了一个锁了,都想申请对方的锁
4)环形锁的申请:多个线程申请锁的顺序形成相互依赖的环形
死锁产生的4个必要条件?
产生死锁的必要条件:
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
解决死锁的基本方法
既然已经知道了形成死锁的条件,那么我们就从这几个条件入手就行,比如:
a.一次性分配完所有资源,这样就不会再有请求了:(破坏请求条件)
b.当进程阻塞时,释放所持有的资源(破坏请保持条件)
c.资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
1、以确定的顺序获得锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:
如果此时把获得锁的时序改成:
那么死锁就永远不会发生。
2、超时放弃
该方法可以按照固定时长等待锁,线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
解除死锁的方法
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
- 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
- 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
接下来看看代码对于死锁的演示:
class JackTang
{
public:
int number;
JackTang(int a) :number(a)
{
}
JackTang(const JackTang &obj)
{
number = obj.number;
}
~JackTang()
{
}
void consumer()
{
for (int i = 0; i < 10000;i++)
{
m_utex2.lock();
//处理某些任务...
m_utex1.lock();
if (!m_que.empty())
{
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor()
{
for (int i = 1; i < 10000;i++)
{
m_utex1.lock();
//处理某些任务...
m_utex2.lock();
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
std::mutex m_utex1;
std::mutex m_utex2;
std::queue<int> m_que;
};
void main()
{
JackTang j(1);
std::thread t1(&JackTang::consumer, &j);
std::thread t2(&JackTang::productor, &j);
t1.detach();
t2.detach();
system("pause");
}
结果:
上面两个锁,由于在两个线程函数中加锁的顺序不同从而造成死锁,下面将两个锁的加锁顺序调一下,就可以正常运行。需要改的代码贴一下:
void consumer()
{
for (int i = 0; i < 10000;i++)
{
m_utex1.lock();
//处理某些任务...
m_utex2.lock();
if (!m_que.empty())
{
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
结果(部分截图):
也可以使用std::lock(),将两个锁一起锁住,但工作中很少这么使用,至少我没有这么干过!
void consumer()
{
for (int i = 0; i < 10000;i++)
{
std::lock(m_utex1, m_utex2);
if (!m_que.empty())
{
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
m_utex2.unlock();
m_utex1.unlock();
}
}
void productor()
{
for (int i = 1; i < 10000;i++)
{
std::lock(m_utex1, m_utex2);
m_que.push(i);
cout << "插入元素:" << i << endl;
m_utex2.unlock();
m_utex1.unlock();
}
}
如果害怕忘记释放锁,还可以使用lock_gard,如下:
void consumer()
{
for (int i = 0; i < 10000;i++)
{
std::lock(m_utex2, m_utex1);
lock_guard<std::mutex>lc(m_utex1, std::adopt_lock);
lock_guard<std::mutex>lc1(m_utex2, std::adopt_lock);
if (!m_que.empty())
{
cout << "取出元素:" << m_que.front() << endl;
m_que.pop();
}
}
}
void productor()
{
for (int i = 1; i < 10000;i++)
{
std::lock(m_utex1, m_utex2);
lock_guard<std::mutex>lc(m_utex1, std::adopt_lock);
lock_guard<std::mutex>lc1(m_utex2, std::adopt_lock);
m_que.push(i);
cout << "插入元素:" << i << endl;
}
}
参考1:https://blog.youkuaiyun.com/t_x_l_/article/details/73159636?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-11.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-11.control
参考2;https://blog.youkuaiyun.com/hd12370/article/details/82814348?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control
如果觉得本文对你有用,可以使用微信扫一扫支持一下。