我正在努力理解这门课程编写背后的逻辑,以及何时应该而且不应该使用它。任何见解都将不胜感激
internal struct SpinLock
{
private volatile int lockHeld;
private readonly static int processorCount;
public bool IsHeld
{
get
{
return this.lockHeld != 0;
}
}
static SpinLock()
{
SpinLock.processorCount = Environment.ProcessorCount;
}
public void Enter()
{
if (Interlocked.CompareExchange(ref this.lockHeld, 1, 0) != 0)
{
this.EnterSpin();
}
}
private void EnterSpin()
{
int num = 0;
while (this.lockHeld != null || Interlocked.CompareExchange(ref this.lockHeld, 1, 0) != 0)
{
if (num >= 20 || SpinLock.processorCount <= 1)
{
if (num >= 25)
{
Thread.Sleep(1);
}
else
{
Thread.Sleep(0);
}
}
else
{
Thread.SpinWait(100);
}
num++;
}
}
public void Exit()
{
this.lockHeld = 0;
}
}
更新:我在源代码中找到了一个示例用法...这表明如何使用上述对象,虽然我不明白“为什么”
internal class FastReaderWriterLock
{
private SpinLock myLock;
private uint numReadWaiters;
private uint numWriteWaiters;
private int owners;
private EventWaitHandle readEvent;
private EventWaitHandle writeEvent;
public FastReaderWriterLock()
{
}
public void AcquireReaderLock(int millisecondsTimeout)
{
this.myLock.Enter();
while (this.owners < 0 || this.numWriteWaiters != 0)
{
if (this.readEvent != null)
{
this.WaitOnEvent(this.readEvent, ref this.numReadWaiters, millisecondsTimeout);
}
else
{
this.LazyCreateEvent(ref this.readEvent, false);
}
}
FastReaderWriterLock fastReaderWriterLock = this;
fastReaderWriterLock.owners = fastReaderWriterLock.owners + 1;
this.myLock.Exit();
}
private void WaitOnEvent(EventWaitHandle waitEvent, ref uint numWaiters, int millisecondsTimeout)
{
waitEvent.Reset();
uint& numPointer = numWaiters;
bool flag = false;
this.myLock.Exit();
try
{
if (waitEvent.WaitOne(millisecondsTimeout, false))
{
flag = true;
}
else
{
throw new TimeoutException("ReaderWriterLock timeout expired");
}
}
finally
{
this.myLock.Enter();
uint& numPointer1 = numWaiters;
if (!flag)
{
this.myLock.Exit();
}
}
}
}
答案 0 :(得分:4)
SpinLocks通常是一种锁定形式,让等待的线程保持清醒状态(在紧密的循环中反复循环 - 想想“妈妈,我们在那里吗?”而不是依靠较重,较慢的内核模式信号。它们通常用于预期等待时间非常短的情况,其中它们优于创建和等待传统锁的OS句柄的开销。虽然它们比传统锁具有更高的CPU成本,但是对于超过很短的等待时间,传统的锁(如Monitor类或各种WaitHandle实现)是首选。
这个短暂的等待时间概念在上面的代码中得到了证明:
waitEvent.Reset();
// All that we are doing here is setting some variables.
// It has to be atomic, but it's going to be *really* fast
uint& numPointer = numWaiters;
bool flag = false;
// And we are done. No need for an OS wait handle for 2 lines of code.
this.myLock.Exit();
有一个非常好的SpinLock built into the BCL,但它只在v4.0 +中,所以如果您使用的是旧版本的.NET框架或从旧版本迁移的代码,有人可能已经编写了自己的实现。
回答您的问题: 如果要在.NET 4.0或更高版本上编写新代码,则应使用内置SpinLock。对于3.5或更早的代码,特别是如果你扩展Nesper,我认为这个实现是经过时间考验和适当的。只使用SpinLock,你知道线程可能等待的时间非常小,如上例所示。
编辑:看起来你的实现来自Nesper- Esper CEP库的.NET端口:
https://svn.codehaus.org/esper/esper/tagsnet/release_1.12.0_beta_1/trunk/NEsper/compat/SpinLock.cs
和
我可以确认Nesper早在.NET框架4之前就存在了,所以这就解释了需要一个家庭旋转的SpinLock。
答案 1 :(得分:1)
原作者似乎想要更快版本的ReaderWriterLock
。这个老班很痛苦。我自己的测试(我很久以前做过)表明RWL的开销是普通旧lock
的8倍。 ReaderWriterLockSlim
改进了很多东西(尽管它与lock
相比仍然有大约2倍的开销)。在这一点上,我会说放弃自定义代码,只使用较新的ReaderWriterLockSlim
类。
但是,值得让我解释一些自定义SpinLock
代码。
Interlocked.CompareExchange
是.NET的CAS操作版本。它是 最基本的同步原语。你可以通过这个单独的操作构建其他所有内容,包括你自己的自定义Monitor
- 类似于类,读者编写器锁等。显然,这里使用了它来创建自旋锁。Thread.Sleep(0)
产生任何处理器上具有相同或更高优先级的任何线程。Thread.Sleep(1)
产生任何处理器上的任何线程。Thread.SpinWait
将线程置于指定迭代次数的紧密循环中。虽然它没有在您发布的代码中使用,但还有另一种有用的机制来创建自旋锁(或其他低锁策略)。
Thread.Yield
产生相同处理器上的任何线程。 Microsoft在其高度并发的同步机制和集合中使用所有这些调用。如果你反编译SpinLock
,SpinWait
,ManualResetEventSlim
等等,你会看到一个相当复杂的歌曲和舞蹈正在进行这些调用......比你发布的代码复杂得多
再一次,抛弃自定义代码,只使用ReaderWriterLockSlim
而不是自定义FastReaderWriterLock
类。
顺便说一句,this.lockHeld != null
应该产生编译器警告,因为lockHeld
是值类型。