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

C# SpinLock实现的更多相关文章

  1. 装逼名词-ABA CAS SpinLock

    今天看wiki,看到一个提到什么什么会陷入 race condition & ABA problem.丫的我没听过ABA呀,那么我去搜了一下,如下: http://www.bubuko.com ...

  2. 【C#】【Thread】SpinLock

    SpinLock结构是一个低级别的互斥同步基元,它在等待获取锁时进行旋转. 在多核计算机上,当等待时间预计较短且极少出现争用情况时,SpinLock 的性能将高于其他类型的锁. 不过,我们建议您仅在通 ...

  3. 锁相关知识 & mutex怎么实现的 & spinlock怎么用的 & 怎样避免死锁 & 内核同步机制 & 读写锁

    spinlock在上一篇文章有提到:http://www.cnblogs.com/charlesblc/p/6254437.html  通过锁数据总线来实现. 而看了这篇文章说明:mutex内部也用到 ...

  4. Linux内核原子(1) - spinlock的实现

    spinlock的数据结构spinlock_t定义在头文件linux/spinlock_types.h里面: typedef struct { raw_spinlock_t raw_lock; #if ...

  5. [20140829]spinlock导致cpu居高不下

    背景: 出现cpu高于常规的告警 排查: 1.开跟踪,没有发现cup特别高的查询 2.查看内核cpu使用量,看是否是sql server 端引起 3.查看负荷,是否负荷特别高这里使用 batch re ...

  6. spinlock原理

    [参考] http://www.searchtb.com/2011/06/spinlock%E5%89%96%E6%9E%90%E4%B8%8E%E6%94%B9%E8%BF%9B.html

  7. 自旋锁-SpinLock(.NET 4.0+)

    短时间锁定的情况下,自旋锁(spinlock)更快.(因为自旋锁本质上不会让线程休眠,而是一直循环尝试对资源访问,直到可用.所以自旋锁线程被阻塞时,不进行线程上下文切换,而是空转等待.对于多核CPU而 ...

  8. 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationContext, CoreDispatcher, ThreadLocal, ThreadStaticAttribute

    [源码下载] 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationCont ...

  9. 【linux】spinlock 的实现

    一.什么是spinlock spinlock又称自旋锁,是实现保护共享资源而提出一种锁机制.自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用 无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一 ...

  10. atomic, spinlock and mutex性能比较

    我非常好奇于不同同步原理的性能,于是对atomic, spinlock和mutex做了如下实验来比较: 1. 无同步的情况 #include <future> #include <i ...

随机推荐

  1. bzoj2152 树分治

    还是太菜了,自己写的wa,但是找不到哪里错了,, 感觉现在学树分治早了点..以后回来再看吧 /* 多少点对之间的路径是3的倍数 */ #include<iostream> #include ...

  2. java -d

    DB_SERVER_URL="-Ddatasource.driver=oracle.jdbc.OracleDriver -Ddatasource.url=jdbc:oracle:thin:@ ...

  3. 用Delphi从内存流中判断图片格式

    https://blog.csdn.net/my98800/article/details/53536774 废话不多说了,利用内存流来判断文件的格式,其实判断文件的前几个字节就可以简单的判断这个文件 ...

  4. [九省联考2018]一双木棋chess

    题解: 水题吧 首先很显然的是状压或者搜索 考虑一下能不能状压吧 这个东西一定是长成三角形的样子的 所以是可以状压的 相邻两位之间有几个0代表他们差几 这样最多会有2n 然后就可以转移了 由于之前对博 ...

  5. Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架

    Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了 Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架 Asp.Ne ...

  6. 【Java】 剑指offer(64) 求1+2+…+n

      本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 求1+2+…+n,要求不能使用乘除法.for.while.if ...

  7. Trident Topology开发Demo

    1.项目结构 · 2.启动的服务 无 3.驱动程序 package com.jun.trident; import backtype.storm.Config; import backtype.sto ...

  8. java连接数据库(jdbc)调用配置文件

    各种语言都有自己所支持的配置文件,后缀名“.properties”结尾的就是其中之一. 在java连接数据库时,采取读取配置文件的方式,来获取数据库连接. 新建jdbc.properties文件,内容 ...

  9. POJ 3279 Fliptile (二进制枚举)

    <题目链接> <转载于 >>> > 题目大意: 给定一个M*N矩阵,有些是黑色(1表示)否则白色(0表示),每翻转一个(i,j),会使得它和它周围4个格变为另 ...

  10. vue+ElementUI使用笔记

    1,使用表单验证: //定义验证规则 window.varifyUtil = { //验证数字 validateNumber: function(rule, value, callback){ if ...