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

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

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

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

1.0 互斥量的基本概念

1.1 Example

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

  • 我們來看一個簡單的操作
#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++;  // 線程同時操作變量
  }
}
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;  // 秒計時
}

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

i=1022418
time: 0.010445s

很明顯和我們預(yù)想的結(jié)果是不一致的,我們使用兩個線程同時對該變量進行加法操作,根據(jù)運行此書來計算,結(jié)果因該為 2000000,但事實上卻不是這樣的,這就是因為有多個線程在對同一個變量進行寫操作的時候會出現(xiàn)難以排查的問題,意想不到的結(jié)果。此時mutex就派上用場了,我們對程序進行稍微的改動。

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

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

i=2000000
time: 0.09337s

1.2 互斥量用法解釋

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

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

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

2.0其他C++11新特性

2.1 std::lock_guard類模板

\(\quad\)我們在代碼中上鎖后,一定要記得解鎖,如果忘記解鎖會導(dǎo)致程序運行異常,而且通常很難排查。為了防止開發(fā)者忘記解鎖,C++11引入了一個叫做 std::lock_guard 的類模板,它在開發(fā)者忘記解鎖的時候,會替開發(fā)者自動解鎖。std::lock_guard 可以直接取代 lock() 和 unlock(),也就說使用 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++;  // 線程同時操作變量
  }
}
  • 輸出結(jié)果為

i=2000000
time: 0.102605s

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

2.2 死鎖

\(\quad\)談到互斥量,就不得不來說一下死鎖。一個簡單的例子:

  • 張三在北京說:等李四來了之后,我就去廣東。
  • 李四在廣東說:等張三來了之后,我就去北京。

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

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

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

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

  • 例如下面的程序
#include <pthread.h>
#include <atomic>
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
#include <list>
using namespace std;
list<int> msgRecvQueue; // 容器(實際上是雙向鏈表):存放玩家發(fā)生命令的隊列 
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ù)據(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(); // 返回第一個元素 
    msgRecvQueue.pop_front();       // 移除第一個元素 
    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;
}

筆者運行的時候發(fā)現(xiàn)程序會卡死,無法輸出最后的一句話

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

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

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

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

2.3 死鎖的另一種解決方案

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

  • 兩個互斥量都沒有鎖??;
  • 兩個互斥量都被鎖住。

如果只鎖了一個,另一個沒鎖成功,則它會立即把已經(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ù)據(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ù)模板鎖上多個互斥量時,也必須得記得把每個互斥量解鎖,此時借助 std::lock_guard 的 std::adopt_lock 參數(shù)可以省略解鎖的代碼。我們再稍微更改一下代碼,讓他看上去更modern一些。

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

Reference

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

相關(guān)文章

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

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

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

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

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

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

    本文系統(tǒng)解析了C++中const與static關(guān)鍵字的核心用法,文章還展示了二者在單例模式等設(shè)計模式中的綜合應(yīng)用,并比較了C++11引入的constexpr等現(xiàn)代特性,正確使用這些關(guān)鍵字能提升代碼安全性、可維護性和性能優(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語言模擬內(nèi)存函數(shù)分析之mencpy與memmove

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

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

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

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

    C語言實現(xiàn)24點游戲計算器的示例代碼

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

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

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

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

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

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

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

最新評論

淮北市| 肇源县| 泽库县| 新郑市| 曲靖市| 辽宁省| 沙洋县| 哈密市| 宣武区| 阿鲁科尔沁旗| 南康市| 珲春市| 稷山县| 高碑店市| 彩票| 双桥区| 阿坝| 临湘市| 吉隆县| 墨脱县| 大丰市| 清徐县| 鄂尔多斯市| 容城县| 东山县| 方山县| 淮北市| 日土县| 烟台市| 深泽县| 水富县| 隆化县| 仁化县| 盐津县| 阿克苏市| 贞丰县| 安远县| 遂溪县| 汪清县| 攀枝花市| 福建省|