1. 程式人生 > >c++11多執行緒中的互斥量

c++11多執行緒中的互斥量

寫在前面

在多執行緒程式中互斥量的概念十分重要,要保護執行緒之間的共享資料,互斥量的lock、unlock、try_lock函式,以及類模板lock_guard、unique_lock十分重要

栗子

首先先看一下,沒有再共享資料上做任何保護的程式:

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
			msglist.push_back(i);
		}
	}

	void getcommand(int &command)
	{
		if (!msglist.empty())
		{
			command = msglist.front();
			msglist.pop_front();
			std::cout << "Outmsglist執行緒正在執行,取出數字為:" << command << std::endl;
		}
		else
		{
			std::cout << "Outmsglist執行緒正在執行,但是訊息佇列為空" << std::endl;
		}
	}
	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 10000; i++)
		{
			getcommand(command);
		}
	}
private:
	std::list<int> msglist;
};

int main()
{
	std::cout << "主執行緒的執行緒id為: " << std::this_thread::get_id() << std::endl;

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);

	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主執行緒執行結束" << std::endl;
	return 0;
}

因為程式中沒有對共享資料msglist進行保護,所以,不止到在什麼時候就會產生異常,程式十分不穩定,我們很具以上程式增加對於共享資料的保護,首先說一下,mutex類

A mutex is a lockable object that is designed to signal when critical sections of code need exclusive access, preventing other threads with the same protection from executing concurrently and access the same memory locations.

//互斥鎖是一個可鎖定的物件,當代碼的關鍵部分需要獨佔訪問時,互斥鎖會發出訊號,防止具有相同保護的其他執行緒併發執行並訪問相同的記憶體位置。

mutex成員函式

(constructor)

mutex類的建構函式
 lock 鎖定互斥鎖
try_lock 嘗試鎖定互斥鎖,馬上返回,如果鎖定成功,返回true,否則返回false
unlock 解鎖互斥鎖
native_handle

返回底層實現定義的原生控制代碼 

 修改後的程式為:

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();
			std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
			msglist.push_back(i);
			my_mutex.unlock();
		}
	}

	void getcommand(int &command)
	{
		if (!msglist.empty())
		{
			command = msglist.front();
			msglist.pop_front();
			std::cout << "Outmsglist執行緒正在執行,取出數字為:" << command << std::endl;
		}
		else
		{
			std::cout << "Outmsglist執行緒正在執行,但是訊息佇列為空" << std::endl;
		}
	}
	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();
			getcommand(command);
			my_mutex.unlock();
		}
	}
private:
	std::list<int> msglist;
	std::mutex my_mutex;
};

int main()
{
	std::cout << "主執行緒的執行緒id為: " << std::this_thread::get_id() << std::endl;

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);

	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主執行緒執行結束" << std::endl;
	return 0;
}

其中lock和unlock函式必須是成對出現的,如果忘記unlock函式沒有寫,將會操作程式發生死鎖,無法繼續執行,在這裡c++11又提出一個新的封裝器,運用RAll機制的lock_guard封裝器

在構造lock_guard物件的時候,即給互斥鎖上鎖(這裡可以提供std::adopt_lock引數來避免建構函式呼叫lock引數),在解構函式中呼叫unlock函式,即建立 lock_guard物件時,它試圖接收給定互斥的所有權。控制離開建立 lock_guard物件的作用域時,銷燬 lock_guard 並釋放互斥。

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::lock_guard<std::mutex> gulock(my_mutex);
			//my_mutex.lock();
			std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
			msglist.push_back(i);
			//my_mutex.unlock();
		}
	}

	void getcommand(int &command)
	{
		if (!msglist.empty())
		{
			command = msglist.front();
			msglist.pop_front();
			std::cout << "Outmsglist執行緒正在執行,取出數字為:" << command << std::endl;
		}
		else
		{
			std::cout << "Outmsglist執行緒正在執行,但是訊息佇列為空" << std::endl;
		}
	}
	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();   //這裡已經給my_mutex上鎖,所以在lock_guard物件構造的時候,要避免再次呼叫lock函式,需要提供std::adopt_lock引數
			std::lock_guard<std::mutex> gulock(my_mutex, std::adopt_lock);
			
			getcommand(command);
			//my_mutex.unlock();
		}
	}
private:
	std::list<int> msglist;
	std::mutex my_mutex;
};

int main()
{
	std::cout << "主執行緒的執行緒id為: " << std::this_thread::get_id() << std::endl;

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);

	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主執行緒執行結束" << std::endl;
	return 0;
}	

