閱讀目錄:
內核鎖:基于內核對象構造的鎖機制,就是通常說的內核構造模式。用戶模式構造和內核模式構造
優點: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是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); //輸出:10000000new SpinLock(false) 這個構造函數主要用來檢查死鎖用,true是開啟。
在開啟狀態下,一旦發生死鎖會直接拋異常的。
SpinLock實現的部分源碼:

public void Enter(ref bool lockTaken) { if (lockTaken) { lockTaken = false; throw new System.ArgumentException(Environment.GetResourceString("SpinLock_TryReliableEnter_ArgumentException")); } // Fast path to acquire the lock if the lock is released // If the thread tracking enabled set the new owner to the current thread id // Id not, set the anonymous bit lock int observedOwner = m_owner; int newOwner = 0; bool threadTrackingEnabled = (m_owner & LOCK_ID_DISABLE_MASK) == 0; if (threadTrackingEnabled) { if (observedOwner == LOCK_UNOWNED) newOwner = Thread.CurrentThread.ManagedThreadId; } else if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) { newOwner = observedOwner | LOCK_ANONYMOUS_OWNED; // set the lock bit } if (newOwner != 0) {#if !FEATURE_CORECLR Thread.BeginCriticalRegion();#endif #if PFX_LEGACY_3_5 if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner) == observedOwner) { lockTaken = true; return; }#else if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner) { // Fast path succeeded return; }#endif #if !FEATURE_CORECLR Thread.EndCriticalRegion();#endif } //Fast path failed, try slow path ContinueTryEnter(Timeout.Infinite, ref lockTaken); } PRivate void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken) { long startTicks = 0; if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0) { startTicks = DateTime.UtcNow.Ticks; }#if !FEATURE_PAL && !FEATURE_CORECLR // PAL doesn't support eventing, and we don't compile CDS providers for Coreclr if (CdsSyncEtwBCLProvider.Log.IsEnabled()) { CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner); }#endif if (IsThreadOwnerTrackingEnabled) { // Slow path for enabled thread tracking mode ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTicks, ref lockTaken); return; } // then thread tracking is disabled // In this case there are three ways to acquire the lock // 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2 // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn // the late the thread arrives the more it spins and less frequent it check the lock avilability // Also the spins count is increaes each iteration // If the spins iterations finished and failed to acquire the lock, go to step 3 // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1) // If the timeout is expired in after step 1, we need to decrement the waiters count before returning int observedOwner; //***Step 1, take the lock or update the waiters // try to acquire the lock directly if possoble or update the waiters count SpinWait spinner = new SpinWait(); while (true) { observedOwner = m_owner; if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) {#if !FEATURE_CORECLR Thread.BeginCriticalRegion(); #endif #if PFX_LEGACY_3_5 if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner) == observedOwner) { lockTaken = true; return; }#else if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner) { return; }#endif #if !FEATURE_CORECLR Thread.EndCriticalRegion();#endif } else //failed to acquire the lock,then try to update the waiters. If the waiters count reached the maximum, jsut break the loop to avoid overflow if ((observedOwner & WAITERS_MASK) == MAXIMUM_WAITERS || Interlocked.CompareExchange(ref m_owner, observedOwner + 2, observedOwner) == observedOwner) break; spinner.SpinOnce(); } // Check the timeout. if (millisecondsTimeout == 0 || (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))) { DecrementWaiters(); return; } //***Step 2. Spinning //lock acquired failed and waiters updated int turn = ((observedOwner + 2) & WAITERS_MASK) / 2; int processorCount = PlatformHelper.ProcessorCount; if (turn < processorCount) { int processFactor = 1; for (int i = 1; i <= turn * SPINNING_FACTOR; i++) { Thread.SpinWai
新聞熱點
疑難解答