簡體   English   中英

重新排序和memory_order_relaxed

[英]Reordering and memory_order_relaxed

Cppreference給出以下有關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. 盡管在線程1中A B 之前排在前面,而在線程2中C D 之前排在前面,
  2. 沒有什么可以阻止D以y的修改順序出現在A之前,而B是以x的修改順序出現在C之前。

現在我的問題是:如果不能在線程1中對A和B進行重新排序,並且類似地在線程2中對C和D進行重新排序(因為它們中的每一個都在其線程內先后排序 ), 那么點1和點2是否不矛盾 換句話說, 在沒有重新排序的情況下 (似乎要指出第1點),如何甚至可視化下面第2點中的情況?

T1 ...... T2

.............. D(y)

A(y)的

B(x)的

.............. C(x)

因為在這種情況下C將測序-之前線程2內d,如點1級的要求。

無需重新排序(如第1點所示)

點1並不意味着“不重新排序”。 這意味着在執行線程中對事件進行排序。 編譯器將在B之前向A發出CPU指令,而在D之前向C發出CPU指令(盡管即使被as-if規則顛覆),但CPU也沒有義務按照該順序執行它們,即緩存/寫入緩沖區/無效隊列沒有義務按該順序傳播,並且內存也沒有統一的義務。

(盡管個別架構可以提供這些保證)

根據這篇文章的STR類推: C ++ 11引入了標准化的內存模型。 這是什么意思? 它將如何影響C ++編程? ,我創建了可視化效果圖(據我了解),如下所示:

在此處輸入圖片說明

線程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原子操作不會發生這樣的輸出)。

您對文本的解釋是錯誤的。 讓我們分解一下:

標記為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在兩個狀態之間不是對象,尤其是它的sizevalues屬性是同步的。

僅查看C ++內存模型(不討論編譯器或硬件重新排序),導致r1 = r2 = 42的唯一執行是:

在此處輸入圖片說明

在這里,我將r1替換為a,將r2替換為b。 像往常一樣,sb代表先於順序,並且僅是線程間排序(指令在源代碼中出現的順序)。 rf是“ Read-From”邊緣,表示一端的“讀取/加載”讀取另一端寫入/存儲的值。

對於循環來說,包含sb和rf邊緣的循環(以綠色突出顯示)對於結果是必不可少的:y被寫在一個線程中,在另一個線程中被讀取到a中,然后從那里被寫入x,在前者中被讀取。再次線程到b(在寫入y之前先排序)。

為什么不能像這樣構造圖有兩個原因:因果關系和因為RF讀取了隱藏的副作用。 在這種情況下,后者是不可能的,因為我們只向每個變量寫入一次,因此很明顯一個寫入不能被另一個寫入隱藏(覆蓋)。

為了回答因果關系問題,我們遵循以下規則:當循環涉及單個內存位置且sb邊的方向在循環中的每個位置都在同一方向(rf邊的方向)時,不允許循環(不可能)在這種情況下不相關); 或者,該循環涉及多個變量,所有邊沿(sb和rf)都在同一方向上,並且至少一個變量在不釋放/獲取的不同線程之間具有一個或多個rf邊沿。

在這種情況下,存在循環,涉及兩個變量(x的一個rf邊和y的一個rf邊),所有邊都在同一方向上,但是兩個變量具有松弛/松弛的rf邊(即x和y)。 因此,沒有因果關系違規,並且此執行與C ++內存模型一致。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM