【學(xué)習(xí)筆記】C++并發(fā)與多線程筆記五:unique_lock詳解

一瞪慧、前言

本文接上文 【學(xué)習(xí)筆記】C++并發(fā)與多線程筆記四:互斥量(概念搔谴、用法毅戈、死鎖) 的內(nèi)容觉渴,主要紀(jì)錄 unique_lock 的使用方法以及原理夯秃。

二座咆、uniqie_lock取代lock_quard

uniqie_lock 是個(gè)類模板,它的功能跟 lock_quard 類似仓洼,但比 lock_quard 更靈活介陶。在工作中,一般用 lock_quard (推薦使用)就足夠了色建,但在一些特殊的場景下會(huì)用到 uniqie_lock哺呜。

在上篇文章中講到了 lock_quard 取代了 mutex 的 lock() 和 unlock(),在 lock_quard 的構(gòu)造函數(shù)中上鎖箕戳,在析構(gòu)函數(shù)中解鎖某残,這點(diǎn)其實(shí)在 uniqie_lock 中也是一樣的。

uniqie_lock 在使用上比 lock_quard 靈活陵吸,但代價(jià)就是效率會(huì)低一點(diǎn)玻墅,并且內(nèi)存占用量也會(huì)相對高一些。

uniqie_lock 的缺省用法實(shí)際上與 lock_quard 一樣壮虫,可以直接替換澳厢,代碼如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;

class A {
 public:
  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      std::unique_lock<std::mutex> m_guard1(m_mutex1);
      msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
    }
  }
  /* 消息隊(duì)列不為空時(shí),返回并彈出第一個(gè)元素 */
  bool outMsgLULProc(int& command) {
    std::unique_lock<std::mutex> m_guard1(m_mutex1);
    if (!msgRecvQueue.empty()) {
      command = msgRecvQueue.front(); /* 返回第一個(gè)元素 */
      msgRecvQueue.pop_front();       /* 移除第一個(gè)元素 */
      return true;
    }
    return false;
  }
  /* 把數(shù)據(jù)從消息隊(duì)列中取出 */
  void outMsgRecvQueue() {
    int command = 0;
    for (int i = 0; i < 100000; ++i) {
      bool result = outMsgLULProc(command);
      if (result)
        cout << "outMsgLULProc exec, and pop_front: " << command << endl;
      else
        cout << "outMsgRecvQueue exec, but queue is empty!" << i << endl;
      cout << "outMsgRecvQueue exec end!" << i << endl;
    }
  }

 private:
  list<int> msgRecvQueue; /* 容器(實(shí)際上是雙向鏈表):存放玩家發(fā)生命令的隊(duì)列 */
  mutex m_mutex1;         /* 創(chuàng)建互斥量1 */
};

int main() {
  A obj;
  thread myInMsgObj(&A::inMsgRecvQueue, &obj);
  thread myOutMsgObj(&A::outMsgRecvQueue, &obj);
  myInMsgObj.join();
  myOutMsgObj.join();

  cout << "Hello World!" << endl;
  return 0;
}

三、uniqie_lock的第二個(gè)參數(shù)

uniqie_lock 的第二個(gè)參數(shù)是一個(gè)標(biāo)志位剩拢,其可取參數(shù)詳見下文线得。

3.1 std::adopt_lock

該標(biāo)記表示這個(gè)互斥鎖已經(jīng)被 lock() 了,uniqie_lock 不會(huì)再重復(fù)上鎖裸扶。

也就是說該標(biāo)記的效果是:假設(shè)調(diào)用方線程已經(jīng)擁有了互斥鎖的所有權(quán)框都,通知 uniqie_lock 不需要再構(gòu)造函數(shù)中 lock 這個(gè)互斥鎖了。

  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      m_mutex1.lock(); /* 先lock()呵晨,才能在 unique_lock 中用 adopt_lock 標(biāo)準(zhǔn) */
      std::unique_lock<std::mutex> m_guard1(m_mutex1, std::adopt_lock);
      msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
    }
  }

備注:lock_quard 中該標(biāo)記的含義相同魏保。

3.2 std::try_to_lock

假設(shè)我們在 myOutMsgObj 線程的回調(diào)函數(shù)中拿到互斥鎖后,sleep 20 秒摸屠,就存在一個(gè)問題谓罗,myOutMsgObj 線程占用了互斥鎖資源,卻不向下執(zhí)行季二,導(dǎo)致另一條線程 myInMsgObj 一直都沒辦法拿到互斥鎖檩咱,也要等 20 秒,造成計(jì)算資源浪費(fèi)胯舷。

  /* 消息隊(duì)列不為空時(shí)刻蚯,返回并彈出第一個(gè)元素 */
  bool outMsgLULProc(int& command) {
    std::unique_lock<std::mutex> m_guard1(m_mutex1);

    std::chrono::milliseconds dura(20000); /* 20秒 */
    std::this_thread::sleep_for(dura);     /* sleep 20秒 */

    if (!msgRecvQueue.empty()) {
      command = msgRecvQueue.front(); /* 返回第一個(gè)元素 */
      msgRecvQueue.pop_front();       /* 移除第一個(gè)元素 */
      return true;
    }
    return false;
  }

