多執行緒05:unique_lock詳解

booker發表於2022-05-12

?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轉移語義呼叫移動建構函式!

相關文章