自旋鎖與互斥鎖的差異:等待方式、CPU 使用、持有時間、核心中的用法比較

自旋鎖與互斥鎖都是同一時間只讓 1 個執行緒進入臨界區段的鎖,但等待鎖釋放的方式不同。自旋鎖以在迴圈中持續檢查鎖變數的忙碌等待方式等待,因此等待期間也持續占用 CPU,但鎖一釋放就能立即取得。互斥鎖讓等待的執行緒進入睡眠並讓出 CPU,在鎖釋放時由 OS 喚醒。持有時間極短,或在無法睡眠的中斷處理常式中使用自旋鎖;持有時間長,或持有期間可能睡眠時使用互斥鎖。

項目自旋鎖互斥鎖
等待方式在迴圈中持續檢查變數,直到鎖釋放(忙碌等待)若未釋放就睡眠等待,釋放時被喚醒
等待期間的 CPU持續占用讓出。期間其他執行緒可以使用 CPU
等待的開銷沒有內文切換,鎖一釋放就能立即取得睡眠與喚醒時,需要系統呼叫與內文切換的開銷
適合的持有時間極短的時間(比內文切換所需的時間更短)長時間。持有期間可能睡眠時,例如等待 I/O
持有期間睡眠不可以。等待的一方會持續占用 CPU可以
只有 1 個核心時等待期間持有鎖的執行緒無法執行,自旋也不會讓鎖釋放。在核心中會停止搶占後使用可以正常使用
在核心中的用法在中斷處理常式這類無法睡眠的場合(中斷內文)也能使用。與中斷共用資料時,先禁止中斷再取得可能會睡眠,因此只能在行程內文中使用,無法在中斷處理常式中使用
優先權反轉持有鎖的執行緒被暫停時,等待的一方會持續自旋。核心中以在持有期間禁止搶占來防止可用具有優先權繼承的互斥鎖因應
實作範例以原子指令(test-and-set 或 compare-and-swap)改寫變數。平時以一般讀取等待的 test-and-test-and-set 可減少核心之間的通訊Linux 的 futex 在沒有競爭時只以使用者空間的原子指令上鎖,只在發生競爭時才請核心讓執行緒睡眠。也有先自旋一下再睡眠的實作

數值為具代表性的大致參考值,會因世代或產品而異。

適用於什麼情況

自旋鎖
在多核心上,持有鎖的時間極短(比內文切換的開銷更短)時。中斷處理常式等不允許睡眠的核心內處理。
互斥鎖
持有鎖的時間長時,或持有期間可能因 I/O 或記憶體配置等而睡眠時。使用者空間應用程式中一般的互斥控制。
搭配使用
許多互斥鎖的實作會結合兩者的優點,例如沒有競爭時不睡眠、只以原子指令取得,無法取得時先自旋一下再睡眠。

常見問題

自旋鎖會浪費 CPU,為什麼還要使用?

因為持有鎖的時間若極短,稍微自旋等待會比為了睡眠與喚醒所需的內文切換開銷更快。此外,在核心的中斷處理常式這類不允許睡眠的場合,只能使用自旋鎖。

為什麼中斷處理常式不能使用互斥鎖?

互斥鎖在無法取得鎖時會讓等待的執行緒睡眠,但中斷處理常式並不是以特定執行緒的身分運作,無法睡眠後再被喚醒。與中斷處理常式共用資料時,要將自旋鎖與禁止中斷搭配使用。

什麼是 futex?

Linux 中用來快速實作互斥鎖等的機制。沒有競爭時,只需在使用者空間以原子指令改寫鎖變數,只有在發生競爭而必須等待時,才透過系統呼叫請核心讓執行緒睡眠。在大多數鎖都不會發生競爭的情況下,可省去系統呼叫的開銷。

在只有 1 個核心的系統中使用自旋鎖會怎樣?

即使在使用者空間自旋等待,持有鎖的執行緒也無法在該核心上執行,因此在自己的時間片段用完之前,鎖都不會釋放。因此,在只有 1 個核心之系統的核心中,會以禁止搶占或中斷來代替自旋鎖進行互斥控制。