為解決這個(gè)問題,uniqie_lock 引入了 try_to_lock 參數(shù)桑嘶,它表示代碼會(huì)嘗試上鎖炊汹,即使沒有成功,也會(huì)立即返回逃顶,不會(huì)阻塞的等待讨便。

  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      /* 嘗試上鎖 */
      std::unique_lock<std::mutex> m_guard1(m_mutex1, std::try_to_lock);
      if (m_guard1.owns_lock()) {  /* 如果拿到了鎖 */
        msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
      } else {
        cout << "try_to_lock fail, do something else!!!" << endl;
      }
    }
  }

備注:使用 try_to_lock 參數(shù)前,線程中不能調(diào)用 lock()以政,否則會(huì)造成死鎖霸褒。

3.3 std::defer_lock

該標(biāo)記表示并沒有給 mutex 加鎖,即初始化了一個(gè)沒有加鎖的 mutex盈蛮。使用該標(biāo)記初始化的 uniqie_lock 對象可以靈活的調(diào)用 uniqie_lock 的成員函數(shù)废菱,這點(diǎn)將在下文中一起演示。

四抖誉、uniqie_lock的成員函數(shù)

4.1 lock()

使用 std::defer_lock 參數(shù)初始化 uniqie_lock 對象可以調(diào)用unique_lock的成員函數(shù)上鎖殊轴,并且無需在代碼中解鎖,它會(huì)自動(dòng)解鎖寸五,有點(diǎn)類似智能指針梳凛。

  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      /* 初始化了沒有加鎖的m_mutex1 */
      std::unique_lock<std::mutex> m_guard1(m_mutex1, std::defer_lock);
      m_guard1.lock(); /* 調(diào)用unique_lock的成員函數(shù)上鎖耿币,并且無需在代碼中解鎖梳杏,它會(huì)自動(dòng)解鎖 */
      msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
    }
  }

4.2 unlock()

根據(jù) 4.1 的代碼,我們可以知道 unique_lock 的成員函數(shù) lock() 上鎖后,在對象析構(gòu)的時(shí)候會(huì)自動(dòng)解鎖十性,那為什么 unique_lock 還需要提供 unlock() 函數(shù)呢叛溢?

這里就體現(xiàn)了 unique_lock 的靈活性,它既具備智能指針自動(dòng)銷毀的特性劲适,又可以在代碼中手動(dòng)解鎖楷掉,方便程序在一段代碼中對其中某幾個(gè)部分上鎖的需求,將一些非共享代碼從鎖中提取出來霞势,從而提高效率烹植。

備注:通常,在代碼中愕贡,上鎖的代碼越少(粒度越胁莸瘛),效率越高固以。

  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      /* 初始化了沒有加鎖的m_mutex1 */
      std::unique_lock<std::mutex> m_guard1(m_mutex1, std::defer_lock);
      m_guard1.lock(); /* 調(diào)用unique_lock的成員函數(shù)上鎖墩虹,并且無需在代碼中解鎖,它會(huì)自動(dòng)解鎖 */
      msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
      m_guard1.unlock();
      /* 非共享代碼...... */
      m_guard1.lock();
      /* 共享代碼...... */
      m_guard1.unlock();  /* 這個(gè)unlock() 可調(diào)可不調(diào)憨琳,m_guard1 對象析構(gòu)時(shí)會(huì)自動(dòng)解鎖 */
    }
  }

4.3 try_lock()

這個(gè)成員函數(shù)與 std::try_to_lock 參數(shù)類似诫钓,嘗試上鎖,如果拿到鎖了篙螟,則返回 true菌湃,否則返回 false。

  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      /* 初始化了沒有加鎖的m_mutex1 */
      std::unique_lock<std::mutex> m_guard1(m_mutex1, std::defer_lock);
      if (m_guard1.try_lock()) {   /* 如果拿到鎖了 */
        msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
      } else {
        cout << "try_to_lock fail, do something else!!!" << endl;
      }
    }
  }

4.4 release()

該成員函數(shù)的作用是返回它所管理的 mutex 對象指針闲擦,并釋放所有權(quán)慢味;也就是說,這個(gè) unique_lock 和 mutex 不再有關(guān)系墅冷。

調(diào)用以下代碼創(chuàng)建 unique_lock 類型的對象 m_guard1 時(shí)纯路,實(shí)際上是把 m_guard1 與 m_mutex1 對象綁定在一起了,release() 函數(shù)可以將這兩個(gè)對象解綁寞忿,并返回綁定的 mutex 指針驰唬,即此處的 m_mutex1 。注意這里是解綁腔彰,并不是銷毀叫编。

std::unique_lock<std::mutex> m_guard1(m_mutex1);

