最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++多線程中互斥量的使用詳解

 更新時(shí)間:2023年08月05日 08:34:57   作者:weihao-ysgs  
這篇文章主要介紹了C++多線程中互斥量的使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

多線程中互斥信號(hào)量(Mutex)的使用

1.0 互斥量的基本概念

1.1 Example

\(\quad\)首先我們要明白,為什么會(huì)有互斥信號(hào)量的出現(xiàn),在多線程編程中,不同的線程之間往往要對(duì)同一個(gè)數(shù)據(jù)進(jìn)行操作,如果該數(shù)據(jù)是只讀的,當(dāng)然不會(huì)出現(xiàn)什么問(wèn)題,但是如果兩個(gè)線程同時(shí)對(duì)某個(gè)數(shù)據(jù)進(jìn)行寫(xiě)操作,則可能出現(xiàn)難以預(yù)料的事情。

  • 我們來(lái)看一個(gè)簡(jiǎn)單的操作
#include <atomic>
#include <iostream>
#include <thread>
#include <chrono>
#include <pthread.h>
using namespace std;
int i = 0;
const int maxCnt = 1000000;
void mythread()
{
  for (int j = 0; j < maxCnt; j++)
  {
    i++;  // 線程同時(shí)操作變量
  }
}
int main()
{
  auto begin = chrono::high_resolution_clock::now();
  thread t1(mythread);
  thread t2(mythread);
  t1.join();
  t2.join();
  auto end = chrono::high_resolution_clock::now();
  cout << "i=" << i << endl;
  cout << "time: "
       << chrono::duration_cast<chrono::microseconds>(end - begin).count() *
              1e-6
       << "s" << endl;  // 秒計(jì)時(shí)
}

可以看到在我的電腦上程序的輸出為

i=1022418
time: 0.010445s

很明顯和我們預(yù)想的結(jié)果是不一致的,我們使用兩個(gè)線程同時(shí)對(duì)該變量進(jìn)行加法操作,根據(jù)運(yùn)行此書(shū)來(lái)計(jì)算,結(jié)果因該為 2000000,但事實(shí)上卻不是這樣的,這就是因?yàn)橛卸鄠€(gè)線程在對(duì)同一個(gè)變量進(jìn)行寫(xiě)操作的時(shí)候會(huì)出現(xiàn)難以排查的問(wèn)題,意想不到的結(jié)果。此時(shí)mutex就派上用場(chǎng)了,我們對(duì)程序進(jìn)行稍微的改動(dòng)。

std::mutex var_mutex;
int i = 0;
const int maxCnt = 1000000;
void mythread()
{
  for (int j = 0; j < maxCnt; j++)
  {
    var_mutex.lock();
    i++;  // 線程同時(shí)操作變量
    var_mutex.unlock();
  }
}

此時(shí)再運(yùn)行程序可以發(fā)現(xiàn)結(jié)果如下,這是符合我們的預(yù)期的。

i=2000000
time: 0.09337s

1.2 互斥量用法解釋

\(\quad\)互斥量就是個(gè)類對(duì)象,可以理解成一把鎖,多個(gè)線程嘗試用lock()成員函數(shù)來(lái)加鎖,從而獲得對(duì)數(shù)據(jù)的訪問(wèn)權(quán)限,或者說(shuō)讀寫(xiě)權(quán)限,其實(shí)就是繼續(xù)執(zhí)行代碼的權(quán)限。最終只有一個(gè)線程能鎖定成功,如果沒(méi)有鎖成功,那么流程將卡在lock()這里不斷嘗試去鎖定。所以我們?cè)谑褂??的時(shí)候要注意盡量在lock()和unlock()中間插入較小的代碼片段,這樣才能提高多線程時(shí)程序的執(zhí)行效率,比如前面的加加操作,只有一行,如果你在鎖住某個(gè)線程之后后面在unlock之前還讓線程睡眠了一會(huì),那你可真是個(gè)大聰明??。當(dāng)然也有其他的方法可以跳過(guò)等待,后面我們也會(huì)說(shuō)到。

\(\quad\)在使用互斥量的時(shí)候要包含頭文件 #include<mutex> 然后使用mutex 類即可創(chuàng)建對(duì)象。更重要的一點(diǎn)是,在代碼中 lock() (上鎖)和 unlock() (解鎖)必須成對(duì)使用,代碼中使用互斥量的時(shí)絕不允許非對(duì)稱調(diào)用,即 lock() 和 unlock() 一定是成對(duì)出現(xiàn)的。步驟如下:

  • 先 lock() 上鎖;
  • 然后操作共享數(shù)據(jù);
  • 再 unlock() 解鎖

2.0其他C++11新特性

2.1 std::lock_guard類模板

