自旋鎖與互斥鎖之抉擇
自旋鎖和互斥鎖是多執行緒程式設計中的兩個重要概念。他們都能用來鎖定一些共享資源,以阻止影響資料一致性的併發訪問。但是他們之間確實存在區別,那麼這些區別是什麼?
1 理論
理論上,當一個執行緒試圖獲取一個被鎖定的互斥鎖時,該操作會失敗然後該執行緒會進入睡眠,這樣就能馬上讓另一個執行緒執行。當持有互斥鎖的執行緒釋放該鎖之後,進入睡眠狀態的執行緒就會被喚醒。但是,當一個執行緒試圖獲取一個自旋鎖而沒有成功時,該執行緒會不斷地重試,直到最終成功為止;因此該執行緒不會將執行權交到其他執行緒手中(當然,一旦當前執行緒的時間片超時,作業系統會強行切換到另一個執行緒)。
2 問題
互斥鎖的問題在於:讓執行緒睡眠和喚醒執行緒都是極為耗時的操作,完成這些操作需要大量CPU指令,因此也就需要耗費不少時間。如果只是鎖定互斥鎖很短一段時間,那麼讓執行緒睡眠和喚醒執行緒所花的時間可能會超過執行緒實際上睡眠的時間,甚至有可能會超過執行緒在自旋鎖上輪詢鎖浪費的時間(如果使用自旋鎖)。另一方面,在自旋鎖上進行輪詢會浪費CPU時間,如果自旋鎖被鎖定較長的時間,可能會浪費大量的CPU時間,這時讓執行緒睡眠可能是一個更好的選擇。
3 解決方法
在一個單核系統中使用自旋鎖是行不通的,因為只要自旋鎖輪詢在阻塞當前CPU,那麼就沒有其他執行緒能夠執行,既然沒有其他執行緒能夠執行,那麼該鎖也就不會被喚醒,對,我們進入死鎖了。最好情況下,自旋鎖僅僅浪費那些對系統沒有任何用處的CPU時間。相反,如果使用互斥鎖,執行緒A進入睡眠,那麼另外一個執行緒B就能夠立即執行,執行緒B有可能會釋放鎖,喚醒執行緒A,使執行緒A繼續執行。
在一個多核系統,如果大量的鎖只持有很短一段時間,那麼讓執行緒睡眠和喚醒執行緒所浪費的時間有可能會極大地降低執行時效能。相反,如果使用自旋鎖,執行緒就有機會利用完全時間片(總是阻塞很短一段時間,然後立即執行),獲得更高的吞吐量。
4 實踐
由於大部分情況下,程式設計師不能預先知道使用互斥鎖好還是使用自旋鎖好(例如:因為不知道目標系統的CPU核心數量),同時作業系統也不知道某個片段的程式碼是否已經為單核或多核環境優化過,因此大部分系統不嚴格區分這兩種鎖。實際上,大部分現代作業系統都提供混合互斥鎖和混合自旋鎖。那麼,什麼是混合互斥鎖和混合自旋鎖?
在一個多核系統,混合互斥鎖開始時會表現得像自旋鎖。即如果一個執行緒A不能獲取到互斥鎖,那麼執行緒A不會立即進入睡眠狀態,因為該鎖可能馬上就被釋放了,因此該互斥鎖開始表現得像自旋鎖。只有當一段固定的時間後,執行緒A還不能獲取到該互斥鎖,執行緒A才會進入睡眠狀態。如果相同的程式執行在單核系統下,該互斥鎖就不會表現出自旋鎖的行為。
一個混合自旋鎖開始時會表現得像一個普通的自旋鎖,但為了避免浪費CPU時間,它提供了一個back-off策略。通常,混合自旋鎖不會使執行緒進入睡眠狀態(因為當你使用自旋鎖時,你不希望發生這種情況),但是它能停止某個執行緒(立即或者一段固定的時間後),然後讓另一個執行緒執行,以提高自旋鎖的閒置率(一個純粹的執行緒切換通常比使執行緒進入睡眠然後喚醒它效率更高,起碼目前如此)。
5 總結
如果你不知道該使用哪一個,那麼使用互斥鎖,因為大部分現代作業系統都允許他們先自旋一小段時間(提前是該自旋有益), 所以互斥鎖通常是更好的選擇。有時,使用自旋鎖會提升效能,在某些特定情況下,你可能會覺得使用自旋鎖更好。這時候,使用你自己的鎖物件,該鎖物件內部使用自旋鎖或者互斥鎖實現(這個行為可以通過配置修改),開始時全部使用互斥鎖,之後,如果你覺得某個地方使用自旋鎖更好,那麼修改它,然後比較下結果,但是在下結論之前,一定要記得在單核和多核環境下進行測試。