行程同步與臨界區間問題

測試並設定指令(test-and-set)

我們看到純軟體的鎖之所以脆弱,是因為普通的讀與寫既不原子、又可能被重排。硬體的解法,是提供一條單一的機器指令,把一個微小的「讀取兼寫入」當成一個不可分割的步驟來做。測試並設定就是經典的那一條。它在一個不可被打斷的動作裡,讀出某個記憶體位置的舊值、並把 true 寫進去,再把舊值回傳給你。名字很直白:它在同一瞬間測試(讀取)並設定(寫入),中間沒有讓另一顆核心能擠進來的縫隙。

用這一個原語,你幾乎能輕而易舉地造出一把鎖。維護一個共用布林值 locked,初始為 false。要取得鎖,就反覆做 old = test_and_set(locked),直到回傳的舊值是 false。第一條呼叫它的執行緒讀到 false(鎖是空的),同時寫入 true(現在被佔了),於是它回傳 false 並進入。之後每一條呼叫 test_and_set 的執行緒都會讀回 true(已被佔),於是持續繞圈。要釋放時,持有者只要寫 locked = false。因為這個「讀取兼寫入」是原子的,兩條執行緒不可能都看到 false——恰好一條獲勝。這就是自旋鎖的核心。

麻煩在於公平性與浪費。測試並設定鎖提供互斥,但本身並不保證有限等待:在爭用下,同一條幸運的執行緒可能一直贏得競賽,而另一條無限地自旋。而且等待中的執行緒是在忙碌等待——在一個緊湊迴圈裡空轉、燒著 CPU 週期卻什麼有用的事都沒做。真實的實作會緩和這點(例如「測試-測試並設定」會先用一次便宜的普通讀取查看旗標,只有在看起來空著時才嘗試那次原子寫入,以減少快取列的爭用),但根本的教訓不變:一條小小的原子指令就足以造出互斥,而一切更豐富的構造都建在它之上。

acquire(lock):while (test_and_set(lock) == true) { /* 自旋 */ } release(lock):lock = false。最先呼叫的那條取得舊值 false 並前進;其餘所有執行緒都看到 true,持續繞圈,直到持有者寫回 false。

一個原子的讀取兼寫入就能造出可用的鎖;落敗者自旋。

測試並設定本身只給互斥、不給有限等待,而且落敗者會忙碌等待。它是自旋鎖的積木,並非完整的公平鎖。

又称
TASTSLtest-and-set lock測試並設定