C++11內存模型的粗略解釋

基本解釋

    C++11引入了多線程,同時也引入了一套內存模型。從而提供了比較完善的一套多線程體系。在單線程時代,一切都很簡單。沒有共享數據,沒有亂序執行,全部的指令的執行都是按照預約的時間線。可是也正是由於這個強的同步關係,給CPU提供的優化程度也就相對低了不少。沒法體現當今多核CPU的性能。所以須要弱化這個強的同步關係,來增長CPU的性能優化。性能優化

Overview

 

    C++11提供了6種內存模型:多線程

  1 enum memory_order{
  2   memory_order_relaxed,
  3   memory_order_consume,
  4   memory_order_acquire,
  5   memory_order_release,
  6   memory_order_acq_rel,
  7   memory_order_seq_cst
  8 }

      原子類型的操做能夠指定上述6種模型的中的一種,用來控制同步以及對執行序列的約束。從而也引發兩個重要的問題:app

1.哪些原子類型操做須要使用內存模型?
2.內存模型定義了那些同步語義(synchronization )和執行序列約束(ordering constraints)?性能

 

原子操做可分爲3大類:優化

讀操做:memory_order_acquire, memory_order_consume
寫操做:memory_order_release
讀-修改-寫操做:memory_order_acq_rel, memory_order_seq_cstui

未被列入分類的memory_order_relaxed沒有定義任何同步語義和順序一致性約束atom

 

執行序列約束

    C++11中有3種不一樣類型的同步語義和執行序列約束:spa

1. 順序一致性(Sequential consistency):對應的內存模型是memory_order_seq_cst線程

2.請求-釋放(Acquire-release):對應的內存模型是memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_relcode

3.鬆散型(非嚴格約束。Relaxed):對應的內存模型是memory_order_relaxed

 

下面對上述3種約束作一個大概解釋:

Sequential consistency:指明的是在線程間,創建一個全局的執行序列

Acquire-release:在線程間的同一個原子變量的讀和寫操做上創建一個執行序列

Relaxed:只保證在同一個線程內,同一個原子變量的操做的執行序列不會被重排序(reorder),這種保證也稱之爲modification order consistency,可是其餘線程看到的這些操做的執行序列式不一樣的。

還有一種consume模式,也就是std::memory_order_consume。這個模式主要是引入了原子變量的數據依賴。

 

代碼解釋

Sequential consistency

Sequential consistency有兩個特性:
1.全部線程執行指令的順序都是按照源代碼的順序;
2.每一個線程所能看到其餘線程的操做的執行順序都是同樣的。

示例代碼:

  1 std::string work;
  2 std::atomic<bool> ready(false);
  3 
  4 void consumer(){
  5   while(!ready.load()){}
  6   std::cout<< work << std::endl;
  7 }
  8 
  9 void producer(){
 10   work= "done";
 11   ready=true;
 12 }

1. work = "done"  sequenced-before ready=true 推導出 work = "done" happens-before ready=true
2. while(!ready.load()){} sequenced-before std::cout<< work << std::endl 推導出 while(!ready.load()){} happens-before std::cout<< work << std::endl
3. ready = true synchronizes-with while(!ready.load()){} 推導出 ready = true inter-thread happens-before while (!ready.load()){},也就推導出ready = true happens-before while (!ready.load()){}

同時由於happens-before關係具備傳遞性,因此上述代碼的執行序列式:       

work = "done" happens-before ready = true happens-before while(!ready.load()){} happens-before std::cout<< work << std::endl

 

Acquire-release

關鍵思想是:在同一個原子變量的release操做和acquire操做間同步,同時也就創建起了執行序列約束。

全部的讀和寫動做不能移動到acquire操做以前。
全部的讀和寫動做不能移動到release操做以後。

release-acquire操做在線程間創建了一種happens-before。因此acquire以後的操做和release以前的操做就能進行同步。同時,release-acquire操做具備傳遞性。

示例代碼:

  1 std::vector<int> mySharedWork;
  2 std::atomic<bool> dataProduced(false);
  3 std::atomic<bool> dataConsumed(false);
  4 
  5 void dataProducer(){
  6     mySharedWork={1,0,3};
  7     dataProduced.store(true, std::memory_order_release);
  8 }
  9 
 10 void deliveryBoy(){
 11     while( !dataProduced.load(std::memory_order_acquire) );
 12     dataConsumed.store(true,std::memory_order_release);
 13 }
 14 
 15 void dataConsumer(){
 16     while( !dataConsumed.load(std::memory_order_acquire) );
 17     mySharedWork[1]= 2;
 18 }

1. mySharedWork={1,0,3};  is sequenced-before dataProduced.store(true, std::memory_order_release);
2. while( !dataProduced.load(std::memory_order_acquire) ); is sequenced-before dataConsumed.store(true,std::memory_order_release);
3. while( !dataConsumed.load(std::memory_order_acquire) ); is sequenced-before mySharedWork[1]= 2;

4. dataProduced.store(true, std::memory_order_release); is synchronizes-with while( !dataProduced.load(std::memory_order_acquire) );
5. dataConsumed.store(true,std::memory_order_release); is synchronizes-with while( !dataConsumed.load(std::memory_order_acquire) );

所以dataProducer和dataConsumer可以正確同步。

 

原子變量的數據依賴

std::memory_order_consume說的是關於原子變量的數據依賴。
數據依賴有兩種方式:
1. carries-a-dependency-to:若是操做A的結果用於操做B的操做當中,那麼A carries-a-dependency-to(將依賴帶入) B
2. dependency-ordered-before:若是操做B的結果進一步在相同的線程內被操做C使用,那麼A的stor操做(with std::memory_order_release, std::memory_order_acq_rel or std::memory_order_seq_cst)是dependency-ordered-before(在依賴執行序列X以前)B的load操做(with std::memory_order_consume)。

 

示例代碼:

  1 std::atomic<std::string*> ptr;
  2 int data;
  3 std::atomic<int> atoData;
  4 
  5 void producer(){
  6     std::string* p  = new std::string("C++11");
  7     data = 2011;
  8     atoData.store(2014,std::memory_order_relaxed);
  9     ptr.store(p, std::memory_order_release);
 10 }
 11 
 12 void consumer(){
 13     std::string* p2;
 14     while (!(p2 = ptr.load(std::memory_order_consume)));
 15     std::cout << "*p2: " << *p2 << std::endl;
 16     std::cout << "data: " << data << std::endl;
 17     std::cout << "atoData: " << atoData.load(std::memory_order_relaxed) << std::endl;
 18 }

1. ptr.store(p, std::memory_order_release) is dependency-ordered-before while (!(p2 = ptr.load(std::memory_order_consume)))。由於後面的std::cout << "*p2: " << *p2 << std::endl;將讀取load操做的結果。
2. while (!(p2 = ptr.load(std::memory_order_consume)) carries-a-dependency-to std::cout << "*p2: " << *p2 << std::endl。由於*p2的輸出使用了ptr.load操做的結果

綜上所述,對於data和atoData的輸出是沒有保證的。由於它們和ptr.load操做沒有carries-a-dependency-to關係。同時它們又不是原子變量,這將會致使race condition。由於在同一時間,多個線程能夠訪問data,線程t1(producer)同時會修改它。程序的行爲所以是未定義的(undefined)。

 

參考:

http://en.cppreference.com/w/cpp/atomic/memory_orderhttp://www.modernescpp.com/

相關文章
相關標籤/搜索