既然已經提到了lock_guard封裝器,就不得不說一下,unique_lock封裝器,相對於lock_guard封裝器來說,unique_lock功能更強大,並且使用更靈活

除了在lock_guard中提到的std::adopt_lock之外,在unique_lock中還有std::defer_lock,std::try_to_lock

表示構造unique_lock物件並且呼叫lock函式
std::adopt_lock 構造unique_lock物件,預設互斥鎖已經使用了lock
std::defer_lock 構造unique_lock物件,不呼叫lock函式,並且此時互斥鎖並未呼叫lock函式
std::try_to_lock 構造unique_lock物件,並且嘗試對互斥鎖呼叫lock函式,如果沒有鎖定成功,也會立即返回,不會發生阻塞,並且此時互斥鎖並未呼叫lock函式

使用std::try_to_lock,我們可以改寫Inmsglist成員函式:

void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> ullock(my_mutex, std::try_to_lock);
			//if (bool(ullock))    //返回嘗試鎖定是否成功
			if(ullock.owns_lock())
			{
				std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
				msglist.push_back(i);
			}
			else
			{
				std::cout << "Inmsglist執行緒正在執行,但是嘗試得到互斥鎖,沒有成功" << i << std::endl;
			}

		}
	}

除了owns_lock可以測試鎖是否佔有其關聯互斥 ,在unique_lock中還過載了bool轉換函式,但是前面有explicit修飾,所以只能進行顯示轉換操作,所以上面也可以使用bool(ullock)測試鎖是否佔有關聯互斥。

使用std::adopt_lock,我們可以改寫Inmsglist成員函式:

void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			my_mutex.lock();     //使用std::adopy_lock必須保障主動上鎖
			std::unique_lock<std::mutex> ullock(my_mutex, std::adopt_lock);
			std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
			msglist.push_back(i);
		}
	}

在資料庫中,事務之間放置發生死鎖的方法之一是:在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖概率。

我們也可以採用這樣的策略進行程式設計,但是這樣就要求我們必須要求有多個互斥量的存在

//假設函式中存在兩個互斥量,為別為my_mutex1,my_mutex2
	void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> ullock1(my_mutex1, std::adopt_lock);
			std::unique_lock<std::mutex> ullock1(my_mutex2, std::adopt_lock);

			std::lock(my_mutex1, my_mutex2);   //只有所有互斥量都上鎖,才可以向後執行,否則只要存在一個未上鎖,就會導致阻塞
			std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
			msglist.push_back(i);
		}
	}

unique_lock的重要成員函式

lock() 鎖定關聯互斥 
unlock() 解鎖關聯互斥 
try_lock() 嘗試鎖定關聯互斥,若互斥不可用則返回 ,可以由owns_lock以及bool()顯示轉換方式檢視是否鎖定關聯互斥
try_lock_for() 試圖鎖定關聯的定時可鎖 (TimedLockable) 互斥,若互斥在給定時長中不可用則返回 
try_lock_until() 嘗試鎖定關聯可定時鎖 (TimedLockable) 互斥,若抵達指定時間點互斥仍不可用則返回 
swap() 與另一 std::unique_lock 交換狀態 
release() 將關聯互斥解關聯而不解鎖它 

 栗子

void Inmsglist()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> ullock(my_mutex, std::defer_lock);   //建構函式中沒有進行加鎖
			//ullock.lock();   //自己增加
			//ullock.unlock();  //自己解鎖
			/*ullock.lock();
			std::mutex *mut = ullock.release();
			std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
			msglist.push_back(i);
			mut->unlock();*/
			
			if (ullock.try_lock())   //具有返回值的try_lock函式
			{
				std::cout << "Inmsglist執行緒正在執行,插入數字為:" << i << std::endl;
				msglist.push_back(i);
			}
			else
			{
				std::cout << "Inmsglist執行緒正在執行,但是沒有拿到鎖:" << i << std::endl;
			}
			
		}
	}

release函式原型為:

_Mutex *release() _NOEXCEPT        //返回管理的mutex物件指標,並釋放所有權,使用之後mutex和unique_lock不再有關係
		{	// disconnect
		_Mutex *_Res = _Pmtx;
		_Pmtx = 0;
		_Owns = false;
		return (_Res);
		}

 

參考文獻

http://www.cplusplus.com/reference

《C++11併發與多執行緒視訊課程》 視訊地址:http://edu.51cto.com/course/15287.html