1. 程式人生 > >自旋鎖與互斥鎖

自旋鎖與互斥鎖

為了解決對共享資源的互斥使用,出現了一些鎖機制。比如,自旋鎖和互斥鎖。
自旋鎖和互斥鎖很類似,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。但是兩者在排程機制上略有不同。對於互斥鎖,如果資源已經被佔用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈在那裡看是否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。

自旋鎖,曾經有個經典的例子來比喻自旋鎖:A,B兩個人合租一套房子,共用一個廁所,那麼這個廁所就是共享資源,且在任一時刻最多隻能有一個人在使用。當廁所閒置時,誰來了都可以使用,當A使用時,就會關上廁所門,而B也要使用,但是急啊,就得在門外焦急地等待,急得團團轉,是為“自旋”。
百度百科:

跟互斥鎖一樣,一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麼將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操作將自旋在那裡,直到該自旋鎖的保持者釋放了鎖。由此我們可以看出,自旋鎖是一種比較低階的保護資料結構或程式碼片段的原始方式,這種鎖可能存在兩個問題:
死鎖。試圖遞迴地獲得自旋鎖必然會引起死鎖:遞迴程式的持有例項在第二個例項迴圈,以試圖獲得相同自旋鎖時,不會釋放此自旋鎖。在遞迴程式中使用自旋鎖應遵守下列策略:遞迴程式決不能在持有自旋鎖時呼叫它自己,也決不能在遞迴呼叫時試圖獲得相同的自旋鎖。此外如果一個程序已經將資源鎖定,那麼,即使其它申請這個資源的程序不停地瘋狂“自旋”,也無法獲得資源,從而進入死迴圈。
過多佔用cpu資源。如果不加限制,由於申請者一直在迴圈等待,因此自旋鎖在鎖定的時候,如果不成功,不會睡眠,會持續的嘗試,單cpu的時候自旋鎖會讓其它process動不了. 因此,一般自旋鎖實現會有一個引數限定最多持續嘗試次數. 超出後, 自旋鎖放棄當前time slice. 等下一次機會
由此可見,自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況。正是由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。訊號量和讀寫訊號量適合於保持時間較長的情況,它們會導致呼叫者睡眠,因此只能在程序上下文使用,而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。如果被保護的共享資源只在程序上下文訪問,使用訊號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和頂半部即軟中斷),就必須使用自旋鎖。自旋鎖保持期間是搶佔失效的,而訊號量和讀寫訊號量保持期間是可以被搶佔的。自旋鎖只有在核心可搶佔或SMP(多處理器)的情況下才真正需要,在單CPU且不可搶佔的核心下,自旋鎖的所有操作都是空操作。

自旋鎖是一種非阻塞鎖,也就是說,如果某執行緒需要獲取自旋鎖,但該鎖已經被其他執行緒佔用時,該執行緒不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取自旋鎖。
互斥量是阻塞鎖,當某執行緒無法獲取互斥量時,該執行緒會被直接掛起,該執行緒不再消耗CPU時間,當其他執行緒釋放互斥量後,作業系統會啟用那個被掛起的執行緒,讓其投入執行。
兩種鎖適用於不同場景:
如果是多核處理器,如果預計執行緒等待鎖的時間很短,短到比執行緒兩次上下文切換時間要少的情況下,使用自旋鎖是划算的。
如果是多核處理器,如果預計執行緒等待鎖的時間較長,至少比兩次執行緒上下文切換的時間要長,建議使用互斥量。
如果是單核處理器,一般建議不要使用自旋鎖。因為,在同一時間只有一個執行緒是處在執行狀態,那如果執行執行緒發現無法獲取鎖,只能等待解鎖,但因為自身不掛起,所以那個獲取到鎖的執行緒沒有辦法進入執行狀態,只能等到執行執行緒把作業系統分給它的時間片用完,才能有機會被排程。這種情況下使用自旋鎖的代價很高。

自旋鎖實際上是忙等鎖(一直重新整理等待),也可能導致系統死鎖(遞迴呼叫)。
互斥鎖實際的效率還是可以讓人接受的,加鎖的時間大概100ns左右,而實際上互斥鎖的一種可能的實現是先自旋一段時間,當自旋的時間超過閥值之後再將執行緒投入睡眠中,因此在併發運算中使用互斥鎖(每次佔用鎖的時間很短)的效果可能不亞於使用自旋鎖。