\(\quad\)我們?cè)诖a中上鎖后,一定要記得解鎖,如果忘記解鎖會(huì)導(dǎo)致程序運(yùn)行異常,而且通常很難排查。為了防止開(kāi)發(fā)者忘記解鎖,C++11引入了一個(gè)叫做 std::lock_guard 的類模板,它在開(kāi)發(fā)者忘記解鎖的時(shí)候,會(huì)替開(kāi)發(fā)者自動(dòng)解鎖。std::lock_guard 可以直接取代 lock() 和 unlock(),也就說(shuō)使用 std::lock_guard 后,就不能再使用 lock() 和 unlock() 了。

如下所示

std::mutex var_mutex;
int i = 0;
const int maxCnt = 1000000;
void mythread()
{
  for (int j = 0; j < maxCnt; j++)
  {
    lock_guard<mutex> guard(var_mutex);
    i++;  // 線程同時(shí)操作變量
  }
}
  • 輸出結(jié)果為

i=2000000
time: 0.102605s

\(\quad\)std::lock_guard 雖然用起來(lái)方便,但是不夠靈活,它只能在析構(gòu)函數(shù)中 unlock(),也就是對(duì)象被釋放的時(shí)候,這通常是在函數(shù)返回的時(shí)候,或者通過(guò)添加代碼塊 { /* 代碼塊 */ } 限定作用域來(lái)指定釋放時(shí)機(jī)。其還有一個(gè)特性是在構(gòu)造的時(shí)候可以傳入第二個(gè)參數(shù)為std::adopt_lock,此時(shí)在析構(gòu)的時(shí)候就不會(huì)unlock了,但是此時(shí)就必須我們手動(dòng)unlock了,這種使用場(chǎng)景也不多。

2.2 死鎖

\(\quad\)談到互斥量,就不得不來(lái)說(shuō)一下死鎖。一個(gè)簡(jiǎn)單的例子:

  • 張三在北京說(shuō):等李四來(lái)了之后,我就去廣東。
  • 李四在廣東說(shuō):等張三來(lái)了之后,我就去北京。

\(\quad\)張三李四互相扯皮,兩人一直互相等待,就死等(??)。同理,假設(shè)代碼中有兩把鎖,至少有兩個(gè)互斥量存在才會(huì)產(chǎn)生死鎖,分別稱為鎖1、鎖2,并且有兩個(gè)線程分別稱為線程A和線程B。只有在某個(gè)線程同時(shí)獲得鎖1和鎖2時(shí),才能完成某項(xiàng)工作:

  • 線程A執(zhí)行時(shí),先上鎖1------------再上鎖2。
  • 線程B執(zhí)行時(shí),先上鎖2------------再上鎖1。

\(\quad\)如果在執(zhí)行線程A的時(shí)候,先對(duì)1上了鎖,這時(shí)候出現(xiàn)了上下文切換(并不是說(shuō)上鎖之后該線程不會(huì)被其他線程占用,而是說(shuō)其他線程執(zhí)行到需要鎖1的時(shí)候如果發(fā)現(xiàn)被鎖了,會(huì)給出執(zhí)行權(quán)),現(xiàn)在來(lái)到了線程B,線程需要對(duì)鎖2上鎖,進(jìn)行數(shù)據(jù)操作,此時(shí)發(fā)現(xiàn)鎖2沒(méi)有被鎖,則上鎖,繼續(xù)執(zhí)行發(fā)現(xiàn),需要上鎖1,但是此時(shí)的鎖1被線程A鎖上了,于是只能給出執(zhí)行權(quán)限,此時(shí)又回到了線程1,線程1發(fā)現(xiàn)我如果想繼續(xù)執(zhí)行,那么就又要給2上鎖,但是發(fā)現(xiàn)鎖2又被線程B給上鎖了,于是也只好給出執(zhí)行權(quán)限。就這樣,兩個(gè)線程來(lái)回扯皮,就形成了死鎖。

\(\quad\)用一句話概括以下呢就是:在程序執(zhí)行線程A的過(guò)程中,上好了鎖1后,出現(xiàn)了上下文切換,系統(tǒng)調(diào)度轉(zhuǎn)去執(zhí)行線程B,把鎖2給上了,那么后續(xù)線程A拿不到鎖2,線程B拿不到鎖1,兩條線程都沒(méi)法往下執(zhí)行,即出現(xiàn)了死鎖。

  • 例如下面的程序
