c++多线程之死锁

什么是死锁?
死锁 (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个必要条件?
产生死锁的必要条件:

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法
既然已经知道了形成死锁的条件,那么我们就从这几个条件入手就行,比如:
a.一次性分配完所有资源,这样就不会再有请求了:(破坏请求条件)
b.当进程阻塞时,释放所持有的资源(破坏请保持条件)
c.资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

1、以确定的顺序获得锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:
在这里插入图片描述
如果此时把获得锁的时序改成:
在这里插入图片描述
那么死锁就永远不会发生。

2、超时放弃

该方法可以按照固定时长等待锁,线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
在这里插入图片描述
解除死锁的方法
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  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.csdn.net/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.csdn.net/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

如果觉得本文对你有用,可以使用微信扫一扫支持一下。
在这里插入图片描述

  • 3
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发如雪-ty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值