?unique_lock詳解
一、unique_lock取代lock_guard
- unique_lock是個類别範本,實際應用中,一般lock_guard(推薦使用);lock_guard取代了mutex和的lock()和nulock(), 而unique_lock也取代mutex和的lock()和nulock();
- unique_lock比lock_guard靈活很多(多出來很多用法),效率差一點,記憶體佔用多一點;
- 使用:
unique_lock<mutex> myUniLock(myMutex)
二、unique_lock的第二個引數
2.1 std:: adopt_lock
-
lock_guard中也可以用這個引數;
-
表示這個互斥量已經被lock(),即不需要在建構函式中lock這個互斥量了,前提:必須提前lock;
-
adopt_lock就是起一種標記作用,標誌效果:“假設呼叫方執行緒已經擁有了互斥的所有權(即已經lock()成功了的);
2.2 std::try_to_lock
- 意思:我們會嘗試用mutex的lock()去鎖定這個mutex,但是不同的是,如果沒有鎖定成功,也會立即返回,不會阻塞繼續嘗試鎖定;
- 搭配owns_lock()方法判斷是否拿到鎖,如拿到返回true,沒有拿到返回false;
- 前提:當下執行緒再使用該引數前,不要使用lock,不然沒有意義,因為肯定拿不到;
- 使用try_to_lock的原因:是防止其他的執行緒鎖定mutex太長時間,導致本執行緒一直阻塞在lock這個地方。
例子:
//寫入資料函式;
void inMsgPro() {
for (int i = 0; i < 100; ++i) {
cout << "inMsgPro()執行,插入元素" << i << endl;
unique_lock<mutex> myUniLock(mutex1, try_to_lock);
if (myUniLock.owns_lock() == true) {
msgRecvQueue.push(i);
}
else {
cout << "拿不到鎖" << endl;
//其他操作程式碼
}
}
}
2.3 std::defer_lock
- 意思:加上defer_lock是初始化了一個沒有加鎖的mutex
- 前提:當下執行緒再使用該引數前,不要使用lock,不然沒有意義,邏輯相悖;
- 使用std::defer_lock的原因:是以後可以呼叫unique_lock的一些方法
三、unique_lock的成員函式(前三個要與defer_lock聯合使用)
3.1 lock(): 加鎖
unique_lock<mutex> myUniLock(myMutex, defer_lock);
//加鎖!
myUniLock.lock();
//注意:可以不用自己解鎖,myUniLock物件析構的時候會進行unlock()操作;
3.2 unlock(): 解鎖
unique_lock<mutex> myUniLock(myMutex, defer_lock);
myUniLock.lock();//加鎖!
//處理共享資料的程式碼
myUniLock.unlock();//解鎖!
//處理非共享資料的程式碼(可能很多)
//.......
myUniLock.lock();//加鎖!
//處理共享資料的程式碼
myUniLock.unlock();//解鎖!
為什麼有時候需要unlock():因為lock鎖住的程式碼越多,鎖的粒度越粗,執行效率就低;反而如果我們只鎖住共享的程式碼,鎖住程式碼少,鎖的粒度細,執行效率高!
要選這合適的粒度:不能漏掉共享資料的保護,但是也不可以將其他不必要的程式碼加入!
3.3 try_lock(): 嘗試給互斥量加鎖
- 如果拿到鎖,則返回true,如果拿不到鎖,函式不阻塞,返回false,繼續往下執行;
這個操作和try_to_lock操作很像,個人感覺像是在defer_lock情況下加上這種不阻塞的功能;
3.4 release():
unique_lock<mutex>myUniLock(myMutex);
相當於把myMutex和myUniLock繫結在了一起,而release()就是解除繫結,返回它所管理的mutex物件的指標,並釋放所有權,不再指向mutex物件;mutex* ptx = myUniLock.release();
所有權由ptx接管,如果原來mutex物件進行了加鎖,處於加鎖狀態,就需要ptx在後面進行解鎖了;- 注意release()和unlock()的區別,一個是釋放了所有權,一個只是釋放鎖,該物件還是和mutex繫結著;
四、unique_lock所有權的傳遞
unique_lock<mutex> myUniLock(myMutex);
把myMutex和myUniLock繫結在了一起,也就是myUniLock擁有myMutex的所有權
所有權轉移方式:
①使用move轉移:
所有權可以轉移,但是不能複製!
unique_lock<mutex> myUniLock(myMutex);
//unique_lock<mutex> myUniLock2(myUniLock); //複製所有權是非法,一種拷貝構造;
unique_lock<mutex> myUniLock2(std::move(myUniLock));//移動語言,傳右值,呼叫帶右值引用的拷貝構造,將myUniLock2和myMutex繫結一起,而myUniLock指向空!
②在函式中return一個臨時變數,即可以實現轉移
unique_lock<mutex> rtn_unique_lock()
{
unique_lock<mutex> myUniLock(myMutex);//位置1
//移動建構函式那裡講從函式返回一個區域性的unique_lock物件是可以的
//返回這種區域性物件會導致系統生成臨時的unique_lock物件,並呼叫unique_lock的移動建構函式
return myUniLock;
}
// 然後就可以在外層呼叫,在myUniLock2具有對myMutex的所有權
std::unique_lock<std::mutex> myUniLock2 = rtn_unique_lock();//位置2
其實這種方法的本質是:用在函式中建立臨時變數(位置1),將區域性臨時變數拷貝一份給呼叫函式(位置2,這裡又有一份臨時變數),最後再由位置2的臨時變數 賦值給myUniLock2!
可以看出,是非常消耗記憶體,浪費資源時間的,因為位置1、2的臨時物件構造馬上又析構了,後面也不會用它們。所以強烈推薦使用move轉移語義呼叫移動建構函式!