信號量(Semaphore)是作業系統提供的一種「軟體層級」的同步工具,用於管理對共享資源的訪問。它本質上是一個整數變數,並搭配兩個原子性的操作:wait() 和 signal()。這使得它不僅可以實現互斥,還能用來處理資源共享和程序間通知等更廣泛的同步問題。
wait(S) 操作:
signal(S) 操作:
這兩個操作(wait() 和 signal())的原子性至關重要。這意味著當一個執行緒正在執行其中一個操作時,其他執行緒不能同時干擾它。這通常需要底層硬體的支援來確保。
根據信號量初始值的不同,信號量可以分為兩種類型:
二元信號量(Binary Semaphore):
計數信號量(Counting Semaphore):
前面版本使用了 while (S <= 0) 的忙等(busy-wait)迴圈,這在單核心系統上會浪費 CPU 資源。而改進版信號量則加入了等待佇列(queue),有效解決了這個問題。當資源不可用時,執行緒會被加入佇列並進入睡眠狀態(sleep()),讓出 CPU。當資源被釋放時,signal() 會從佇列中喚醒一個等待的執行緒(wakeup()),實現更高效的資源利用。
// 改進版 Semaphore 實作核心
wait(semaphore *S) {
S->value--; // 資源數減一
if (S->value < 0) {
add this process to S->list; // 將當前執行緒加入等待佇列
sleep(); // 進入睡眠狀態,讓出 CPU
}
}
signal(semaphore *S) {
S->value++; // 資源數加一
if (S->value <= 0) {
P = remove a process from S->list; // 從佇列中移除一個執行緒
wakeup(P); // 喚醒該執行緒
}
}
Monitor 是一種高階、語言層級的同步工具,旨在簡化並強化執行緒間的同步操作。它將共享資料、操作共享資料的函式,以及同步機制(如鎖和條件變數)封裝在一個單一的物件中。其核心優勢是內建了「互斥保護機制」,可以大幅減少因手動管理鎖而產生的常見錯誤。
手動使用信號量(Semaphore)或互斥鎖(Mutex)時,程式設計師很容易犯下以下錯誤,這些錯誤可能導致嚴重的同步問題,例如死鎖(deadlock)或資料不一致:
Monitor 的目的正是要解決這些問題。它將鎖的管理交由語言或編譯器自動處理,確保在執行 Monitor 內部程式碼時,永遠只有一個執行緒可以進入。
Monitor 通常由以下三個部分組成:
// Monitor 的邏輯結構範例
monitor MonitorName {
// 共享資料,只能由 Monitor 內部的函式存取
shared_data_type buffer[BUFFER_SIZE];
int count = 0;
// 條件變數,用於管理等待的執行緒
condition full, empty;
// 成員函式,自動受互斥鎖保護
void produce(item_type item) { // 自動加鎖
while (count == BUFFER_SIZE) {
full.wait(); // 如果緩衝區已滿,進入等待狀態
}
buffer[count++] = item;
empty.signal(); // 通知有空間可以取用
} // 自動解鎖
void consume(item_type &item) { // 自動加鎖
while (count == 0) {
empty.wait(); // 如果緩衝區為空,進入等待狀態
}
item = buffer[--count];
full.signal(); // 通知有空間可以生產
} // 自動解鎖
// 初始化函式
initialization() { ... }
}
當 Monitor 內部的一個執行緒(A)呼叫 x.signal() 來喚醒另一個正在等待的執行緒(B)時,只有一個執行緒能留在 Monitor 內執行。這時,就會產生一個關鍵的排程問題:A 和 B 誰該繼續執行?常見的解決策略有三種:
第一,Signal-and-Wait(Hoare-style):
第二,Signal-and-Continue(Mesa-style):
第三,Compromise(折衷方案):
存活性(Liveness)是指在一個並行系統中,所有執行緒(或程序)最終都能夠繼續執行,不會因為等待特定條件而無限期地停止。如果一個程式發生無限等待,它就違反了同步機制中的進度(Progress)或有限等待(Bounded Waiting)條件。兩種最常見的存活性問題是死結和優先權反轉。
死結是指兩個或多個執行緒彼此互相等待對方所持有的資源,導致所有執行緒都無法繼續執行。這就像一個僵局,沒有人願意讓步。
// 佔用且等待和循環等待
// P0
wait(S); // 持有資源S,等待資源Q
// P1
wait(Q); // 持有資源Q,等待資源S
// P0 等待 P1 釋放 Q,而 P1 等待 P0 釋放 S,形成一個封閉的循環,雙方都無法前進。
優先權反轉是指一個高優先權的執行緒被一個低優先權的執行緒間接拖延,導致其無法及時完成工作。這會嚴重影響即時系統(Real-time System)的效能和可靠性。
假設系統中有三個執行緒,優先權由低到高為:L < M < H。
解決優先權反轉最常見且有效的方案是優先權繼承 (Priority Inheritance)
當一個低優先權的執行緒(L)佔有高優先權執行緒(H)所需的資源時,它會暫時繼承 H 的高優先權。這讓 L 能夠在最短時間內執行完其臨界區內的程式碼,並盡快釋放資源。一旦資源被釋放,L 的優先權會恢復到原來的等級。這樣就能確保 H 不會被不必要的拖延,從而維持系統的存活性。