閱讀324 返回首頁    go 阿裏雲 go 技術社區[雲棲]


C++11 memory order

C++11引入了atomic和memory order支持,使得寫可移植的無鎖數據結構成為可能。

其中memory order支持兩種形式的API,一種是在操作一個atomic變量時指定memory order,另外一種是單獨指定memory order的atomic_thread_fence()函數調用.

1. memory order主要有以下幾種:

  • memory_order_relaxed
    隻提供對單個atomic變量的原子讀/寫,不和前後語句有任何memory order的約束關係。這種情況往往使用於普通計數器,它甚至不能用來做引用計數器。因為引用計數器涉及對象析構,因為缺少內存柵欄作用,這可能導致別的CPU看不到對象內容最新數據而產生錯誤的析構行為。
  • memory_order_consume
    程序可以說明哪些變量有依賴關係,從而隻需要同步這些變量的內存。
    類似於memory_order_acquire,但是隻對有依賴關係的內存。意思是別的CPU執行了memory_order_release操作,而其他依賴於這個atomic變量的內存會被執行memory_order_consume的CPU看到。這個操作是C++特有的,x86也不支持這種類型的memory order,不清楚其他種類的cpu是否支持,這還涉及到編譯器是否支持這種細粒度控製,也許它直接按memory_order_acquire來處理。

  • memory_order_acquire
    執行memory_order_acquire的cpu,可以看到別的cpu執行memory_order_release為止的語句對內存的修改。執行memory_order_acquire這條指令猶如一道柵欄,這條指令沒執行完之前,後續的訪問內存的指令都不能執行,這包括讀和寫。

  • memory_order_release
    執行memory_order_release的cpu,在這條指令執行前的對內存的讀寫指令都執行完畢,這條語句之後的對內存的修改指令不能超越這條指令優先執行。這也象一道柵欄。
    在這之後,別的cpu執行memory_order_acquire,都可以看到這個cpu所做的memory修改。

  • memory_order_acq_rel
    是memory_order_acquire和memory_order_release的合並,這條語句前後的語句都不能被reorder。

  • memory_order_seq_cst
    這是比memory_order_acq_rel更加嚴格的順序保證,memory_order_seq_cst執行完畢後,所有其cpu都是確保可以看到之前修改的最新數據的。如果前麵的幾個memory order模式允許有緩衝存在的話,memory_order_seq_cst指令執行後則保證真正寫入內存。一個普通的讀就可以看到由memory_order_seq_cst修改的數據,而memory_order_acquire則需要由memory_order_release配合才能看到,否則什麼時候一個普通的load能看到memory_order_release修改的數據是不保證的。

2. x86的memory order

x86的memory order是一種strong memory order,它保證:

  • LoadLoad是順序的
    一個cpu上前後兩條load指令是順序執行的,前麵一條沒執行完畢,後麵一條不能執行

  • StoreStore是順序的
    一個cpu上前後兩條store指令是順序執行的,前麵一條沒執行完畢,後麵一條不能執行

  • LoadStore
    一個cpu上前麵一條是Load指令,這條指令沒執行完畢,後麵一條store不能執行

x86不保證StoreLoad的順序,一條Store指令在前,後麵一條不相關的load指令可以先執行。因為這個順序的不保證,導致Peterson lock實際上需要使用mfence指令才能在x86上實現。

x86上很多原子操作需要使用lock前綴或者隱含lock語義,例如xchg指令。這個lock語義是上麵memory_order_seq_cst的語義,是一個full memory barrier。相對來說在x86上的memory order 比較容易使用,但是性能有所損失,例如上麵的LoadLoad是順序執行的,但是如果第一個Load因為cache不命中,就引起從內存Load而導致的延遲,雖然第二個Load是可以cache命中的,但是因為第一個Load的delay,影響到第二個Load的執行,繼而導致後續運算都delay。

3.實踐中碰到編譯器的bug

考慮以下代碼,是用來防止x86上的StoreLoad的reorder問題。

#include<atomic>                                                                
using namespace std;

atomic_int a;
int j;

int func()
{
  int n;

  a.store(1,memory_order_acquire);
  n = j;
  return n;
} 

程序想先給a賦值1,然後讀變量j的值。如果不強加memory order,則讀j的指令可能會被cpu先執行,

這是用clang++編譯後的結果:

clang.png

可以看到,clang++並沒有對memory order進行約束。

gcc編譯的結果:

gcc.png

明顯可以看到在中間加入了mfence指令,防止前後兩條指令執行亂序。

從編譯情況來看gcc正確理解了程序的意圖,而clang++貌似理解錯了。

最後更新:2017-08-25 15:32:21

  上一篇:go  一起來學ES —— Bulk剖析
  下一篇:go  百度權重值是什麼意思?看完秒懂!