皮特森解法(Peterson's solution)
/ PEE-ter-son /
在硬體還沒給我們原子指令之前,人們問了一個純粹的謎題:兩條執行緒能不能只靠對共用變數的普通讀寫,就協調出對臨界區間的存取?皮特森解法(由 Gary Peterson 於 1981 年發表)就是針對兩條執行緒、著名而優雅的答案。它只用兩個共用變數加上一個禮讓的瞬間,就可被證明同時滿足三項臨界區間要求。教它,不是因為你該把它用在產品裡,而是因為它清晰地展示了那三項要求到底在要求什麼。
訣竅是把「意圖旗標」和一個「turn 變數」結合起來。每條執行緒 i 先設 flag[i] = true 宣告「我想進」,再設 turn = j 禮貌地把第一個機會讓給對方。接著它持續等待,只要對方也想進、而且現在輪到對方:while (flag[j] && turn == j) 就自旋。最後設 turn 的那一方在平手中落敗、要等;另一方則前進。離開時,執行緒清除 flag[i] = false。精妙之處正在於這個結合:只靠旗標會死結(兩邊都堅持),只靠 turn 會過不了進展(嚴格輪流),但合起來時,旗標說「我有興趣」、turn 負責打破平手,於是一次給齊了互斥、進展與有限等待。
這裡有個關鍵且誠實的提醒:皮特森解法只在一台理想化的機器上才正確——在那台機器上,記憶體操作按程式順序發生,並立刻對其他核心可見。真實的 CPU 與編譯器為了速度會重排讀寫,所以在現代多核心硬體上,除非你插入記憶體屏障來禁止重排,否則皮特森演算法可能失效。因此它的實務教訓有兩層:純軟體的互斥原則上是可行的;而我們在實務上之所以仍訴諸硬體原子指令,正是因為它們繞開了那片讓純軟體解法如此脆弱的記憶體順序雷區。
兩條執行緒都想進。執行緒 0 設 flag[0]=true、turn=1。執行緒 1 設 flag[1]=true、turn=0。後寫 turn 的那一方看到存的是自己的代號,於是等待;另一方看到 turn 對自己有利,便進入。兩者永不相撞,且誰也不會等超過一輪。
旗標 = 「我想進」;turn = 禮貌的平手判定。合起來:三項要求全到。
在會重排的真實硬體上,皮特森解法需要記憶體屏障才正確。它是教學工具,而非正式產品程式碼;實務上請用硬體原子指令或函式庫提供的鎖。