行程同步與臨界區間問題

記憶體屏障(memory barrier)

如果說記憶體順序是 CPU 與編譯器「可以隨意洗牌並延遲你的讀寫」的自由,那麼記憶體屏障就是那道命令:「重排到此為止」。想像在繁忙的工坊地板上畫一條線:工人在線的兩側愛怎麼重新安排工作都行,但什麼都不准越過這條線。記憶體屏障(又叫柵欄/fence)正是指令流中這樣的一條線:它強制屏障之前的某些記憶體操作先完成並變得可見,然後屏障之後的任何記憶體操作才被允許開始。

屏障有幾種口味,但概念始終是把「重排原本會奪走的保證」重新補回來。寫入屏障確保較早的寫入先被刷出(變得可見),然後才輪到較晚的寫入。讀取屏障確保較早的讀取先完成,然後才輪到較晚的讀取。全屏障則把跨越它的一切都排好序。最有用的樣式是 acquire/release 配對:寫者端的 release 屏障保證它在「發布旗標」之前所寫的一切,都對之後看到那個旗標的人可見;讀者端的 acquire 屏障則保證一旦它看到旗標,就也看得到那些較早的寫入。這一對,正是讓「先設 data、再設 ready」/「等到 ready、再讀 data」這套交握安全無虞的關鍵。

實務上你很少手寫屏障。互斥鎖的取得與釋放、號誌的 wait 與 signal,以及一個好函式庫裡的原子操作,都已內含正確的屏障,這就是為什麼一般以鎖為基礎的程式碼直接就能正確運作。你只有在低階的無鎖程式設計時才會動用明確的屏障,而那充滿陷阱:少一道屏障所造成的錯誤,只在某些處理器、某些時序下才現身,測試時可能永遠不會出現。屏障也有代價——它阻止最佳化、可能讓管線停頓——所以要謹慎而精準地使用,而不是為了「安全」到處亂灑。

修正先前那套交握:生產者先寫 data=42,接著發出一道 release 屏障,再設 ready=true。消費者讀 ready,發出一道 acquire 屏障,再讀 data。如此一來,只要它看到 ready 為 true,就保證看得到 data 是 42——絕不會是過時的 0。

寫者端一道 release 屏障 + 讀者端一道 acquire 屏障,讓這次交接安全無虞。

少一道屏障是運算界最棘手的錯誤之一:在你機器上正確、在別台機器上錯誤、測試時又看不見。請優先使用內含屏障的函式庫鎖/原子操作,而非自己土法煉鋼。

又稱
fencememory fence記憶體柵欄屏障指令