2011-04-05 70 views
10

C++0x draft有一個柵欄的概念,它看起來與CPU /芯片級柵欄概念非常不同,或者說Linux內核人員期望的fences。問題在於草案是否真的意味着一個非常有限的模式,或者措辭只是很差,實際上意味着真正的圍牆。C++ 0x的柵欄只保證原子或內存一般

例如,下29.8柵欄它指出了諸如:

如果存在原子 操作X和Y A釋放圍欄阿與 獲取圍欄乙同步時,上 一些原子兩種操作對象M,使得在X之前排序的A是 ,在B之前排序的Y是 ,並且Y讀取由X寫入的值 或由假設的 版本序列X中的任何一方寫入的值 如果它是 是發佈操作。

它使用這些術語atomic operationsatomic object。草案中定義了這樣的原子操作和方法,但是這僅僅意味着那些嗎? A 發佈圍欄聽起來像一個存儲圍欄。一個店圍欄不保證所有數據之前柵欄寫幾乎是無用的。類似於負載(獲取)柵欄和全柵欄。

所以,在C++ 0x中適當的柵欄的圍欄/巴里和措辭只是難以置信差,或者如所描述的被它們限制exremely /無用?


在C++方面,說我有這個現有的代碼(假設圍欄可作爲高層次的結構現在 - 而不是說在GCC使用__sync_synchronize):

Thread A: 
b = 9; 
store_fence(); 
a = 5; 

Thread B: 
if(a == 5) 
{ 
    load_fence(); 
    c = b; 
} 

假設一個, b,c的大小在平臺上具有原子拷貝。以上意味着c只會被分配9。請注意,當線程B看到a==5時,我們並不在乎,只是當它看到時,它也會看到b==9

什麼是C++ 0x中,保證同一關係的代碼?


答案:如果你讀過我選擇的答案,所有的評論,你會得到的情況的要點。 C++ 0x似乎強迫你使用帶有柵欄的原子,而普通的硬件柵欄不具備這個要求。在很多情況下,只要sizeof(atomic<T>) == sizeof(T)atomic<T>.is_lock_free() == true,這仍然可以用來取代併發算法。

不幸的是,is_lock_free不是一個constexpr。這將允許它在static_assert中使用。使用退化爲鎖的atomic<T>通常是一個糟糕的主意:與互斥體設計的算法相比,使用互斥鎖的原子算法會產生可怕的爭用問題。

+7

貧鈾已經流離失所的首選炮彈的材料。 C++ 0x草稿的過時副本證明是更密集的。 – 2011-04-05 05:01:53

+0

儘管那個人只有一個月左右的時間。 – 2011-04-05 05:09:08

+0

漢斯可能知道我們沒有的東西?有關C++ 11的** real **提案有望在下週發佈。 – 2011-04-05 05:15:52

回答

14

柵欄提供訂購所有數據。但是,爲了保證一個線程的fence操作一秒鐘可見,您需要對該標誌使用原子操作,否則您會遇到數據競爭。

std::atomic<bool> ready(false); 
int data=0; 

void thread_1() 
{ 
    data=42; 
    std::atomic_thread_fence(std::memory_order_release); 
    ready.store(true,std::memory_order_relaxed); 
} 

void thread_2() 
{ 
    if(ready.load(std::memory_order_relaxed)) 
    { 
     std::atomic_thread_fence(std::memory_order_acquire); 
     std::cout<<"data="<<data<<std::endl; 
    } 
} 

如果thread_2讀取readytrue,然後圍欄確保data可以安全地讀取,輸出將是data=42。如果ready被讀取爲false,則不能保證thread_1已發出適當的圍欄,因此線程2中的圍欄仍不能提供必要的訂購保證---如果thread_2中的if被省略,則訪問data會數據競賽和未定義的行爲,即使是圍牆。

