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还原为初始值。
C# SpinLock实现的更多相关文章
- 装逼名词-ABA CAS SpinLock
今天看wiki,看到一个提到什么什么会陷入 race condition & ABA problem.丫的我没听过ABA呀,那么我去搜了一下,如下: http://www.bubuko.com ...
- 【C#】【Thread】SpinLock
SpinLock结构是一个低级别的互斥同步基元,它在等待获取锁时进行旋转. 在多核计算机上,当等待时间预计较短且极少出现争用情况时,SpinLock 的性能将高于其他类型的锁. 不过,我们建议您仅在通 ...
- 锁相关知识 & mutex怎么实现的 & spinlock怎么用的 & 怎样避免死锁 & 内核同步机制 & 读写锁
spinlock在上一篇文章有提到:http://www.cnblogs.com/charlesblc/p/6254437.html 通过锁数据总线来实现. 而看了这篇文章说明:mutex内部也用到 ...
- Linux内核原子(1) - spinlock的实现
spinlock的数据结构spinlock_t定义在头文件linux/spinlock_types.h里面: typedef struct { raw_spinlock_t raw_lock; #if ...
- [20140829]spinlock导致cpu居高不下
背景: 出现cpu高于常规的告警 排查: 1.开跟踪,没有发现cup特别高的查询 2.查看内核cpu使用量,看是否是sql server 端引起 3.查看负荷,是否负荷特别高这里使用 batch re ...
- spinlock原理
[参考] http://www.searchtb.com/2011/06/spinlock%E5%89%96%E6%9E%90%E4%B8%8E%E6%94%B9%E8%BF%9B.html
- 自旋锁-SpinLock(.NET 4.0+)
短时间锁定的情况下,自旋锁(spinlock)更快.(因为自旋锁本质上不会让线程休眠,而是一直循环尝试对资源访问,直到可用.所以自旋锁线程被阻塞时,不进行线程上下文切换,而是空转等待.对于多核CPU而 ...
- 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationContext, CoreDispatcher, ThreadLocal, ThreadStaticAttribute
[源码下载] 重新想象 Windows 8 Store Apps (48) - 多线程之其他辅助类: SpinWait, SpinLock, Volatile, SynchronizationCont ...
- 【linux】spinlock 的实现
一.什么是spinlock spinlock又称自旋锁,是实现保护共享资源而提出一种锁机制.自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用 无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一 ...
- atomic, spinlock and mutex性能比较
我非常好奇于不同同步原理的性能,于是对atomic, spinlock和mutex做了如下实验来比较: 1. 无同步的情况 #include <future> #include <i ...
随机推荐
- 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记
回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...
- log4j2 使用详解
转载自 Blog of 天外的星星: http://www.cnblogs.com/leo-lsw/p/log4j2tutorial.html Log4j 2的好处就不和大家说了,如果你搜了2,说明你 ...
- 数据流分段下载(Http之 Range)
public FileStreamResult StreamUploadedSongs(int id) { byte[] song = db.UploadedSongs.Where(x => x ...
- [颜色知识] 潘通色卡、CMYK、RGB、 ARGB...
潘通色卡C结尾的色号都是RGB色系的,而CMYK是印刷系的,两者不能通用的,所以,不能完全对等进行转换 Pantone Colors [色卡]PANTONE潘通色卡C面颜色http://blog.si ...
- Visual Studio快速调出异常设置
使用快捷键:
- hdu 1004 颜色与数字(map水题)
Sample Input5 //Tgreenredblueredred 统计颜色的次数 输出最多的颜色3pinkorangepink0 Sample Outputred pink # include ...
- Springboot实现filter拦截token验证和跨域
背景 web验证授权合法的一般分为下面几种 使用session作为验证合法用户访问的验证方式 使用自己实现的token 使用OCA标准 在使用API接口授权验证时,token是自定义的方式实现起来不需 ...
- CDQ分治求前缀和
#include<bits/stdc++.h> using namespace std; ; int n,a_tot,q_tot,ans[N]; ]; struct query { int ...
- 6.Django与Ajax
Ajax 文件夹为Ajaxdemo 向服务器发送请求的途径: 1.浏览器地址栏,默认get请求: 2.form表单: get请求 post请求 3.a标签,超链接(get请求) 4.Ajax请求 特点 ...
- Flutter常用组件(Widget)解析-ListView
一个可滚动的列表组件 不管在哪,列表组件都尤为重要和常用. 首先来看个例子: import 'package:flutter/material.dart'; void main () => ru ...