2013-02-13 33 views
30

對於任何std::atomic<T>其中T是一個基本類型:採集/釋放與順序一致性存儲器順序

如果我使用std::memory_order_acq_relfetch_xxx操作,並且std::memory_order_acquireload操作和std::memory_order_releasestore操作盲目(我的意思一樣復位這些功能的默認內存排序)

  • 會,如果我用std::memory_order_seq_cst(正在使用默認)任何聲明的運算的結果是一樣的嗎?
  • 如果結果相同,這種用法是否與在效率方面使用std::memory_order_seq_cst不同?
+0

這取決於底層硬件必須提供什麼。如果您不知道具體是如何工作的,並且不得不根據這一點進行優化,那麼默認值可能是正確的。在常見的x86系統中,如果有的話,幾乎沒有什麼區別。 – 2013-02-13 19:58:44

+1

@Bo Persson在x86上,gcc在seq_cst存儲後插入完整的MFENCE。當你說「退出內存」時,這會使它顯着變慢 – LWimsey 2017-01-24 00:32:21

回答

56

原子操作的C++ 11內存排序參數指定排序約束。如果你做一個存儲與std::memory_order_release,並且從另一個線程的負載讀取第二線程與std::memory_order_acquire然後隨後的讀操作的值將由前店釋放那名第一個線程看到存儲到任何存儲位置的任何值,或稍後的商店到這些存儲位置中的任何一個。

如果同時存儲和後續的加載是std::memory_order_seq_cst那麼這兩個線程之間的關係是相同的。你需要更多線程才能看出差異。

例如std::atomic<int>變量xy,無論最初0

線程1:

x.store(1,std::memory_order_release); 

線程2:

y.store(1,std::memory_order_release); 

線程3:

int a=x.load(std::memory_order_acquire); // x before y 
int b=y.load(std::memory_order_acquire); 

線程4:

int c=y.load(std::memory_order_acquire); // y before x 
int d=x.load(std::memory_order_acquire); 

書面,還有就是xy商店之間沒有任何關係,所以它很可能看到a==1b==0螺紋3,和c==1d==0螺紋4

如果所有存儲器訂單更改爲std::memory_order_seq_cst,那麼這會強制在商店到xy之間訂購。因此,如果線程3看到a==1b==0那麼這意味着商店x必須商店y之前,因此,如果線程4看到c==1,這意味着商店y已完成,然後店裏x也必須完成,所以我們必須有d==1

在實踐中,然後使用std::memory_order_seq_cst到處都將增加額外的開銷,要麼加載或存儲或兩者兼而有之,這取決於你的編譯器和處理器架構。例如用於x86處理器的通用技術是使用XCHG指令,而不是爲std::memory_order_seq_cst存儲MOV指令,以便提供必要的排序保證,而對於std::memory_order_releaseMOV就足夠了。在內存架構更寬鬆的系統上,開銷可能更大,因爲普通加載和存儲的保證更少。

內存排序是很難的。我在my book中用了幾乎整整一章的篇幅。

+0

我期待着一個失敗的例子,謝謝你的回答。 – zahir 2013-02-13 22:40:38

+0

「如果所有的內存排序變更爲std :: memory_order_seq_cst那麼這種強制x和y的存儲之間的排序」 - 這可能通過設置只有一些排序來seq_cst達到同樣的效果?像y。商店 – qble 2013-02-15 14:18:24

+2

否。「單一總訂單」約束僅適用於'memory_order_seq_cst'操作。不包含其他內存排序的操作,並且因此可以在不同線程中以不同順序出現,只要滿足任何其他約束。 – 2013-02-15 14:25:30

6

內存排序可能相當棘手,並且使其出錯的影響往往非常微妙。

所有記憶順序的關鍵在於它保證了「已經發生」,而不是發生什麼事情。例如,如果您將某些內容存儲到多個變量(例如x = 7; y = 11;)中,則另一個處理器在看到x中的值7之前可能會將y看作11。通過在設置x和設置y之間使用內存排序操作,您正在使用的處理器將保證x = 7;已被寫入內存,然後繼續將內容存儲在y中。

大多數情況下,只要最終更新值,對於您的寫入操作發生順序並不重要。但是,如果我們說,有整數的循環緩衝區,我們這樣做:

buffer[index] = 32; 
index = (index + 1) % buffersize; 

和一些其他的線程使用index,以確定新的值寫入,那麼我們就需要有32寫FIRST,然後index更新後。否則,另一個線程可能會獲得old數據。

這同樣適用於製造信號量,互斥和這些工作 - 這就是爲什麼釋放和獲取條款用於內存屏障類型。

現在,cst是最嚴格的排序規則 - 它強制讀取和寫入您寫入的數據在處理器可以繼續執行更多操作之前會寫入內存。這將比做具體的獲取或釋放壁壘慢。它強制處理器確保商店和加載已完成,而不僅僅是存儲或僅加載。

這樣做有多大的區別?它高度依賴於系統的結構。在某些系統上,緩存需要[部分]刷新,並且從一個核心發送到另一個核心的中斷髮出「請在繼續之前執行緩存刷新工作」 - 這可能需要幾百個週期。在其他處理器上,它只比進行常規內存寫入慢一點點。 X86在這方面做得非常好。某些類型的嵌入式處理器(某些型號 - 不確定?)例如,ARM需要在處理器上多做一些工作才能確保一切正常。

+0

,我們必須從CPU緩存理解到中央內存? – Guillaume07 2014-12-14 20:15:26