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種模型的中的一種,用來控制同步以及對執行序列的約束。從而也引起兩個重要的問題:

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

 

原子操作可分為3大類:

讀操作:memory_order_acquire, memory_order_consume
寫操作:memory_order_release
讀-修改-寫操作:memory_order_acq_rel, memory_order_seq_cst

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

 

執行序列約束

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

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

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

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_order
http://www.modernescpp.com/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM