zoukankan      html  css  js  c++  java
  • C# SpinLock实现

    关于SpinLock自旋锁网上已经有很多说明,这里也copy了一部分,我这里主要关注微软的实现,学习人家的实现方式。

    如果由于垃圾回收,基于对象的锁对象开销太高,可以使用SpinLock结构。.NET 4以后版本可使用,如果你有很多个锁(如,一个列表里面的每一个节点)并且锁时间通常非常的短,使用SpinLock将很有用。你需要避免使用超过一个的SpinLock,并且不要调用任何可能阻塞的。除了架构不同,SpinLock的使用同Monitor类非常相似。通过Enter或者TryEnter请求锁,并通过Exit释放锁。SpinLock同样也通过两个属性来提供关于它当前是否已锁的信息:IsHeld和IsHeldByCurrentThread.

    不要将SpinLock声明为只读字段,如果声明为只读字段,会导致每次调用都会返回一个SpinLock新副本,在多线程下,每个方法都会成功获得锁,而受到保护的临界区不会按照预期进行串行化。

    SpinLock 仅当您确定这样做可以改进应用程序的性能之后才能使用另外,务必请注意 SpinLock 是一个值类型(出于性能原因)。因此,您必须非常小心,不要意外复制了 SpinLock 实例,因为两个实例(原件和副本)之间完全独立,这可能会导致应用程序出现错误行为。如果必须传递 SpinLock 实例,则应该通过引用而不是通过值传递

        [HostProtection(Synchronization = true, ExternalThreading = true)]
        public struct SpinLock
        {
           private volatile int m_owner;
           private const int SPINNING_FACTOR = 100;
    
            // After how many yields, call Sleep(1)
            private const int SLEEP_ONE_FREQUENCY = 40;
    
            // After how many yields, call Sleep(0)
            private const int SLEEP_ZERO_FREQUENCY = 10;
    
            // After how many yields, check the timeout
            private const int TIMEOUT_CHECK_FREQUENCY = 10;
    
            // Thr thread tracking disabled mask
            private const int LOCK_ID_DISABLE_MASK = unchecked((int)0x80000000);  
           
               public SpinLock(bool enableThreadOwnerTracking)
            {
                m_owner = LOCK_UNOWNED;
                if (!enableThreadOwnerTracking)
                {
                    m_owner |= LOCK_ID_DISABLE_MASK;
                    Contract.Assert(!IsThreadOwnerTrackingEnabled, "property should be false by now");
                }
            }
            
            public void TryEnter(ref bool lockTaken)
            {
                TryEnter(0, ref lockTaken);
            }
            public void TryEnter(int millisecondsTimeout, ref bool lockTaken)
            {
                int observedOwner = m_owner;
                if (millisecondsTimeout < -1 || //invalid parameter
                    lockTaken || //invalid parameter
                    (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK ||  //thread tracking is enabled or the lock is already acquired
                    Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) // acquiring the lock failed
                    ContinueTryEnter(millisecondsTimeout, ref lockTaken); // The call the slow pth
            }
        
            private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken)
            {         
                uint startTime = 0;
                if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0)
                {
                    startTime = TimeoutHelper.GetTime();
                }
                if (IsThreadOwnerTrackingEnabled)
                {
                    // Slow path for enabled thread tracking mode
                    ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, 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 increases 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;
                int turn = int.MaxValue;
                //***Step 1, take the lock or update the waiters
    
                // try to acquire the lock directly if possible or update the waiters count
                observedOwner = m_owner;
                if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED)
                {
                    if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)
                    {
                        return;
                    }
                }
                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)
                        turn = (Interlocked.Add(ref m_owner, 2) & WAITERS_MASK) >> 1 ;
                }
                // Check the timeout.
                if (millisecondsTimeout == 0 ||
                    (millisecondsTimeout != Timeout.Infinite &&
                    TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
                {
                    DecrementWaiters();
                    return;
                }
    
                //***Step 2. Spinning
                //lock acquired failed and waiters updated
                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)
                        {
                            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 (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                            {
                                return;
                            }
                        }
                    }
                }
    
                // Check the timeout.
                if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
                {
                    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)
                    {
                        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 (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner)
                        {
                            return;
                        }
                    }
                    if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0)
                    {
                        Thread.Sleep(1);
                    }
                    else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0)
                    {
                        Thread.Sleep(0);
                    }
                    else
                    {
                        Thread.Yield();
                    }
                    if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0)
                    {
                        //Check the timeout.
                        if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)
                        {
                            DecrementWaiters();
                            return;
                        }
                    }
                    yieldsoFar++;
                }
            }
    
            private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken)
            {
                Contract.Assert(IsThreadOwnerTrackingEnabled);
                int lockUnowned = 0;
                // We are using thread IDs to mark ownership. Snap the thread ID and check for recursion.
                // We also must or the ID enablement bit, to ensure we propagate when we CAS it in.
                int m_newOwner = Thread.CurrentThread.ManagedThreadId;
                if (m_owner == m_newOwner)
                {
                    // We don't allow lock recursion.
                    throw new LockRecursionException(Environment.GetResourceString("SpinLock_TryEnter_LockRecursionException"));
                }
                SpinWait spinner = new SpinWait();
                // Loop until the lock has been successfully acquired or, if specified, the timeout expires.
                do
                {
                    // We failed to get the lock, either from the fast route or the last iteration
                    // and the timeout hasn't expired; spin once and try again.
                    spinner.SpinOnce();
                    // Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily.
                    if (m_owner == lockUnowned)
                    {
                        if (Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned, ref lockTaken) == lockUnowned)
                        {
                            return;
                        }
                    }
                    // Check the timeout.  We only RDTSC if the next spin will yield, to amortize the cost.
                    if (millisecondsTimeout == 0 ||
                        (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield &&
                        TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0))
                    {
                        return;
                    }
                } while (true);
            }
            public void Exit()
            {
                //This is the fast path for the thread tracking is disabled, otherwise go to the slow path
                if ((m_owner & LOCK_ID_DISABLE_MASK) == 0)
                    ExitSlowPath(true);
                else
                    Interlocked.Decrement(ref m_owner);
            }
            
            private void ExitSlowPath(bool useMemoryBarrier)
            {
                bool threadTrackingEnabled = (m_owner & LOCK_ID_DISABLE_MASK) == 0;
                if (threadTrackingEnabled && !IsHeldByCurrentThread)
                {
                    throw new System.Threading.SynchronizationLockException(
                        Environment.GetResourceString("SpinLock_Exit_SynchronizationLockException"));
                }
    
                if (useMemoryBarrier)
                {
                    if (threadTrackingEnabled)
                        Interlocked.Exchange(ref m_owner, LOCK_UNOWNED);
                    else
                        Interlocked.Decrement(ref m_owner);
    
                }
                else
                {
                    if (threadTrackingEnabled)
                        m_owner = LOCK_UNOWNED;
                    else
                    {
                        int tmpOwner = m_owner;
                        m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED);
                    }
    
                }
    
            }
            public bool IsHeld
            {           
                get
                {
                    if (IsThreadOwnerTrackingEnabled)
                        return m_owner != LOCK_UNOWNED;
                    return (m_owner & LOCK_ANONYMOUS_OWNED) != LOCK_UNOWNED;
                }
            }
            public bool IsHeldByCurrentThread
            {
                get
                {
                    if (!IsThreadOwnerTrackingEnabled)
                    {
                        throw new InvalidOperationException(Environment.GetResourceString("SpinLock_IsHeldByCurrentThread"));
                    }
                    return ((m_owner & (~LOCK_ID_DISABLE_MASK)) == Thread.CurrentThread.ManagedThreadId);
                }
            }
        }

    SpinLock 构造函数有一个bool enableThreadOwnerTracking参数用来表示是否跟踪线程,如果为true,那么在获取锁以后变量m_owner就是线程ManagedThreadId属性,否者为1,因为获取锁的修改 observedOwner | 1 ,就相当于m_owner设为1,在释放锁的时候m_owner减1Interlocked.Decrement(ref m_owner) 或者设置为 Interlocked.Exchange(ref m_owner, LOCK_UNOWNED)。

    SpinLock的核心方法Enter和TryEnter最终都是调用ContinueTryEnter方法,该方法首先检查IsThreadOwnerTrackingEnabled是否启用线程跟踪,如果启用就调用ContinueTryEnterWithThreadTracking方法,ContinueTryEnterWithThreadTracking方法里面实例化了一个SpinWait,然后自旋获取锁,这里也是借用原子操作【Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned, ref lockTaken)】,如果没有启用跟踪,那么ContinueTryEnter将分3不走,就像里面的注释描述的那样;case 1通过原子操作【Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner)】直接获取锁,如果失败进入到case2【turn < processorCount】,然后在循环尝试获取锁,每次循环都会调用 Thread.SpinWait方法等待;获取锁还是通过原子操作,如果失败,则进入case3,该case也是循环等待,在循环体里面不在是 Thread.SpinWait而是  Thread.Yield();和    Thread.Sleep(0);

    if (yieldsoFar % 40 == 0) 
                        Thread.Sleep(1);
                    else if (yieldsoFar % 10 == 0)
                        Thread.Sleep(0);
                    else
                        Thread.Yield();

    看到这个代码 是不是和 SpinWait相似啊。可以总结以下,case1 直接尝试获取锁,case2 循环中通过调用Thread.SpinWait 尝试获取锁【当前线程不会让出CPU】, case3循环中通过Thread.Yield()和Thread.Sleep来尝试获取锁

     Exit的方法实现就非常简单了,主要是调用ExitSlowPath,说白了就是把变量m_owner还原为初始值

     

  • 相关阅读:
    前方高能!!!一大泼干货来袭。。。。
    spring-cloud-gateway(三)自定义lb实现
    spring-cloud-gateway(二)es代理功能需求
    spring-cloud-gateway(一)代码分析
    一个spark MurmurHash map类加器
    hbase RegionTooBusyException报错异常处理
    hbase HexStringSplit 预分区
    spark读写hbase的几种方式,及读写相关问题
    实现elasticsearch网关,兼容不同版本es,滚动升级-功能验证开发
    k8s平台集成kong ingress 布署konga集成ui
  • 原文地址:https://www.cnblogs.com/majiang/p/7890032.html
Copyright © 2011-2022 走看看