6-5 互斥量概念、用法、死锁演示及解决详解

一:互斥量(mutex)的基本概念

保护共享数据,操作时,某个线程用代码把共享数据锁住、操作数据、解锁,其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁。
互斥量是个类对象。理解成一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回),如果没锁成功,那么流程卡在lock()这里不断的尝试去锁这把锁头;

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue()
	{
		for(int i = 0;i<100000;++i)
		{
			cout<<"inMsgRecvQueue()执行,插入一个元素"<<i<<endl;
			//my_mutex2.lock();		//死锁,这边先2后1,下边先1后2
			//my_mutex1.lock();	//实际工程中这两个锁头不一定挨着,们需要保护不同的数据共享块;
			std::lock(my_mutex1,my_mutex2);	//相当于每个互斥量都调用了.lock();
			std::lock_guard<std::mutex> sbguard(my_mutex1,std::adopt_lock);	//这样能省略自己unlock的步骤
			std::lock_guard<std::mutex> sbguard(my_mutex2,std::adopt_lock);
			msgRecvQueue.push_back(i);//假设这个数字i就是我收到的命令,我直接弄到消息队列里边来;
			//my_mutex.unlock();
		}
		return;
	}
	bool out MsgLULProc(int &command)
	{
		std::lock_guard<std::mutex> sbguard(my_mutex);//sbguard是随便起的对象名
		//lock_guard构造函数里执行了mutex::lock(),析构函数里执行了mutex::unlock();
		//my_mutex1.lock();		//死锁
		//my_mutex2.lock();
		//std::lock(my_mutex1,my_mutex2);	//相当于每个互斥量都调用了.lock();
		if(!msgRecvQueue.empty())
		{
			//消息不为空
			int command = msgRecvQueue.front();	//返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();	//移除第一个元素
			//my_mutex.unlock();	//一定不能忘记return前的解锁
			return true;
		}
		//my_mutex.unlock();
		return false;
	}
	//把数据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for(int i = 0;i<10000;i++)
		{
			bool result = outMsgLULProc(command);
			if(result == true)
			{
				cout<<"out执行了,取出一个元素"<<command<<endl;
			}
			else
			{
				cout<<"消息队列为空"<<i<<endl;
			}
			/*if(!msgRecvQueue.empty())
			{
				//消息不为空
				int command = msgRecvQueue.front();//返回第一个元素,但不检查元素是否存在;
				msgRecvQueue.pop_front();	//移除第一个元素,但不返回;
				//这里就考虑处理数据....
				//....
			}
			else
			{
				//消息队列为空
				cout<<"消息队列为空"<<i<<endl;
			}*/
		}
		cout<<"end"<<endl;
	}
private:
std::list<int> msgRecvQueue;//容器,专门用于代表玩家给咱们发送过来的命令。
std::mutex my_mutex;	//创建了一个互斥量
};

int main()
{
	A myobja;
	std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);//第二个参数是引用才能保证用的是同一个对象
	std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);//
	myOutnMsgObj.join();
	myInMsgObj.join();
}

二:互斥量的用法

2.1、lock(),unlock()
步骤:先lock(),操作共享数据,unlock();
lock()和unlock()要成对使用,有lock()必须要有unlock,每调用一次lock(),必然应该调用一次unlock();
不应该也不允许调用1次lock()却调用了2次unlock(),也不允许调用2次lock却调用1次unlock(),这些非对称数量的调用
有lock忘记unlock的问题非常难排查;
为了防止大家忘记unlock(),引入了一个叫std::lock_guard的类模板:你忘记unlock不要紧,我替你unlock();
学习过智能指针(unique_ptr<>):你忘记释放内存不要紧,我给你释放

2.2、std::lock_guard类模板:直接取代lock()和unlock();

三:死锁

张三:站在北京等李四,不挪窝
李四:站在深圳等张三,不挪窝
C++中:
比如我有两把锁(死锁这个问题 是由至少两个锁头也就是两个互斥量才能产生);金锁,银锁;
两个线程A,B
1、线程A执行的时候,这个线程先锁金锁,把金锁lock()成功了,然后它去lock银锁。。
出现了上下文切换
2、线程B执行了,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会lock()成功,线程B要去lock金锁。。。
此时此刻,死锁就产生了;
3、线程A因为拿不到银锁头,流程走不下去(所有后边代码有解锁金锁头的但是流程走不下去,所以金锁头解不开)
4、线程B因为拿不到金锁头,流程走不下去(所有后边代码有解锁银锁头的但是流程走不下去,所以银锁头解不开)
大家都晾在这里你等我,我等你

3.1、死锁演示
3.2、死锁的一般解决方案
只要保证这两个互斥量上锁的顺序一致就不会死锁。
3.3、std::lock()函数模板
能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不行,1个不行);
它不存在这种因为在多个线程中因为锁的顺序问题导致死锁的风险问题;
std::lock():如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回);
要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没锁成功,则它立即把已经锁住的解锁。

3.4、std::lock_guqrd的std::adopt_lock参数
std::adopt_lock是个结构体对象,起一个标记作用:作用就是表示这个互斥量已经lock(),不需要在std::lock_guardstd::mutex里面对对象进行再次lock()了

总结:

std::lock():一次锁定多个互斥量谨慎使用,建议一个一个锁。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章