a 和 b 初始化都爲 0。 a 在 CPU1 的 cache 中, b 在 CPU0 的 cache 中。
CPU 0 運行如下的代碼:
a = 1;
mfence;
b = 1;
CPU1 運行如下的代碼:
while (b != 1);
assert (a == 1);
斷言必定能成功嗎? 讓咱們若是如下的狀況發生:
1. CPU0 運行 a = 1, 但是發現 a 不在 cache 中。它就發出 "read invalidate" 消息,並且把 a 的值存在 store buffer 中;
2。安全
CPU1 運行 b != 1 的比較,讀取 b 的值,結果不在 cache 中,因爲它不想改寫,因此僅僅發一個 "read" 消息出去。
3. CPU0 運行 mfence, 將 Store Buffer 中的項全部標記,而後運行 b = 1, 結果發現 Store Buffer 中有標記的項,因此就把 b 的值也記錄在 Store Buffer,這些項是未標記的。
4. CPU1 收到 "read invalidate" 消息,它把這個消息存入 Invalidate Queue 中。並做出迴應;
5. CPU0 收到迴應,把 a 的值寫入 Cache line, 把 b 的值也寫入 cache line;
6. CPU0 收到 "read" 消息,並用出迴應;
7. CPU1 收到迴應,退出循環,此時讀取 a 的值,由於 Invalidate 消息還在隊列中。此時它會以爲 Cache line 中 a 的值有效,但事實上爲舊值,因此斷言錯誤。
很是明顯。兵敗在了 Invalidate Queue 上。那可不可以向 Store Buffer 同樣。弄一個屏障在讀取 Cache line 時做一下檢查呢?
假設你能想到這裏,說明遇上了設計 CPU 的那些傢伙。對,這裏也可以用一個 mfence.
代碼就成了這個樣子
while (b != 1);
mfence;
assert(a == 1);
這樣當 while (b != 1) 退出循環以後,遇到了 mfence, 它就必須停下來把 Message Queue 中的因此消息應用到 Cache line 中,此時就會發現 a 的 cache line 失效。當再進行讀取 a 時,就會發消息給
CPU0 。進而獲得正確的結果。架構
事實上 X86 提供細粒度的指令 lfence (讀屏障), sfence (寫屏障)。 mfence(讀寫屏障)。spa
lfence: 該指令把當前invalidate queue 裏的全部項標記。當 load 動做發生時,假設隊列中有標記項。那麼 CPU 必須把 Message Queue 中存在的消息全部應用以後,纔會運行後面的指令。設計
這樣讀取就變得安全;
sfence: 該指令把標記 Store Buffer 中因此已存在的寫入記錄項。當下次再有寫入操做時,即便命中也不會直接應用到 cache 中。而是記錄到 Store Buffer 中。
mfence: 兼備以上兩條指令的做用。
另外一些其餘指令具有這種做用,通常如帶 lock 前綴的指令等。隊列
因此普通狀況下內存屏障需要成對使用。
最終快要完了。 內存