#include <pthread.h>
#include <atomic>
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#include <list>
using namespace std;
list<int> msgRecvQueue; // 容器(實(shí)際上是雙向鏈表):存放玩家發(fā)生命令的隊(duì)列 
mutex m_mutex1; // 創(chuàng)建互斥量1 
mutex m_mutex2; // 創(chuàng)建互斥量2 
void inMsgRecvQueue()
{
  for (int i = 0; i < 100000; ++i)
  {
    cout << "inMsgRecvQueue exec, push an elem " << i << endl;
    m_mutex1.lock(); // 實(shí)際代碼中,兩把鎖不一定同時(shí)上,它們可能保護(hù)不同的數(shù)據(jù)
    m_mutex2.lock();
    msgRecvQueue.push_back(i); // 假設(shè)數(shù)字 i 就是收到的玩家命令
    m_mutex2.unlock();
    m_mutex1.unlock();
  }
}
bool outMsgLULProc(int &command)
{
  m_mutex2.lock();
  m_mutex1.lock();
  if (!msgRecvQueue.empty())
  {
    command = msgRecvQueue.front(); // 返回第一個(gè)元素 
    msgRecvQueue.pop_front();       // 移除第一個(gè)元素 
    m_mutex1.unlock();
    m_mutex2.unlock();
    return true;
  }
  m_mutex1.unlock();
  m_mutex2.unlock();
  return false;
}
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;
  }
}
int main()
{
	thread myInMsgObj(inMsgRecvQueue);
	thread myOutMsgObj(outMsgRecvQueue);
	myInMsgObj.join();
	myOutMsgObj.join();
	cout << "Hello World!" << endl;
	return 0;
}

筆者運(yùn)行的時(shí)候發(fā)現(xiàn)程序會(huì)卡死,無(wú)法輸出最后的一句話

outMsgLULProc exec, and pop_front: 271
outMsgRecvQueue exec end!289
outMsgLULProc exec, and pop_front: 272
outMsgRecvQueue exec end!290
inMsgRecvQueue exec, push an elem 491

通常來(lái)講,死鎖的一般解決方案,只要保證多個(gè)互斥量上鎖的順序一致,就不會(huì)出現(xiàn)死鎖,比如把上面示例代碼的兩個(gè)線程回調(diào)函數(shù)中的上鎖順序改一下,保持一致就好了(都改為先上鎖1,再上鎖2)。讀者可以自己試一下改動(dòng)下代碼。

  • 線程A執(zhí)行時(shí),先上鎖1------------再上鎖2。
  • 線程B執(zhí)行時(shí),先上鎖1------------再上鎖2。

這樣的順序之下就形不成死鎖了。因?yàn)楫?dāng)切換到B的時(shí)候B由于沒(méi)有鎖1所以值接讓出執(zhí)行權(quán)限。

2.3 死鎖的另一種解決方案

std::lock() 函數(shù)模板是C++11引入的,它能一次鎖住兩個(gè)或兩個(gè)以上的互斥量,并且它不存在上述的在多線程中由于上鎖順序問(wèn)題造成的死鎖現(xiàn)象,原因如下:std::lock() 函數(shù)模板在鎖定兩個(gè)互斥量時(shí),只有兩種情況:

  • 兩個(gè)互斥量都沒(méi)有鎖??;
  • 兩個(gè)互斥量都被鎖住。

如果只鎖了一個(gè),另一個(gè)沒(méi)鎖成功,則它會(huì)立即把已經(jīng)鎖住的互斥量解鎖。將上面的接收函數(shù)改為如下就可以避免死鎖的出現(xiàn)。

void inMsgRecvQueue()
{
  for (int i = 0; i < 100000; ++i)
  {
    cout << "inMsgRecvQueue exec, push an elem " << i << endl;
    // m_mutex1.lock(); // 實(shí)際代碼中,兩把鎖不一定同時(shí)上,它們可能保護(hù)不同的數(shù)據(jù)
    // m_mutex2.lock();
    std::lock(m_mutex1,m_mutex2);
    msgRecvQueue.push_back(i); // 假設(shè)數(shù)字 i 就是收到的玩家命令
    m_mutex2.unlock();
    m_mutex1.unlock();
  }
}

在使用 std::lock() 函數(shù)模板鎖上多個(gè)互斥量時(shí),也必須得記得把每個(gè)互斥量解鎖,此時(shí)借助 std::lock_guard 的 std::adopt_lock 參數(shù)可以省略解鎖的代碼。我們?cè)偕晕⒏囊幌麓a,讓他看上去更modern一些。

void inMsgRecvQueue()
{
  for (int i = 0; i < 100000; ++i)
  {
    cout << "inMsgRecvQueue exec, push an elem " << i << endl;
    // m_mutex1.lock(); // 實(shí)際代碼中,兩把鎖不一定同時(shí)上,它們可能保護(hù)不同的數(shù)據(jù)
    // m_mutex2.lock();
    std::lock(m_mutex1, m_mutex2);									 // 鎖上兩個(gè)互斥量 
    std::lock_guard<std::mutex> m_guard1(m_mutex1, std::adopt_lock); // 構(gòu)造時(shí)不上鎖,但析構(gòu)時(shí)解鎖 
    std::lock_guard<std::mutex> m_guard2(m_mutex2, std::adopt_lock); // 構(gòu)造時(shí)不上鎖,但析構(gòu)時(shí)解鎖 
    msgRecvQueue.push_back(i); // 假設(shè)數(shù)字 i 就是收到的玩家命令
  }
}

