Semaphore 與 Monitor 的同步原語互轉實作
Semaphore 與 Monitor 的同步原語互轉實作的重點整理。
同步原語互轉實作 (Semaphore \(\Leftrightarrow\) Monitor)
筆記摘要 本筆記整理 Semaphore (號誌) 與 Monitor (監視器) 的雙向轉換實作,證明兩者在同步能力上的等價性。
- Sem \(\to\) Mon:作業系統理論重點 (Hoare-style),需精密控制排隊與優先權。
- Mon \(\to\) Sem:現代程式實務 (Mesa-style),利用高階鎖實作計數器。
Part 1. 低階轉高階:用 Semaphore 實作 Monitor
場景設定 假設 OS 核心只提供 Semaphore,我們需要手動建構出一個支援 Condition Variable 的 monitor。
- 機制:Hoare-style (Signal-and-Wait)。
- 特點:發訊號者 (Signaler) 暫停,讓被喚醒者 (Signalee) 立即執行。
1. 變數宣告 (The Monitor Structure)
我們需要一組全域 Semaphore 來模擬 Monitor 的「大門」與「緊急暫停區」。
| 變數名稱 | 類型 | 初始值 | 用途說明 |
|---|---|---|---|
| \(mutex\) | sem |
1 | 大門鎖:確保 Monitor 的互斥性 (Mutual Exclusion)。 |
| \(next\) | sem |
0 | 緊急暫停區:當行程 signal() 別人時,自己需在此暫停。 |
| \(next\_count\) | int |
0 | 記錄目前卡在 \(next\) 上的人數。 |
| \(x\_sem\) | sem |
0 | 條件排隊區:行程呼叫 x.wait() 時在此卡住。 |
| \(x\_count\) | int |
0 | 記錄目前卡在 \(x\_sem\) 上的人數。 |
2. 外部程序控制 (Entry & Exit Protocol)
為了確保互斥,任何外部函式 \(F\) 的前後都必須加上控制碼。
// 【進入 Monitor】
wait(mutex);
// ... 函式 F 的主體內容 ...
// 【離開 Monitor】
// 優先檢查:有沒有人因為發 signal 而暫停在 next?
if (next_count > 0)
signal(next); // 有的話,接力棒交給他 (優先於門外排隊的人)
else
signal(mutex); // 沒有的話,才開大門
3. 條件變數操作 (Condition Variables)
A. x.wait() 的實作
邏輯:釋放目前的鎖 \(\to\) 進入睡眠 \(\to\) 醒來後恢復執行。
x_count++; // 1. 登記:我要去睡了
// 2. 釋放鎖 (邏輯同「離開 Monitor」)
if (next_count > 0)
signal(next);
else
signal(mutex);
wait(x_sem); // 3. 【睡眠】卡在這裡,直到被 signal
x_count--; // 4. 醒來了,撤銷登記
B. x.signal() 的實作
邏輯:喚醒等待者 \(\to\) 自己暫停 (Hoare-style 核心)。
if (x_count > 0) { // 只有當有人在等 x 時才做 (Monitor 的 signal 對空無效)
next_count++; // 1. 登記:我要去 next 暫停了
signal(x_sem); // 2. 喚醒一個在 x 等待的人
wait(next); // 3. 【暫停】自己在 next 睡著 (讓對方先跑)
next_count--; // 4. 重新獲得執行權,撤銷登記
}
4. 邏輯流程圖 (Mermaid)
graph TD
subgraph Signal Process
Start([呼叫 x.signal]) --> Check{x_count > 0 ?}
Check -- No (沒人等) --> End([繼續執行])
Check -- Yes --> IncNext[next_count++]
IncNext --> WakeX[signal x_sem<br>喚醒等待者]
WakeX --> SleepSelf[wait next<br>自己暫停]
SleepSelf --> DecNext[next_count--]
DecNext --> End
end
Part 2. 高階轉低階:用 Monitor 實作 Semaphore
場景設定 利用現代語言 (如 C++, Java) 的 Monitor 機制 (Mutex + CV),重新發明一個 Counting Semaphore。
- 機制:通常為 Mesa-style (Signal-and-Continue)。
C++ 實作碼
#include <mutex>
#include <condition_variable>
class Semaphore {
private:
std::mutex mtx_; // Monitor 鎖
std::condition_variable cv_; // 條件變數
int count_; // 模擬號誌的資源計數
public:
explicit Semaphore(int initial = 0) : count_(initial) {}
// P Operation (Wait)
void wait() {
std::unique_lock<std::mutex> lk(mtx_); // 進入 Monitor
// 條件檢查:若資源不足 (count <= 0) 則等待
// 注意:Mesa style 醒來後必須由 while 迴圈重新檢查條件
cv_.wait(lk, [&]{ return count_ > 0; });
--count_; // 消耗資源
}
// V Operation (Signal)
void signal() {
std::lock_guard<std::mutex> lk(mtx_); // 進入 Monitor
++count_; // 釋放資源
cv_.notify_one(); // 喚醒一個等待者
}
};
Part 3. 綜合比較 (Exam Focus)
| 比較維度 | 用 Semaphore 實作 Monitor | 用 Monitor 實作 Semaphore |
|---|---|---|
| 主要用途 | 理論證明、OS 核心實作 | 應用程式開發、同步工具封裝 |
| 互斥鎖管理 | 手動:需判斷釋放 next 還是 mutex (Baton Passing)。 |
自動:由 lock_guard 或 synchronized 關鍵字處理。 |
| Signal 行為 | Hoare (Signal-and-Wait) 發訊號者暫停,交出 CPU。 |
Mesa (Signal-and-Continue) 發訊號者繼續,被喚醒者排隊。 |
| Signal 特性 | 無記憶性 (Memoryless) 沒人等訊號就消失。 |
有記憶性 (Memory) 訊號會轉為 count++ 存起來。 |
| 程式複雜度 | 高 (容易 Deadlock) | 低 (邏輯直觀) |