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++並沒有對memory order進行約束。
gcc編譯的結果:
明顯可以看到在中間加入了mfence指令,防止前後兩條指令執行亂序。
從編譯情況來看gcc正確理解了程序的意圖,而clang++貌似理解錯了。
最後更新:2017-08-25 15:32:21
上一篇:
一起來學ES —— Bulk剖析
下一篇:
百度權重值是什麼意思?看完秒懂!
Git服務器Gitosis安裝設置
為何機器學習識別聲音還做不到像識別圖片那麼容易?
《配置管理最佳實踐》——2.3 構建工程的核心概念
微服務——程序員的自我修養
一個fprintf的簡單封裝實例(vsprintf,va_start(),+va_arg(),+va_end()可變參數列表)
如何利用多核CPU來加速你的Linux命令 — awk, sed, bzip2, grep, wc等
找不到完美數據科學家?你還可以組建一支數據科學夢之隊
struts標簽格式化時間<s:date name="neowaveBloodPressure.monitorTime" format="yyyy-MM-dd HH:mm:ss&q
讓網站鏈接樣式千奇百怪
手把手教你如何在ECS上部署Oracle RAC