欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

探究在C++程序并發(fā)時保護共享數(shù)據(jù)的問題

 更新時間:2015年07月14日 11:49:55   投稿:goldensun  
這篇文章主要介紹了探究在C++程序并發(fā)時保護共享數(shù)據(jù)的問題,也有利于大家更好地理解C++多線程的一些機制,需要的朋友可以參考下

 我們先通過一個簡單的代碼來了解該問題。
同步問題

我們使用一個簡單的結構體 Counter,該結構體包含一個值以及一個方法用來改變這個值:
 

struct Counter {
  int value;
 
  void increment(){
    ++value;
  }
};

然后啟動多個線程來修改結構體的值:

 

int main(){
  Counter counter;
 
  std::vector<std::thread> threads;
  for(int i = 0; i < 5; ++i){
    threads.push_back(std::thread([&counter](){
      for(int i = 0; i < 100; ++i){
        counter.increment();
      }
    }));
  }
 
  for(auto& thread : threads){
    thread.join();
  }
 
  std::cout << counter.value << std::endl;
 
  return 0;
}

我們啟動了5個線程來增加計數(shù)器的值,每個線程增加了100次,然后在線程結束時打印計數(shù)器的值。


但我們運行這個程序的時候,我們是希望它會答應500,但事實不是如此,沒人能確切知道程序?qū)⒋蛴∈裁唇Y果,下面是在我機器上運行后打印的數(shù)據(jù),而且每次都不同:
 

442
500
477
400
422
487

問題的原因在于改變計數(shù)器值并不是一個原子操作,需要經(jīng)過下面三個操作才能完成一次計數(shù)器的增加:

  •     首先讀取 value 的值
  •     然后將 value 值加1
  •     將新的值賦值給 value

但你使用單線程來運行這個程序的時候當然沒有任何問題,因此程序是順序執(zhí)行的,但在多線程環(huán)境中就有麻煩了,想象下下面這個執(zhí)行順序:

  •     Thread 1 : 讀取 value, 得到 0, 加 1, 因此 value = 1
  •     Thread 2 : 讀取 value, 得到 0, 加 1, 因此 value = 1
  •     Thread 1 : 將 1 賦值給 value,然后返回 1
  •     Thread 2 : 將 1 賦值給 value,然后返回 1

這種情況我們稱之為多線程的交錯執(zhí)行,也就是說多線程可能在同一個時間點執(zhí)行相同的語句,盡管只有兩個線程,交錯的現(xiàn)象也很明顯。如果你有更多的線程、更多的操作需要執(zhí)行,那么這個交錯是必然發(fā)生的。

有很多方法來解決線程交錯的問題:

  •     信號量 Semaphores
  •     原子引用 Atomic references
  •     Monitors
  •     Condition codes
  •     Compare and swap

在這篇文章中我們將學習如何使用信號量來解決這個問題。信號量也有很多人稱之為互斥量(Mutex),同一個時間只允許一個線程獲取一個互斥對象的鎖,通過 Mutex 的簡單屬性就可以用來解決交錯的問題。

使用 Mutex 讓計數(shù)器程序是線程安全的

在 C++11 線程庫中,互斥量包含在 mutex 頭文件中,對應的類是 std::mutex,有兩個重要的方法 mutex:lock() 和 unlock() ,從名字上可得知是用來鎖對象以及釋放鎖對象。一旦某個互斥量被鎖,那么再次調(diào)用 lock() 返回堵塞值得該對象被釋放。

為了讓我們剛才的計數(shù)器結構體是線程安全的,我們添加一個 set:mutext 成員,并在每個方法中通過 lock()/unlock() 方法來進行保護:
 

struct Counter {
  std::mutex mutex;
  int value;
 
  Counter() : value(0) {}
 
  void increment(){
    mutex.lock();
    ++value;
    mutex.unlock();
  }
};

然后我們再次測試這個程序,打印的結果就是 500 了,而且每次都一樣。

異常和鎖

現(xiàn)在讓我們來看另外一種情況,想象我們的的計數(shù)器有一個減操作,并在值為0的時候拋出異常:
 

struct Counter {
  int value;
 
  Counter() : value(0) {}
 
  void increment(){
    ++value;
  }
 
  void decrement(){
    if(value == 0){
      throw "Value cannot be less than 0";
    }
 
    --value;
  }
};

然后我們不需要修改類來訪問這個結構體,我們創(chuàng)建一個封裝器:
 

struct ConcurrentCounter {
  std::mutex mutex;
  Counter counter;
 
  void increment(){
    mutex.lock();
    counter.increment();
    mutex.unlock();
  }
 
  void decrement(){
    mutex.lock();
    counter.decrement();    
    mutex.unlock();
  }
};

大部分時候該封裝器運行挺好,但是使用 decrement 方法的時候就會有異常發(fā)生。這是一個大問題,一旦異常發(fā)生后,unlock 方法就沒被調(diào)用,導致互斥量一直被占用,然后整個程序就一直處于堵塞狀態(tài)(死鎖),為了解決這個問題我們需要用 try/catch 結構來處理異常情況:
 