Reference

到此這篇關(guān)于C++多線程中互斥量的使用的文章就介紹到這了,更多相關(guān)C++多線程互斥量?jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • android studio創(chuàng)建C++項(xiàng)目的實(shí)現(xiàn)示例

    android studio創(chuàng)建C++項(xiàng)目的實(shí)現(xiàn)示例

    本文主要介紹了android studio創(chuàng)建C++項(xiàng)目的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • C語(yǔ)言游戲之猜數(shù)字

    C語(yǔ)言游戲之猜數(shù)字

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言游戲之猜數(shù)字,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • C++ const 與 static變量、函數(shù)、類成員的全面解析(最新推薦)

    C++ const 與 static變量、函數(shù)、類成員的全面解析(最新推薦)

    本文系統(tǒng)解析了C++中const與static關(guān)鍵字的核心用法,文章還展示了二者在單例模式等設(shè)計(jì)模式中的綜合應(yīng)用,并比較了C++11引入的constexpr等現(xiàn)代特性,正確使用這些關(guān)鍵字能提升代碼安全性、可維護(hù)性和性能優(yōu)化,感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • MySQL的內(nèi)存表的基礎(chǔ)學(xué)習(xí)教程

    MySQL的內(nèi)存表的基礎(chǔ)學(xué)習(xí)教程

    這篇文章主要介紹了MySQL的內(nèi)存表的基礎(chǔ)學(xué)習(xí)教程,包括內(nèi)存表的創(chuàng)建以及使用限制等等,需要的朋友可以參考下
    2015-12-12
  • C語(yǔ)言模擬內(nèi)存函數(shù)分析之mencpy與memmove

    C語(yǔ)言模擬內(nèi)存函數(shù)分析之mencpy與memmove

    這篇文章主要介紹了C語(yǔ)言詳解如何模擬內(nèi)存函數(shù),用到了mencpy與memmove兩個(gè)函數(shù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-03-03
  • C/C++ Qt QThread線程組件的具體使用

    C/C++ Qt QThread線程組件的具體使用

    QThread庫(kù)是QT中提供的跨平臺(tái)多線程實(shí)現(xiàn)方案,本文詳細(xì)的介紹了Qt QThread線程組件的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • C語(yǔ)言實(shí)現(xiàn)24點(diǎn)游戲計(jì)算器的示例代碼

    C語(yǔ)言實(shí)現(xiàn)24點(diǎn)游戲計(jì)算器的示例代碼

    24點(diǎn)是一種益智游戲,24點(diǎn)是把4個(gè)整數(shù)(一般是正整數(shù))通過(guò)加減乘除以及括號(hào)運(yùn)算,使最后的計(jì)算結(jié)果是24的一個(gè)數(shù)學(xué)游戲,24點(diǎn)可以考驗(yàn)人的智力和數(shù)學(xué)敏感性,它能在游戲中提高人們的心算能力。本文將用C語(yǔ)言實(shí)現(xiàn)這一游戲,感興趣的可以了解一下
    2022-08-08
  • C++多線程中互斥量的使用詳解

    C++多線程中互斥量的使用詳解

    這篇文章主要介紹了C++多線程中互斥量的使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • VC程序設(shè)計(jì)小技巧20例

    VC程序設(shè)計(jì)小技巧20例

    這篇文章主要介紹了VC程序設(shè)計(jì)小技巧20例,需要的朋友可以參考下
    2014-07-07
  • Qt?CEF融合技QCefView使用教程(推薦)

    Qt?CEF融合技QCefView使用教程(推薦)

    QCefView是一個(gè)與Chromium?Embedded?Framework集成的Qt第三方開(kāi)源庫(kù),LGPL許可,可以在項(xiàng)目中免費(fèi)使用,功能類似CEF、QWebEngineView,提供C++和web交互的能力,本文給大家介紹Qt?CEF融合技QCefView使用教程,感興趣的朋友參考下吧
    2021-12-12

最新評(píng)論

安陆市| 遵义市| 缙云县| 剑阁县| 临湘市| 钟山县| 洛川县| 大丰市| 辉南县| 同心县| 门头沟区| 道真| 吴桥县| 达州市| 安吉县| 嘉峪关市| 博野县| 仲巴县| 樟树市| 社会| 鹤山市| 平乐县| 鹤峰县| 宁津县| 正蓝旗| 凭祥市| 望都县| 宜春市| 双桥区| 罗城| 炉霍县| 手游| 伊川县| 什邡市| 山阴县| 常熟市| 耒阳市| 洛南县| 宁夏| 瓦房店市| 济南市|