Peterson's Solution
Peterson's Solution的重點整理。
演算法 1:使用 turn 變數 (Strict Alternation)
此方法嘗試使用一個共享的「輪流」變數來強制互斥。
1. 程式碼邏輯 (Process \(P_i\))
-
共享變數:
int turn;(初始值為 \(i\) 或 \(j\)) -
邏輯:
do {
// Entry Section
while (turn != i); // 忙碌等待 (Busy waiting)
/* critical section */
// Exit Section
turn = j; // 將權限交給對方
/* remainder section */
} while (true);
2. 正確性分析
| 條件 | 結果 | 證明 / 說明 |
|---|---|---|
| Mutual Exclusion | ✅ 滿足 | 由於 turn 在任何時間點只能儲存 \(i\) 或 \(j\) 其中一個值,因此最多只有一個處理程序能夠通過 while 迴圈進入 CS。 |
| Progress | ❌ 違反 | 如果 \(P_i\) 想進入 CS 但此時 \(turn == j\),即使 \(P_j\) 正在剩餘區域 (Remainder Section) 執行而不打算進入 CS,\(P_i\) 也必須被迫等待。這違反了前進性(由不想進入 CS 的行程阻擋了想進入的行程),更簡單的來說就是會有嚴格的輪流限制也就是 \(P_i\) 進入 CS 之後把 \(turn==j\) 之後 \(P_j\) 其實並無意願,但是這時候在 remainder section 的 \(P_i\) 又想要進入了。 |
| Bounded Waiting | ✅ 滿足 | 雖然滿足,但因為前進性已違反,此條件在此處討論意義不大。 |
演算法 2:使用 flag 旗標
此方法嘗試讓處理程序「宣布」它們進入臨界區域的意願。
1. 程式碼邏輯 (Process \(P_i\))
-
共享變數:
boolean flag[2];(初始值皆為false) -
邏輯:
do {
// Entry Section
flag[i] = true; // 宣示意外:我想進入
while (flag[j]); // 檢查對方是否也想進入
/* critical section */
// Exit Section
flag[i] = false; // 離開 CS,取消意願
/* remainder section */
} while (true);
2. 正確性分析
| 條件 | 結果 | 證明 / 說明 |
|---|---|---|
| Mutual Exclusion | ✅ 滿足 | 若 \(P_i\) 進入 CS,則 \(flag[j]\) 必為 false。若兩者同時嘗試進入,其中一個會先看到對方的旗標為 true 而被擋住。 |
| Progress | ❌ 違反 | Deadlock 風險。如果 \(P_i\) 和 \(P_j\) 幾乎同時執行到 flag[i] = true 和 flag[j] = true,接著兩者都會卡在 while 迴圈中互相等待,無人能進入 CS。 |
| Bounded Waiting | ❌ 違反 | 如果產生死結,則兩者都無限期等待。 |
演算法 3:Peterson’s Solution (彼得森解法)
Peterson’s Solution 結合了上述兩種方法的優點:
-
使用
flag宣示意願。 -
使用
turn作為發生爭用時的決策者 (tie-breaker)。
1. 程式碼邏輯 (Process \(P_i\))
-
共享變數:
int turn;和boolean flag[2];(初始值:flag皆為false) -
邏輯:
while (true) {
// Entry Section
flag[i] = true; // 1. 我想進入
turn = j; // 2. 禮讓給對方 (若對方也想進,對方優先)
// 檢查條件:對方想進 (flag[j]) 且 輪到對方 (turn == j)
while (flag[j] && turn == j);
/* CRITICAL SECTION */
// Exit Section
flag[i] = false; // 我不想進了
/* REMAINDER SECTION */
}
2. 正確性分析
Peterson’s Solution 滿足所有臨界區域設計的三個必要條件:
A. Mutual Exclusion: ✅ 滿足
-
證明:若 \(P_i\) 和 \(P_j\) 同時位於 CS,則必須同時滿足
flag[i] == true,flag[j] == true。 -
但要通過
while迴圈,turn必須分別不等於 \(j\) (對 \(P_i\) 而言) 和不等於 \(i\) (對 \(P_j\) 而言)。 -
然而
turn變數在同一時刻只能是 \(i\) 或 \(j\),不可能同時滿足turn == i和turn == j。因此互斥成立。
B. Progress: ✅ 滿足
-
證明:若 \(P_i\) 想進入 CS,它只會被 \(P_j\) 阻擋。阻擋條件是 \(P_j\) 也想進
(flag[j] == true)且 \(P_j\) 擁有優先權(turn == j)。 -
如果 \(P_j\) 不想進 (在 R.S.),則
flag[j]為false,\(P_i\) 直接進入。 -
如果 \(P_j\) 也想進,當它執行
turn = i時 (禮讓),如果 \(P_i\) 已經在等待,則turn變為 \(i\), \(P_i\) 的等待條件被打破,得以進入。這保證了不會發生死結。
C. Bounded Waiting: ✅ 滿足
-
證明:如果 \(P_i\) 正在等待,代表
flag[j] == true且turn == j。 -
\(P_j\) 進入 CS 並完成後,會將
flag[j]設為false,這時 \(P_i\) 可以進入。 -
即便 \(P_j\) 立刻再次想進入,它會執行
turn = i。因為 \(P_j\) 是最後一個寫入turn的人,這次turn會變成 \(i\),導致 \(P_j\) 必須等待,\(P_i\) 優先進入。 -
因此 \(P_i\) 最多等待 \(P_j\) 進入一次,即滿足有限等待。
-
更簡單的說就是因為最後移行的
turn=j或是turn=i的緣故,所以結束過後會把這個執行權利交給對方,也就是說在下一次他們會等一次而已。
⚠️ 現代架構下的限制 (Modern Architecture Limitations)
Peterson’s Solution 在現代電腦上可能失效
儘管 Peterson’s Solution 在理論上正確,但在現代處理器與編譯器上不保證能正確運作。
原因:重新排序 (Reordering)
-
問題:現代處理器 (Processors) 和編譯器 (Compilers) 為了效能最佳化,可能會對沒有資料相依性 (Data Dependency) 的讀寫操作進行指令重新排序。
-
後果:在 Peterson’s Solution 中,Entry Section 的兩個操作:
-
flag[i] = true; -
turn = j;
- 這兩者對處理器來說沒有直接相依,可能會被對調執行順序。若發生重排,可能導致兩個程序都尚未設定旗標就先檢查對方,進而同時進入 CS,導致 互斥性 (Mutual Exclusion) 被違反。
-
解決方案
純軟體解法(如 Peterson’s)在現代系統已不夠可靠,通常需搭配硬體支援或高階 API:
-
硬體指令:
TestAndSet,CompareAndSwap (CAS),Memory Barriers (Fences). -
高階軟體 API:
Mutex Locks,Semaphores,Monitors.