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():一次鎖定多個互斥量謹慎使用,建議一個一個鎖。

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