理解度檢測

關於自旋鎖(spinlock),下列說明何者正確?

  1. A在鎖被釋放之前,讓執行緒休眠並由作業系統喚醒的鎖
  2. B在鎖被釋放之前,以迴圈持續檢查鎖的變數來等待的鎖
  3. C旋轉磁碟以保護資料的機制
查看答案與解析

正確答案:B. 在鎖被釋放之前,以迴圈持續檢查鎖的變數來等待的鎖

自旋鎖會以原子指令嘗試取得鎖,若取不到就立刻反覆再試。由於沒有呼叫作業系統來休眠與喚醒的額外負擔,在持有鎖的時間極短時運作很快。常用於作業系統核心內,或與中斷處理常式之間的互斥。另一方面,它的缺點是等待期間仍會持續占用CPU。

下列何者是最不適合使用自旋鎖的情境?

  1. A持有鎖的時間很長,等待的執行緒白白持續占用CPU的情境
  2. B持有鎖的時間只有數十奈秒,非常短的情境
  3. C在作業系統核心內,無法休眠的處理情境
查看答案與解析

正確答案:A. 持有鎖的時間很長,等待的執行緒白白持續占用CPU的情境

以自旋鎖等待的執行緒,在鎖被釋放之前會持續使用CPU。若持有鎖的時間很長,那段CPU時間就全部浪費,也會妨礙其他執行緒的執行。尤其只有一個核心時,除非持有鎖的執行緒被執行,否則鎖不會被釋放,所以等待會持續到時間配額用完為止。長時間的等待應使用休眠等待的互斥鎖,也有先自旋一小段時間再休眠的方式。

在自旋鎖中,不直接用原子指令去改寫鎖的變數,而是平時以一般的讀取等待鎖被釋放,只有看到鎖空出來時才使用原子指令(test-and-test-and-set),其理由下列何者正確?

  1. A因為一般的讀取比原子指令更準確
  2. B因為原子指令在一個程式中只能使用一次
  3. C因為只是讀取的話可以在自己的快取內等待,能大幅減少快取列在核心之間來回移動的通訊
查看答案與解析

正確答案:C. 因為只是讀取的話可以在自己的快取內等待,能大幅減少快取列在核心之間來回移動的通訊

原子指令伴隨寫入,所以等待的執行緒若重複執行,每次都會以M狀態爭奪快取列,並持續使其他核心的副本失效。若是一般的讀取,各核心只需持續在自己的快取內讀取S狀態的副本,不會產生通訊。只有在鎖被釋放而值改變時,才以原子指令去取得。

  1. 比較互斥鎖(休眠等待的鎖)與自旋鎖,下列說明何者正確?
  2. 關於RTOS中發生的優先順序反轉,下列說明何者正確?
  3. 用來防止優先順序反轉的「優先順序繼承」,其概念下列何者正確?

前往多核心與同步的練習題

相關名詞的解說

自旋鎖(Spinlock)
以迴圈持續檢查鎖變數直到鎖被釋放來等待的鎖。等待時間短時很快,但等待期間仍持續佔用 CPU。
互斥鎖(Mutex)
同一時間只有一個執行緒能持有的鎖。無法取得時讓執行緒休眠並讓出 CPU,鎖被釋放時由作業系統喚醒,是一般常見的實作。
環境切換
切換在CPU上執行的行程或執行緒。儲存並還原暫存器與程式計數器等狀態,切換行程時也會切換位址空間。
原子指令(Atomic instruction)
將記憶體的讀取與寫入,作為不會被其他核心中途打斷的單一操作來進行的指令。包括 CAS、fetch-and-add、LR/SC 等。
優先權反轉(Priority inversion)
在高優先權任務等待低優先權任務所持有的資源期間,中優先權任務打斷低優先權任務而持續執行,使高優先權任務被長時間等待的現象。

相關的比較