基本解釋
C++11引入了多線程,同時也引入了一套內存模型。從而提供了比較完善的一套多線程體系。在單線程時代,一切都很簡單。沒有共享數據,沒有亂序執行,所有的指令的執行都是按照預定的時間線。但是也正是因為這個強的同步關系,給CPU提供的優化程度也就相對低了很多。無法體現當今多核CPU的性能。因此需要弱化這個強的同步關系,來增加CPU的性能優化。
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/