調(diào)用 release() 函數(shù)解綁后,我們必須保存返回的 mutex 指針霹抛,并在接下來的代碼中自行管理搓逾。

  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      /* 初始化了沒有加鎖的m_mutex1 */
      std::unique_lock<std::mutex> m_guard1(m_mutex1);
      std::mutex* pmutex = m_guard1.release(); /* 解綁后返回之前綁定的m_mutex1,接下來我們要自行管理m_mutex1 */
      msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
      pmutex->unlock();          /* 解綁后需要自己解鎖 */
    }
  }

五杯拐、uniqie_lock所有權(quán)的傳遞

當(dāng) uniqie_lock 與 mutex 對象綁定在一起才是一個(gè)完整的霞篡、能發(fā)揮作用的 uniqie_lock 實(shí)例世蔗,也就是說 uniqie_lock 需要和 mutex 配合使用,可以理解為 uniqie_lock 需要管理一個(gè) mutex朗兵。

一個(gè) mutex 對象只能被一個(gè) uniqie_lock 對象所有(擁有)污淋,即同一個(gè) mutex 無法被兩個(gè) uniqie_lock 對象同時(shí)使用,這就是 uniqie_lock 的所有權(quán)概念余掖。

/* 上鎖兩次寸爆,造成死鎖 */
std::unique_lock<std::mutex> m_guard1(m_mutex1);
std::unique_lock<std::mutex> m_guard2(m_mutex1); /* 復(fù)制所有權(quán)(程序崩潰) */

m_guard1 擁有 m_mutex1 的所有權(quán),并且 m_guard1 可以把自己 mutex(m_mutex1) 的所有權(quán)轉(zhuǎn)移給其他的 uniqie_lock 對象盐欺。

備注:所有權(quán)可以轉(zhuǎn)移赁豆,但不能復(fù)制,這與智能指針 unique_ptr 類似冗美,智能指針指向的對象同樣也是可以移動(dòng)歌憨,但不能復(fù)制。

將 m_guard1 的所有權(quán)轉(zhuǎn)移給 m_guard2墩衙,代碼如下:

std::unique_lock<std::mutex> m_guard1(m_mutex1);
/* 移動(dòng)語義务嫡,相當(dāng)于m_guard1與m_mutex1解綁;m_guard2與m_mutex1綁定 */
std::unique_lock<std::mutex> m_guard2(std::move(m_guard1)); 

轉(zhuǎn)移unique_lock所有權(quán)的擴(kuò)展方法:

  std::unique_lock<std::mutex> rtn_unique_lock() {
    std::unique_lock<std::mutex> temp_guard(m_mutex1);
    /* 從函數(shù)返回一個(gè)局部的temp_guard(移動(dòng)構(gòu)造函數(shù))*/
    /* 返回局部對象temp_guard會(huì)讓系統(tǒng)創(chuàng)建臨時(shí)的unique_lock對象漆改,并調(diào)用unique_lock的移動(dòng)構(gòu)造函數(shù) */
    return temp_guard;
  }
  /* 把收到的消息(玩家命令)存到隊(duì)列中 */
  void inMsgRecvQueue() {
    for (int i = 0; i < 100000; ++i) {
      cout << "inMsgRecvQueue exec, push an elem " << i << endl;
      /* rtn_unique_lock 函數(shù)中 temp_guard 對象的所有權(quán)轉(zhuǎn)移到 m_guard1 中了 */
      std::unique_lock<std::mutex> m_guard1 = rtn_unique_lock();
      msgRecvQueue.push_back(i); /* 假設(shè)數(shù)字 i 就是收到的玩家命令 */
    }
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末心铃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挫剑,更是在濱河造成了極大的恐慌去扣,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件樊破,死亡現(xiàn)場離奇詭異愉棱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)哲戚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門奔滑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人顺少,你說我怎么就攤上這事朋其。” “怎么了脆炎?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵梅猿,是天一觀的道長祟牲。 經(jīng)常有香客問我拱礁,道長衣屏,這世上最難降的妖魔是什么皱炉? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮千康,結(jié)果婚禮上峦嗤,老公的妹妹穿的比我還像新娘嬉荆。我一直安慰自己,他們只是感情好响蓉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哨毁,像睡著了一般枫甲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扼褪,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天想幻,我揣著相機(jī)與錄音,去河邊找鬼话浇。 笑死脏毯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的幔崖。 我是一名探鬼主播食店,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赏寇!你這毒婦竟也來了吉嫩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嗅定,失蹤者是張志新(化名)和其女友劉穎自娩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渠退,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忙迁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碎乃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姊扔。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖梅誓,靈堂內(nèi)的尸體忽然破棺而出旱眯,到底是詐尸還是另有隱情,我是刑警寧澤证九,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布删豺,位于F島的核電站,受9級特大地震影響愧怜,放射性物質(zhì)發(fā)生泄漏呀页。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一拥坛、第九天 我趴在偏房一處隱蔽的房頂上張望蓬蝶。 院中可真熱鬧尘分,春花似錦、人聲如沸丸氛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缓窜。三九已至定续,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禾锤,已是汗流浹背私股。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恩掷,地道東北人倡鲸。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像黄娘,于是被迫代替她去往敵國和親峭状。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容