多線程中的鎖系統(四)-談談自旋鎖
閱讀目錄:
- 基礎
- 自旋鎖示例
- SpinLock
- 繼續SpinLock
- 總結
基礎
內核鎖:基於內核對象構造的鎖機制,就是通常說的內核構造模式。用戶模式構造和內核模式構造
優點:cpu利用最大化。它發現資源被鎖住,請求就排隊等候。線程切換到別處幹活,直到接受到可用信號,線程再切回來繼續處理請求。
缺點:托管代碼->用戶模式代碼->內核代碼損耗、線程上下文切換損耗。
在鎖的時間比較短時,系統頻繁忙於休眠、切換,是個很大的性能損耗。
自旋鎖:原子操作+自循環。通常說的用戶構造模式。 線程不休眠,一直循環嘗試對資源訪問,直到可用。
優點:完美解決內核鎖的缺點。
缺點:長時間一直循環會導致cpu的白白浪費,高並發競爭下、CPU的消耗特別嚴重。
混合鎖:內核鎖+自旋鎖。 混合鎖是先自旋鎖一段時間或自旋多少次,再轉成內核鎖。
優點:內核鎖和自旋鎖的折中方案,利用前二者優點,避免出現極端情況(自旋時間過長,內核鎖時間過短)。
缺點: 自旋多少時間、自旋多少次,這些策略很難把控。
在操作系統及net框架層,這塊算法策略做的已經非常優了,有些API函數也提供了時間及次數可配置項,讓使用者根據需求自行判斷。
自旋鎖示例
來看下我們自己簡單實現的自旋鎖:
int signal = 0; var li = new List<int>(); Parallel.For(0, 1000 * 10000, r => { while (Interlocked.Exchange(ref signal, 1) != 0)//加自旋鎖 { //黑魔法 } li.Add(r); Interlocked.Exchange(ref signal, 0); //釋放鎖 }); Console.WriteLine(li.Count); //輸出:10000000
上面就是自旋鎖:Interlocked.Exchange+while
1:定義signal 0可用,1不可用。
2:Parallel模擬並發競爭,原子更改signal狀態。 後續線程自旋訪問signal,是否可用。
3:A線程使用完後,更改signal為0。 剩余線程競爭訪問資源,B線程勝利後,更改signal為1,失敗線程繼續自旋,直到可用。
SpinLock
SpinLock是net4.0後Net提供的自旋鎖類庫,內部做了優化。
簡單看下實例:
var li = new List<int>(); var sl = new SpinLock(); Parallel.For(0, 1000 * 10000, r => { bool gotLock = false; //釋放成功 sl.Enter(ref gotLock); //進入鎖 li.Add(r); if (gotLock) sl.Exit(); //釋放 }); Console.WriteLine(li.Count); //輸出:10000000
繼續SpinLock
new SpinLock(false) 這個構造函數主要用來檢查死鎖用,true是開啟。
在開啟狀態下,一旦發生死鎖會直接拋異常的。
SpinLock實現的部分源碼:
View Code從代碼中發現SpinLock並不是簡單的實現那樣一直自旋,其內部做了很多優化。
1:內部使用了Interlocked.CompareExchange保持原子操作, m_owner 0可用,1不可用。
2:第一次獲得鎖失敗後,繼續調用ContinueTryEnter,ContinueTryEnter有三種獲得鎖的情況。
3:ContinueTryEnter函數第一種獲得鎖的方式,使用了while+SpinWait。
4:第一種方式達到最大等待者數量後,命中走第二種。 繼續自旋 turn * 100次。100這個值是處理器核數(4, 8 ,16)下最好的。
5:第二種如果還不能獲得鎖,走第三種。這種就帶有混合構造的意思了,如下:
if (yieldsoFar % 40 == 0) Thread.Sleep(1); else if (yieldsoFar % 10 == 0) Thread.Sleep(0); else Thread.Yield();
Thread.Sleep(1) : 終止當前線程,放棄剩下時間片 休眠1毫秒, 退出跟其他線程搶占cpu。當然這個一般會更多,系統無法保證這麽細的時間粒度。
Thread.Sleep(0): 終止當前線程,放棄剩下時間片。 但立馬還會跟其他線程搶cpu,能不能搶到跟線程優先級有關。
Thread.Yeild(): 結束當前線程,讓出CPU給其他準備好的線程。其他線程ok後或沒有還沒有準備好,繼續執行當前,Thread.Yeild()會返回個bool值,表示CPU是否讓出成功。
從源碼中可以學到不少編程技巧,比如可以借鑒自旋+Thread.Yeild() 或 while+Thread.Yeild()等組合使用方式。
總結
本章介紹了自旋鎖的基礎及樓主的經驗。 關於SpinLock類源碼這塊,只簡單理解了下並沒有深究。
測試了下SpinLock和自己實現的自旋鎖性能對比(並行添加1000w List<int>()),SpinLock是單純的自旋鎖性能2倍以上。
另外測試了lock的性能,是系統SpinLock性能的3倍以上,可見lock內部自旋的效率更高,CLR暫沒開源,看不到CLR具體實現的代碼。
參考http://www.projky.com/dotnet/4.0/System/Threading/SpinLock.cs.html
多線程中的鎖系統(四)-談談自旋鎖