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; } }