2016-02-26 27 views
1

Cppreference給following examplememory_order_relaxed重新排序和memory_order_relaxed

原子操作標記memory_order_relaxed不同步 操作,他們不要命的記憶。它們只保證原子性 和修改順序一致性。

然後解釋,隨着xy最初爲零,這個例子代碼

// Thread 1: 
r1 = y.load(memory_order_relaxed); // A 
x.store(r1, memory_order_relaxed); // B 

// Thread 2: 
r2 = x.load(memory_order_relaxed); // C 
y.store(42, memory_order_relaxed); // D 

被允許產生r1 == r2 == 42因爲:

  1. 雖然A是測序-前內乙線程1和C是已排序 - 之前 D在線程2中,
  2. 沒有什麼能夠防止D出現在修改順序爲y的A之前,並且B在修改順序爲x的C之前出現。

現在的問題是:如果A和B不能線程1內被重新排序,並且類似地,線程2內C和d(因爲每個那些是測序-之前其螺紋內),不是第1點和第2點相沖突?換句話說,不需要重新訂購(如第1點似乎需要的那樣),第2點中的情景如何可能,下面是可視化的?

T1 ........... T2

.............. d(y)的

A(Y)

B(x)的

.............. C(X)

因爲在這種情況下C將測序-之前 d線2內作爲一點1的要求。

+0

我會建議在這裏看:http://stackoverflow.com/questions/6319146/c11-introduced-a-standardized-memory-model-what-does-it-mean-and-how-is-it- g,特別是關於與狹義相對論類比的答案。我對此的理解是,在輕鬆原子的情況下,沒有什麼是全球時間,因此您不能簡單地將某些全球時間圖中的動作可視化。特別是,每個線程可以在特定時間具有不同的內存視圖。 –

回答

4

沒有重新排序(如1點似乎需要)

1點並不意味着 「不重排」。它意味着執行線程內的事件排序。編譯器將在B之前爲A發出CPU指令,在D之前爲C發出CPU指令(雖然即使這可能會被as-if規則破壞),但CPU沒有義務按順序執行它們,緩存/寫入緩衝區/無效隊列沒有義務按順序傳播它們,並且內存沒有義務統一。

(個體架構可提供那些保證雖然)

+0

所以,_sequenced-before_只適用於compiler_發出的相應指令的順序,CPU_can_可以在寬鬆的內存模型中重新排序? –

+0

CPU可以對所有內容重新排序,但是fence指令(和A和B之間的數據依賴關係)限制了它的功能。 – Cubbi

+0

cpu無法對所有內容重新排序,每個體系結構都有規則(有時在同一個體系結構中有不同的mem /操作類型),說明可以重新排序的內容。 – Leeor

0

您對文本的解釋是錯誤的。讓我們打破這:

原子操作標記memory_order_relaxed沒有同步操作,他們不下令內存

這意味着,這些操作做出關於事件的順序不能保證。正如原始文本中的那個語句之前所解釋的那樣,允許多線程處理器在單個線程內重新排序操作。這會影響寫入,讀取或兩者。此外,編譯器被允許在編譯時做同樣的事情(主要是爲了優化目的)。要看看這與示例有何關係,假設我們根本不使用atomic類型,但我們確實使用原子類型設計的基本類型(8位值...)。讓我們重寫例如:

// Somewhere... 
uint8_t y, x; 

// Thread 1: 
uint8_t r1 = y; // A 
x = r1; // B 

// Thread 2: 
uint8_t r2 = x; // C 
y = 42; // D 

同時考慮編譯器和CPU被允許在每個線程重新排序操作,可以很容易地看到x == y == 42是可能的。


語句的下一部分是:

他們只保證原子和修改順序的一致性。

這意味着只能保證是,每個操作是原子,即,是不可能的被觀察的操作「儘管中間方式」。這意味着如果xatomic<someComplexType>,則一個線程不可能觀察到x在兩個狀態之間具有值。