說明:A std::atomic_thread_fence(std::memory_order_release)通常相當於一個商店圍欄,並且可能會像這樣實施。但是,一個處理器上的單個圍欄不能保證任何內存排序:您需要在第二個處理器上使用相應的圍欄,您需要知道,執行獲取圍欄時,釋放圍欄的效果對第二個處理器是可見的處理器。很顯然,如果CPU A發出獲取柵欄,然後5秒鐘後CPU B發佈發佈柵欄,那麼該發佈柵欄不能與獲取柵欄同步。除非您有一些方法來檢查在另一個CPU上是否已經發出圍欄,否則CPU A上的代碼無法分辨是在CPU B圍欄之前還是之後發出圍柵。

要求您使用檢查柵欄是否被看到的原子操作是數據競爭規則的結果:無法從多個線程訪問非原子變量而沒有排序關係,因此您不能使用非原子變量來檢查訂購關係。

當然可以使用更強大的機制,例如互斥鎖,但這會使單獨的柵欄毫無意義,因爲互斥鎖將提供柵欄。

寬鬆原子操作是在現代的CPU有可能只是普通的加載和存儲,雖然可能有額外的對準要求,以確保原子。寫入到使用特定處理器圍欄

代碼可以容易地改變要使用的C++ 0x圍欄,提供用於檢查同步(而不是那些用於訪問同步數據)的操作是原子的。現有的代碼可能很好地依賴於給定CPU上的簡單加載和存儲的原子性,但是爲了提供排序保證,轉換爲C++ 0x將需要對這些檢查使用原子操作。

+0

該標準的哪一部分闡明瞭排序是在*所有數據*而不僅僅是原子。這就是我要找的?你必須使用原子的要求(如你所指出的)意味着這些不是合適的圍欄。 – 2011-04-05 09:08:43

+1

該標準的相關部分是1.10,29.3和29.8。排序規則複雜,但如果A爲*測序-之前在相同的線程* B,和B *同步-與* C在另一個線程,和C是*測序-之前* d對第二線程,那麼A *在* D之前發生,即使它們是非原子操作,A和D也可以訪問相同的數據。給出的段落說明了B和C必須提供這種擔保。如果在非原子訪問之間沒有* happen-before *關係,那麼你有一個數據競爭。 – 2011-04-05 09:40:54

+0

這似乎是錯誤的(標準,而不是你的解釋)。硬件柵欄不需要原子操作來創建*發生之前*關係,柵欄本身就足夠了。 – 2011-04-05 09:46:32

2

我的理解是,它們是合適的圍欄。間接的證據是,畢竟,它們旨在映射到在實際硬件中找到的功能,並允許有效實施同步算法。正如你所說,只適用於某些特定值的柵欄是1.無用的,2.在當前硬件上找不到。

這就是說,AFAICS引用的部分描述了柵欄和原子操作之間的「同步」關係。有關這意味着什麼的定義,請參見1.10 多線程執行和數據競賽。同樣,AFAICS並不意味着柵欄只適用於原子對象,而是我懷疑它的含義是,雖然普通的加載和存儲可能以通常的方式(僅僅一個方向)通過獲取和釋放柵欄,但原子加載/商店可能不會。

Wrt。原子對象,我的理解是,在Linux支持的所有目標上,正確對齊的整數變量的sizeof()爲sizeof(* void)是原子的,因此Linux使用普通整數作爲同步變量(即Linux內核原子操作在正常的整數變量上)。 C++不想施加這樣的限制,因此是獨立的原子整數類型。此外,在C++中,對原子整數類型的操作意味着障礙,而在Linux內核中,所有障礙都是顯式的(這是顯而易見的,因爲沒有編譯器支持原子類型,這是必須做的)。

+0

我重讀部分1.10,這是完全不清楚我的排序保證是否適用於非原子值。幾乎所有的觀點都只表示「原子對象」。第13點提到非標量,前面的項目與關係有關,但我並不積極,這確實表明柵欄是作爲硬件柵欄工作的。 – 2011-04-05 09:16:33