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还原为初始值

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值