C++多線程unique_lock詳解

  • 2020 年 2 月 14 日
  • 筆記

       C++11中的unique_lock使用起來要比lock_guard更靈活,但是效率會第一點,內存的佔用也會大一點。同樣,unique_lock也是一個類模板,但是比起lock_guard,它有自己的成員函數來更加靈活進行鎖的操作。

       使用方式和lock_guard一樣,不同的是unique_lock有不一樣的參數和成員函數。它的定義是這樣的:

std::unique_lock<std::mutex> munique(mlock);

       這樣定義的話和lock_guard沒有什麼區別,最終也是通過析構函數來unlock。

std::unique_lock

       unique_lock也可以加std::adopt_lock參數,表示互斥量已經被lock,不需要再重複lock。該互斥量之前必須已經lock,才可以使用該參數。

std::try_to_lock

       可以避免一些不必要的等待,會判斷當前mutex能否被lock,如果不能被lock,可以先去執行其他代碼。這個和adopt不同,不需要自己提前加鎖。舉個例子來說就是如果有一個線程被lock,而且執行時間很長,那麼另一個線程一般會被阻塞在那裡,反而會造成時間的浪費。那麼使用了try_to_lock後,如果被鎖住了,它不會在那裡阻塞等待,它可以先去執行其他沒有被鎖的代碼。具體實現過程如下:

#include <iostream>  #include <mutex>    std::mutex mlock;    void work1(int& s) {  	for (int i = 1; i <= 5000; i++) {  		std::unique_lock<std::mutex> munique(mlock, std::try_to_lock);  		if (munique.owns_lock() == true) {  			s += i;  		}  		else {  			// 執行一些沒有共享內存的代碼  		}  	}  }    void work2(int& s) {  	for (int i = 5001; i <= 10000; i++) {  		std::unique_lock<std::mutex> munique(mlock, std::try_to_lock);  		if (munique.owns_lock() == true) {  			s += i;  		}  		else {  			// 執行一些沒有共享內存的代碼  		}  	}  }    int main()  {  	int ans = 0;  	std::thread t1(work1, std::ref(ans));  	std::thread t2(work2, std::ref(ans));  	t1.join();  	t2.join();  	std::cout << ans << std::endl;  	return 0;  }

std::defer_lock

       這個參數表示暫時先不lock,之後手動去lock,但是使用之前也是不允許去lock。一般用來搭配unique_lock的成員函數去使用。下面就列舉defer_lock和一些unique_lock成員函數的使用方法。

       當使用了defer_lock參數時,在創建了unique_lock的對象時就不會自動加鎖,那麼就需要藉助lock這個成員函數來進行手動加鎖,當然也有unlock來手動解鎖。這個和mutex的lock和unlock使用方法一樣,實現代碼如下:

#include <iostream>  #include <mutex>    std::mutex mlock;    void work1(int& s) {  	for (int i = 1; i <= 5000; i++) {  		std::unique_lock<std::mutex> munique(mlock, std::defer_lock);  		munique.lock();  		s += i;  		munique.unlock();         // 這裡可以不用unlock,可以通過unique_lock的析構函數unlock  	}  }    void work2(int& s) {  	for (int i = 5001; i <= 10000; i++) {  		std::unique_lock<std::mutex> munique(mlock, std::defer_lock);  		munique.lock();  		s += i;  		munique.unlock();  	}  }    int main()  {  	int ans = 0;  	std::thread t1(work1, std::ref(ans));  	std::thread t2(work2, std::ref(ans));  	t1.join();  	t2.join();  	std::cout << ans << std::endl;  	return 0;  }

       還有一個成員函數是try_lock,和上面的try_to_lock參數的作用差不多,判斷當前是否能lock,如果不能,先去執行其他的代碼並返回false,如果可以,進行加鎖並返回true,代碼如下:

void work1(int& s) {  	for (int i = 1; i <= 5000; i++) {  		std::unique_lock<std::mutex> munique(mlock, std::defer_lock);  		if (munique.try_lock() == true) {  			s += i;  		}  		else {  			// 處理一些沒有共享內存的代碼  		}  	}  }

       release函數,解除unique_lock和mutex對象的聯繫,並將原mutex對象的指針返回出來。如果之前的mutex已經加鎖,需在後面自己手動unlock解鎖,代碼如下:

void work1(int& s) {  	for (int i = 1; i <= 5000; i++) {  		std::unique_lock<std::mutex> munique(mlock);   // 這裡是自動lock  		std::mutex *m = munique.release();  		s += i;  		m->unlock();  	}  }

unique_lock的所有權的傳遞

       對越unique_lock的對象來說,一個對象只能和一個mutex鎖唯一對應,不能存在一對多或者多對一的情況,不然會造成死鎖的出現。所以如果想要傳遞兩個unique_lock對象對mutex的權限,需要運用到移動語義或者移動構造函數兩種方法。

移動語義:

std::unique_lock<std::mutex> munique1(mlock);  std::unique_lock<std::mutex> munique2(std::move(munique1));  // 此時munique1失去mlock的權限,並指向空值,munique2獲取mlock的權限

移動構造函數:

std::unique_lock<std::mutex> rtn_unique_lock()  {  	std::unique_lock<std::mutex> tmp(mlock);  	return tmp;  }    void work1(int& s) {  	for (int i = 1; i <= 5000; i++) {  		std::unique_lock<std::mutex> munique2 = rtn_unique_lock();  		s += i;  	}  }