多執行緒05:unique_lock詳解

📕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轉移語義調用移動構造函數!