程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 探討在C++法式並發時掩護同享數據的成績

探討在C++法式並發時掩護同享數據的成績

編輯:關於C++

探討在C++法式並發時掩護同享數據的成績。本站提示廣大學習愛好者:(探討在C++法式並發時掩護同享數據的成績)文章只能為提供參考,不一定能成為您想要的結果。以下是探討在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個線程來增長計數器的值,每一個線程增長了100次,然後在線程停止時打印計數器的值。


但我們運轉這個法式的時刻,我們是願望它會准許500,但現實不是如斯,沒人能確實曉得法式將打印甚麼成果,上面是在我機械上運轉後打印的數據,並且每次都分歧:
 

442
500
477
400
422
487

成績的緣由在於轉變計數器值其實不是一個原子操作,須要經由上面三個操作能力完成一次計數器的增長:

  •     起首讀取 value 的值
  •     然後將 value 值加1
  •     將新的值賦值給 value

但你應用單線程來運轉這個法式的時刻固然沒有任何成績,是以法式是次序履行的,但在多線程情況中就有費事了,想象下上面這個履行次序:

  •     Thread 1 : 讀取 value, 獲得 0, 加 1, 是以 value = 1
  •     Thread 2 : 讀取 value, 獲得 0, 加 1, 是以 value = 1
  •     Thread 1 : 將 1 賦值給 value,然後前往 1
  •     Thread 2 : 將 1 賦值給 value,然後前往 1

這類情形我們稱之為多線程的交織履行,也就是說多線程能夠在統一個時光點履行雷同的語句,雖然只要兩個線程,交織的景象也很顯著。假如你有更多的線程、更多的操作須要履行,那末這個交織是必定產生的。

有許多辦法來處理線程交織的成績:

  •     旌旗燈號量 Semaphores
  •     原子援用 Atomic references
  •     Monitors
  •     Condition codes
  •     Compare and swap

在這篇文章中我們將進修若何應用旌旗燈號量來處理這個成績。旌旗燈號量也有許多人稱之為互斥量(Mutex),統一個時光只許可一個線程獲得一個互斥對象的鎖,經由過程 Mutex 的簡略屬性便可以用來處理交織的成績。

應用 Mutex 讓計數器法式是線程平安的

在 C++11 線程庫中,互斥量包括在 mutex 頭文件中,對應的類是 std::mutex,有兩個主要的辦法 mutex:lock() 和 unlock() ,從名字上可得知是用來鎖對象和釋放鎖對象。一旦某個互斥量被鎖,那末再次挪用 lock() 前往梗塞值得該對象被釋放。

為了讓我們適才的計數器構造體是線程平安的,我們添加一個 set:mutext 成員,並在每一個辦法中經由過程 lock()/unlock() 辦法來停止掩護:
 

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

然後我們再次測試這個法式,打印的成果就是 500 了,並且每次都一樣。

異常和鎖

如今讓我們來看別的一種情形,想象我們的的計數器有一個減操作,並在值為0的時刻拋出異常:
 

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

然後我們不須要修正類來拜訪這個構造體,我們創立一個封裝器:
 

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

年夜部門時刻該封裝器運轉挺好,然則應用 decrement 辦法的時刻就會有異常產生。這是一個年夜成績,一旦異常產生後,unlock 辦法就沒被挪用,招致互斥量一向被占用,然後全部法式就一向處於梗塞狀況(逝世鎖),為懂得決這個成績我們須要用 try/catch 構造來處置異常情形:
 

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

這個代碼其實不難,但看起來很丑,假如你一個函數有 10 個加入點,你就必需為每一個加入點挪用一次 unlock 辦法,也許你能夠在某個處所忘失落了 unlock ,那末各類喜劇行將產生,喜劇產生將直接招致法式逝世鎖。

接上去我們看若何處理這個成績。

主動鎖治理

當你須要包括整段的代碼(在我們這裡是一個辦法,也能夠是一個輪回體或許其他的掌握構造),有這麼一種好的處理辦法可以免忘卻釋放鎖,那就是 std::lock_guard.

這個類是一個簡略的智能鎖治理器,但創立 std::lock_guard 時,會主動挪用互斥量對象的 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 ,你不再須要斟酌甚麼時刻要釋放鎖,這個任務曾經由 std::lock_guard 實例幫你完成。

結論

在這篇文章中我們進修了若何經由過程旌旗燈號量/互斥量來掩護同享數據。須要記住的是,應用鎖會下降法式機能。在一些高並發的運用情況中有其他更好的處理方法,不外這不在本文的評論辯論領域以內。

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved