有人可以簡單解釋一下如何使用 Threading.MemoryBarrier 在 .Net 中實現“全柵欄”嗎?
我很清楚 MemoryBarrier 的用法,但不清楚執行時幕後發生的事情。誰能很好地解釋發生了什麼?
在一個非常強大的記憶體模型中,發出柵欄指令是不必要的。所有記憶體訪問都將按順序執行,所有儲存都將是全域可見的。
需要記憶體柵欄是因為目前的常見架構不提供強大的記憶體模型- 例如,x86/x64 可以相對於寫入重新排序讀取。(更詳盡的來源是*“英特爾® 64 和 IA-32 架構軟體開發人員手冊,8.2.2 P6 和更多最新處理器系列中的記憶體訂購”*)。舉個例子, Dekker 的算法在沒有柵欄的 x86/x64 上會失敗。
即使 JIT 生成的機器程式碼在其中仔細放置了帶有記憶體載入和儲存的指令,如果 CPU 然後重新排序這些載入和儲存,它的努力也是無用的——它可以這樣做,只要為目前保持順序一致性的錯覺。上下文/執行緒。
過於簡單化的風險:它可能有助於將指令流產生的負載和儲存視覺化為一群狂暴的野生動物。當它們穿過一座狹窄的橋(你的 CPU)時,你永遠無法確定動物的順序,因為它們中的一些會更慢,一些更快,一些超越,一些落後。如果在開始時 - 當您發出機器程式碼時 - 通過在它們之間放置無限長的柵欄將它們分成組,您至少可以確保 A 組在 B 組之前。
柵欄確保讀取和寫入的順序。措辭並不准確,但是:
- 儲存柵欄“等待”所有未完成的儲存(寫入)操作完成,但不影響載入。
- 載入柵欄“等待”所有未完成的載入(讀取)操作完成,但不影響儲存。
- 一個完整的柵欄“等待”所有儲存和載入操作完成。它的效果是在柵欄之前的讀取和寫入將在“柵欄的另一側”(比柵欄晚)上的寫入和載入之前執行。
JIT 發出的完整圍欄取決於(CPU)架構和它提供的記憶體排序保證。由於 JIT 確切地知道它執行在什麼架構上,它可以發出正確的指令。
在我的 x64 機器上,使用 .NET 4.0 RC,它恰好是一個
lock or.int a = 0; 00000000 sub rsp,28h Thread.MemoryBarrier(); 00000004 lock or dword ptr [rsp],0 Console.WriteLine(a); 00000009 mov ecx,1 0000000e call FFFFFFFFEFB45AB0 00000013 nop 00000014 add rsp,28h 00000018 ret英特爾® 64 和 IA-32 架構軟體開發人員手冊第 8.1.2 章:
- “…鎖定操作序列化所有未完成的載入和儲存操作(即等待它們完成)。” … “鎖定操作相對於所有其他記憶體操作和所有外部可見事件是原子的。只有取指和頁表訪問才能傳遞鎖定指令。鎖定指令可用於同步一個處理器寫入和另一個處理器讀取的數據。”
- 記憶體排序指令解決了這一特定需求。
MFENCE在上述情況下可以用作完全屏障(至少在理論上 - 一方面,鎖定操作可能更快,另一方面它可能導致不同的行為)。MFENCE及其朋友可以在*第 8.2.5 章“加強或削弱記憶體排序模型”*中找到。還有更多方法可以序列化儲存和載入,儘管它們不切實際或比上述方法慢:
- 在第 8.3 章中,您可以找到完整的序列化指令,例如
CPUID. 這些序列化指令流也是如此:“沒有任何東西可以傳遞序列化指令,而序列化指令不能傳遞任何其他指令(讀、寫、取指令或 I/O)”。- 如果您將記憶體設置為強非記憶體 (UC),它將為您提供強大的記憶體模型:不允許推測性或亂序訪問,所有訪問都將出現在匯流排上,因此無需發出指令。:) 當然,這會比平時慢一點。
…
所以這取決於。如果有一台具有強排序保證的電腦,JIT 可能不會發出任何內容。
IA64 和其他架構有自己的記憶體模型——因此保證了記憶體排序(或缺少它們)——以及他們自己的指令/方法來處理記憶體儲存/載入排序。