這應該已經是不清楚的地方可以說是有用的,但讓我們來看看具體的例子(示範只提出,這是不是你想怎麼代碼):

class SomeComplexType { 
    public: 
    int size; 
    int *values; 
} 

// Thread 1: 
SomeComplexType r = x.load(memory_order_relaxed); 
if(r.size > 3) 
    r.values[2] = 123; 

// Thread 2: 
SomeComplexType a, b; 
a.size = 10; a.values = new int[10]; 
b.size = 0; b.values = NULL; 
x.store(a, memory_order_relaxed); 
x.store(b, memory_order_relaxed); 

什麼atomic類型爲我們確保線程1中的r不是處於狀態之間的對象,具體而言,它是size & valuesvalues屬性同步。

+0

不是子句_「A被排序 - 在線程1之前的B之前,並且C被排序 - 之前線程2之中的D」_意味着線程中的**操作不被重新排序**? –

+0

@DanielLangr withing線程 - 是的,但CPU和緩存都重新排序的內存訪問B之前進一步 – Cubbi

+0

@Cubbi _IF A被測序,則A的評價會是B begins._那麼,什麼是從cppreference這條規則的評估之前完成。 com的意思是?它是否允許CPU重新排序,即在運行時,B可以在A之前被評估? –

1

據這個職位的STR比喻:C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?,我創建的可以在這裏發生的事情可視化(據我所知)如下:

enter image description here

線程1第一次看到y=42,那麼它執行r1=y,x=r1。線程2首先看到x=r1已經是42,然後它執行r2=x之後y=42

線表示單個線程對內存的「視圖」。這些行/視圖不能爲特定的線程交叉。但是,隨着輕鬆的原子,一個線程的線/視圖可以跨越這些其他線程。

編輯:

我想這是相同的,與下面的程序:

atomic<int> x{0}, y{0}; 

// thread 1: 
x.store(1, memory_order_relaxed); 
cout << x.load(memory_order_relaxed) << y.load(memory_order_relaxed); 

// thread 2: 
y.store(1, memory_order_relaxed); 
cout << x.load(memory_order_relaxed) << y.load(memory_order_relaxed); 

它可以在輸出端產生0110(這樣的輸出無法與SC原子發生操作)。

0

在C++存儲器模型(未談及編譯器或硬件重排序),導致R1 = R2 = 42只執行尋找僅僅是:

enter image description here

在這裏,我代替R1與和r2用b。 像往常一樣,sb代表sequenced-before,簡直就是線程間排序(指令在源代碼中出現的順序)。 rf是Read-From邊緣,表示一端的讀取/讀取讀取寫入/存儲在另一端的值。

以綠色突出顯示的包含sb和rf邊緣的循環對於結果是必需的:y寫入一個線程,它在另一個線程中讀入,並從那裏寫入x,再讀入前面的線程到b中(在寫入y之前,它被排序)。

有兩個原因,這樣的構造圖是不可能的:因果關係,因爲RF讀取隱藏的副作用。在這種情況下,後者是不可能的,因爲我們只向每個變量寫入一次,所以顯然,一次寫入不能被另一次寫入隱藏(覆蓋)。

爲了回答因果性問題,我們遵循以下規則:當涉及單個存儲位置並且sb邊的方向在循環中的任何地方處於相同方向時(循環的方向),循環被禁止(不可能)在這種情況下,射頻邊緣不相關);或者,循環涉及多於一個變量,所有邊(sb和rf)都處於相同方向,並且其中一個變量在不同線程之間具有一個或多個不是釋放/獲取的rf邊。在這種情況下,存在兩個變量(一個用於x的邊緣和一個用於y的邊緣),所有的邊都在同一個方向上,但是兩個變量具有鬆弛/鬆弛的邊緣(即x和y)。因此沒有因果關係衝突,這是一個與C++內存模型一致的執行。