void decrement(){
  mutex.lock();
  try {
    counter.decrement();
  } catch (std::string e){
    mutex.unlock();
    throw e;
  }
  mutex.unlock();
}

這個代碼并不難,但看起來很丑,如果你一個函數(shù)有 10 個退出點,你就必須為每個退出點調(diào)用一次 unlock 方法,或許你可能在某個地方忘掉了 unlock ,那么各種悲劇即將發(fā)生,悲劇發(fā)生將直接導致程序死鎖。

接下來我們看如何解決這個問題。

自動鎖管理

當你需要包含整段的代碼(在我們這里是一個方法,也可能是一個循環(huán)體或者其他的控制結構),有這么一種好的解決方法可以避免忘記釋放鎖,那就是 std::lock_guard.

這個類是一個簡單的智能鎖管理器,但創(chuàng)建 std::lock_guard 時,會自動調(diào)用互斥量對象的 lock() 方法,當 lock_guard 析構時會自動釋放鎖,請看下面代碼:

 

struct ConcurrentSafeCounter {
  std::mutex mutex;
  Counter counter;
 
  void increment(){
    std::lock_guard<std::mutex> guard(mutex);
    counter.increment();
  }
 
  void decrement(){
    std::lock_guard<std::mutex> guar(mutex);
    mutex.unlock();
  }
};

是不是看起來爽多了?

使用 lock_guard ,你不再需要考慮什么時候要釋放鎖,這個工作已經(jīng)由 std::lock_guard 實例幫你完成。

結論

在這篇文章中我們學習了如何通過信號量/互斥量來保護共享數(shù)據(jù)。需要記住的是,使用鎖會降低程序性能。在一些高并發(fā)的應用環(huán)境中有其他更好的解決辦法,不過這不在本文的討論范疇之內(nèi)。

你可以在 Github 上獲取本文的源碼.

相關文章

  • C++內(nèi)存模型和名稱空間詳解

    C++內(nèi)存模型和名稱空間詳解

    這篇文章主要給大家介紹了關于C/C++中的內(nèi)存模型和名稱空間詳解,文中通過示例代碼介紹的非常詳細,對大家學習或者使用c/c++具有一定的參考學習價值,需要的朋友們下面隨著小編來一起看看吧
    2021-09-09
  • C語言逆向分析語法超詳細分析

    C語言逆向分析語法超詳細分析

    這篇文章主要介紹了C語言逆向分析語法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-11-11
  • C語言實現(xiàn)多線程定時器實例講解

    C語言實現(xiàn)多線程定時器實例講解

    在本篇文章里小編給各位分享的是一篇關于C語言實現(xiàn)多線程定時器實例講解內(nèi)容,有需要的朋友們可以參考學習下。
    2021-01-01
  • C++編程歸并排序算法實現(xiàn)示例

    C++編程歸并排序算法實現(xiàn)示例

    這篇文章主要為大家介紹了C++編程歸并排序算法實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2021-11-11
  • c++11 chrono全面解析(最高可達納秒級別的精度)

    c++11 chrono全面解析(最高可達納秒級別的精度)

    chrono是c++ 11中的時間庫,本文就來詳細的介紹一下chrono庫的具體使用,關鍵是理解里面時間段(Durations)、時間點(Time points)的概念,感興趣的可以了解一下
    2021-11-11
  • 深入分析C++中deque的使用

    深入分析C++中deque的使用

    本篇文章介紹了,深入分析C++中deque的使用。需要的朋友參考下
    2013-05-05
  • C++設計模式之模板方法模式(TemplateMethod)

    C++設計模式之模板方法模式(TemplateMethod)

    這篇文章主要為大家詳細介紹了C++設計模式之模板方法模式TemplateMethod,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • C++實現(xiàn)MyString的示例代碼

    C++實現(xiàn)MyString的示例代碼

    本文主要介紹了C++實現(xiàn)MyString的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 詳解C語言解決經(jīng)典問題之兔子產(chǎn)子

    詳解C語言解決經(jīng)典問題之兔子產(chǎn)子

    有一對兔子,從出生后的第 3 個月起每個月都生一對兔子。小兔子長到第 3 個月后每個月又生一對兔子,假設所有的兔子都不死,問 30 個月內(nèi)每個月的兔子總數(shù)為多少?本文將用C語言解決這一經(jīng)典問題,需要的可以參考一下
    2022-03-03
  • C++分析如何用虛析構與純虛析構處理內(nèi)存泄漏

    C++分析如何用虛析構與純虛析構處理內(nèi)存泄漏

    虛析構和純虛析構共性:可以解決父類指針釋放子類對象,都需要有具體的函數(shù)實現(xiàn);虛析構和純虛析構區(qū)別:如果是純虛析構,該類屬于抽象類,無法實例化對象
    2022-08-08

最新評論