今天研究所上有發生些事...總結來說,研究所真的是一個很尷尬的時期,因為沒有薪資(畢竟不是勞工),但實際生活卻不像學生...
有時會面臨一些複雜的狀況。當一個人盡力擔負起責任,試圖解決問題時,最終卻發現自己也成了被指責的對象。
願意付出,結果卻事與願違的感受,或許只有親身經歷的人才能體會。
Peterson's Solution是一種只用軟體就能實現兩個 process 的互斥臨界區的演算法。它是 OS 同步中的經典演算法,儘管在現代硬體上不保證正確,但它非常適合用來:
Peterson's Solution的適用條件:僅限兩個 process(P0 與 P1),Process 之間會輪流進入 critical section。
// 共享兩個變數
int turn; // 輪到誰(0 或 1)
bool flag[2]; // flag[i] 表示 Pi 想要進入臨界區
// 演算法流程(Process Pi 的程式碼)
while (true) {
flag[i] = true; // 宣告自己要進入 CS
turn = j; // 禮讓對方先選
while (flag[j] && turn == j){} // 忍耐等待
// 🔐 Critical Section
...
flag[i] = false; // 我執行完畢,退出 CS
// 🧘 Remainder Section
}
// 模擬狀況
state 初始:flag[0] = flag[1] = false
假設P0想進入:
flag[0] = true
turn = 1
假設P1想進入:
flag[1] = true
turn = 0
while 條件:
P0: flag[1] && turn==1 → ❌ 不滿足(turn = 0)
P1: flag[0] && turn==0 → ✅ 卡住
➡ P0 可以進入
以三大同步條件來回顧Peterson’ s Solution(彼得森解法):
while
條件在多核心或多執行緒環境中,若多個行程或執行緒同時存取共享變數,就可能會產生race condition,導致資料錯誤或程式邏輯異常。雖然我們可以使用Peterson’s Solution等軟體層級的協議來實現互斥(mutual exclusion),這些方法在理論上可以滿足互斥、前進性、有限等待三大條件,但它們在現代多核心系統中可能會失效,原因包括:
也因此,在現代系統中,硬體層級的同步原語是實現安全互斥的必要條件。
在現代多核心系統中,為了效能:
這些都可能導致 多執行緒同步錯誤(尤其 race condition 難以重現)。而Memory Barrier 是一種特殊的 CPU 指令或編譯器指令,用來防止指令重排序(reordering),確保指令的執行順序與你寫的程式順序相同。
// Thread 1
while (!flag)
memory_barrier(); // 確保讀 flag 之前不會偷跑去讀 x
print x;
// Thread 2
x = 100;
memory_barrier(); // 確保 x = 100 寫入後,flag 才會設為 true
flag = true;
不同的硬體架構定義了不同的記憶體模型(memory consistency model),用來規範記憶體操作的順序性與可見性。
軟體實作(如 Peterson’s Solution)在多核心系統中可能失效。而硬體提供了不可中斷的原子操作,確保共享變數存取過程不被其他 process/thread 插入
// 定義 test-and-set
bool test_and_set(bool* target) {
bool rv = *target;
*target = true;
return rv;
}
// 使用 test-and-set:(這段操作是「原子」進行,兩個核心同時執行也不會競爭失敗)
bool lock = false;
while (test_and_set(&lock)) ; // busy-wait
// critical section
lock = false; // 解鎖
以三大同步條件來回顧test-and-set:
為何會出現 starvation(飢餓)? OS沒有FCFS的排隊機制,多個執行緒同時 busy-wait
// 定義 CAS:
int compare_and_swap(int* value, int expected, int new_value) {
int temp = *value;
if (*value == expected)
*value = new_value;
return temp;
}
// 使用 CAS:(這段操作是「原子」進行,兩個核心同時執行也不會競爭失敗)
int lock = 0;
while (compare_and_swap(&lock, 0, 1) != 0) ; // busy-wait
// critical section
lock = 0; // 解鎖
以三大同步條件來回顧CAS:
補充:為何test-and-set跟CAS都無法滿足bounded waiting:
兩者都採用 busy-wait 自旋,而且沒有先來先服務(FIFO)或排隊機制。執行緒的搶鎖成功機率可能受排程與 CPU 核心切換影響,導致某些執行緒長時間得不到進入機會。如果要滿足有限等待,需額外設計waiting 陣列實現有限等待,保證每個 process 最多等待 n-1 次就可以進入 critical section。
Mutex Locks是一種作業系統提供的「高階、易用、抽象」的同步工具,用於保護共享資源,確保在任何給定時間內,只有一個執行緒(或進程)可以訪問臨界區(Critical Section)。
互斥鎖的設計目的在於解決底層硬體原子操作(如 test-and-set
或 CAS
)不直觀、不易使用的問題。它將底層操作封裝起來,讓程式設計師可以更簡單地使用 acquire()
和 release()
兩個操作來管理共享資源。
acquire()
: 嘗試獲取鎖。如果鎖是可用的,就成功取得鎖並進入臨界區;如果鎖已被其他執行緒佔用,則呼叫此函式的執行緒會進入睡眠狀態(block),讓出 CPU,等待鎖被釋放。release()
: 釋放鎖。當執行緒完成對共享資源的操作後,呼叫此函式來釋放鎖,並喚醒一個正在等待的執行緒,使其可以繼續執行。這個過程就像你比喻的:「先去喝咖啡,等有人用完再叫我回來。」這種讓出 CPU 的等待方式,是 Mutex 的核心特徵,有效避免了 CPU 資源的浪費。
// 典型的 Mutex 使用流程
while (true) {
acquire_lock(); // 嘗試獲取鎖,若失敗則進入睡眠
// critical section // 只有一個執行緒能執行的區域
release_lock(); // 釋放鎖,讓其他等待的執行緒有機會
// remainder section // 非共享資源操作
}
Contention指的是當多個執行緒同時嘗試獲取同一把鎖的情況。這是理解 Mutex 與 Spinlock 差異的關鍵。
Spinlock是一種特殊的鎖機制。當執行緒嘗試獲取鎖但發現它被佔用時,它不會進入睡眠狀態,而是會進入一個忙等(busy-wait)迴圈,不斷重複檢查鎖的狀態,直到鎖可用為止。
一直站在門口盯著影印機,問『好了沒?好了沒?
比較項目 | 自旋鎖 (Spinlock) | 互斥鎖 (Mutex) |
---|---|---|
等待方式 | 忙等(busy-wait),持續消耗 CPU 週期。 | 阻塞(block),進入睡眠狀態並讓出 CPU。 |
效率特性 | 適合臨界區極短的場景,可以避免上下文切換的開銷。 | 適合臨界區較長的場景,可以避免浪費 CPU 資源。 |
實作方式 | 通常由底層硬體原子操作(如 CAS 或 test-and-set)加上一個忙等迴圈實現。 | 透過作業系統的系統呼叫(System Call)來實現,例如 pthread_mutex_lock() 。 |
資源使用 | 高,因其持續佔用 CPU。 | 低,因其讓出 CPU 給其他執行緒。 |
適用場景 | 多核心系統,且鎖被佔用的時間極短。 | 單核心系統或鎖被佔用時間較長的任何場景。 |
我們可以用三大同步條件來評估 Mutex 的特性: