程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#多線程編程中的鎖體系(四):自旋鎖

C#多線程編程中的鎖體系(四):自旋鎖

編輯:C#入門知識

C#多線程編程中的鎖體系(四):自旋鎖。本站提示廣大學習愛好者:(C#多線程編程中的鎖體系(四):自旋鎖)文章只能為提供參考,不一定能成為您想要的結果。以下是C#多線程編程中的鎖體系(四):自旋鎖正文


目次
一:基本

二:自旋鎖示例

三:SpinLock

四:持續SpinLock

五:總結

一:基本

內核鎖:基於內查對象結構的鎖機制,就是平日說的內核結構形式。用戶形式結構和內核形式結構

           長處:cpu應用最年夜化。它發明資本被鎖住,要求就列隊等待。線程切換到別處干活,直到接收到可用旌旗燈號,線程再切回來持續處置要求。

           缺陷:托管代碼->用戶形式代碼->內核代碼消耗、線程高低文切換消耗。

                   在鎖的時光比擬短時,體系頻仍忙於休眠、切換,是個很年夜的機能消耗。

自旋鎖:原子操作+自輪回。平日說的用戶結構形式。  線程不休眠,一向輪回測驗考試對資本拜訪,直到可用。

           長處:完善處理內核鎖的缺陷。

           缺陷:長時光一向輪回會招致cpu的白白糟蹋,高並發競爭下、CPU的消費特殊嚴重。

混雜鎖:內核鎖+自旋鎖。 混雜鎖是先自旋鎖一段時光或自旋若干次,再轉成內核鎖。

           長處:內核鎖和自旋鎖的折衷計劃,應用前兩者長處,防止湧現極端情形(自旋時光太長,內核鎖時光太短)。

           缺陷: 自旋若干時光、自旋若干次,這些戰略很難把控。

           ps:操作體系或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後體系幫我們完成的自旋鎖,外部做了優化。

簡略看下實例:
 
 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是開啟。

開啟狀況下,假如產生逝世鎖會直接拋異常的。

貼了一部門源碼(已折疊),我們來看下:

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.SpinWait((turn + i) * SPINNING_FACTOR * processFactor);
                    if (processFactor < processorCount)
                        processFactor++;
                    observedOwner = m_owner;
                    if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                    {
#if !FEATURE_CORECLR
                        Thread.BeginCriticalRegion();
#endif
 
                        int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
                            observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
                            : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
                        Contract.Assert((newOwner & WAITERS_MASK) >= 0);
#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)
                        {
                            return;
                        }
#endif

#if !FEATURE_CORECLR
                        Thread.EndCriticalRegion();
#endif
                    }
                }
            }

            // Check the timeout.
            if (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))
            {
                DecrementWaiters();
                return;
            }

            //*** Step 3, Yielding
            //Sleep(1) every 50 yields
            int yieldsoFar = 0;
            while (true)
            {
                observedOwner = m_owner;
                if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                {
#if !FEATURE_CORECLR
                    Thread.BeginCriticalRegion();
#endif
                    int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero
                           observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters
                           : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit
                    Contract.Assert((newOwner & WAITERS_MASK) >= 0);
#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)
                    {
                        return;
                    }
#endif
 
#if !FEATURE_CORECLR
                    Thread.EndCriticalRegion();
#endif
                }

                if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0)
                {
                    Thread.Sleep(1);
                }
                else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0)
                {
                    Thread.Sleep(0);
                }
                else
                {
#if PFX_LEGACY_3_5
                    Platform.Yield();
#else
                    Thread.Yield();
#endif
                }
 
                if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0)
                {
                    //Check the timeout.
                    if (millisecondsTimeout != Timeout.Infinite && TimeoutExpired(startTicks, millisecondsTimeout))
                    {
                        DecrementWaiters();
                        return;
                    }
                }

                yieldsoFar++;
            }
        }
 
        /// <summary>
        /// decrements the waiters, in case of the timeout is expired
        /// </summary>
        private void DecrementWaiters()
        {
            SpinWait spinner = new SpinWait();
            while (true)
            {
                int observedOwner = m_owner;
                if ((observedOwner & WAITERS_MASK) == 0) return; // don't decrement the waiters if it's corrupted by previous call of Exit(false)
                if (Interlocked.CompareExchange(ref m_owner, observedOwner - 2, observedOwner) == observedOwner)
                {
                    Contract.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped
                    break;
                }
                spinner.SpinOnce();
            }
 
        }

從代碼中發明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值,能否讓出勝利。

從源碼中,我們可以學到很多編程技能。 好比我們也能夠應用  自旋+Thread.Yeild()   或 while+Thread.Yeild() 等組合。


五:總結

本章談了自旋鎖的基本+樓主的經歷。  SpinLock類源碼這塊,只深刻懂得了下,並沒有深究。

測了下SpinLock和本身完成的自旋鎖機能比較(並行添加1000w List<int>()),SpinLock是純真的自旋鎖機能2倍以上。

還測了下lock的機能,是體系SpinLock機能的3倍以上。  可見lock外部自旋的效力更高,CLR暫沒開源,所以看不到CLR詳細完成的代碼。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved