何时应该&不应该使用此C#实用程序类通过Interlocked控制线程

我正在努力理解这个类的编写背后的逻辑,以及何时应该而且不应该使用它。 任何见解将不胜感激

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 = 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(); } } } } 

一般来说 , SpinLocks是一种锁定forms,可以让等待的线程保持清醒状态(在紧密的循环中反复循环 – 想想“ 妈咪我们还在那里吗? ”)而不是依靠更重,更慢的内核模式信号。 它们通常用于预期等待时间非常短的情况,它们优于创建和等待传统锁的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(); 

BCL中内置了一个非常好的SpinLock,但它只在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

https://svn.codehaus.org/esper/esper/tagsnet/release_1.12.0_beta_1/trunk/NEsper/compat/FastReaderWriterLock.cs

我可以确认Nesper早在.NET框架4之前就已存在,因此这解释了需要一个家庭旋转的SpinLock。

原作者似乎想要一个更快版本的ReaderWriterLock 。 这个老class很痛苦。 我自己的测试(我很久以前做过)表明RWL的开销是普通旧lock 8倍。 ReaderWriterLockSlim改进了很多东西(尽管它与lock相比仍然有2倍的开销)。 在这一点上,我会说放弃自定义代码,只使用较新的ReaderWriterLockSlim类。

但是,值得让我解释一下自定义的SpinLock代码。

  • Interlocked.CompareExchange是.NET的CAS操作版本。 它是最基本的同步原语。 你可以从这个单独的操作构建其他所有东西,包括你自己的自定义类似于Monitor的类,读写器锁等。显然,这里使用它来创建自旋锁。
  • Thread.Sleep(0)产生任何处理器上具有相同或更高优先级的任何线程。
  • Thread.Sleep(1)产生任何处理器上的任何线程。
  • Thread.SpinWait将线程置于指定迭代次数的紧密循环中。

虽然它没有在您发布的代码中使用,但还有另一种有用的机制来创建自旋锁(或其他低锁策略)。

  • Thread.Yield产生于同一处理器上的任何线程。

Microsoft在其高度并发的同步机制和集合中使用所有这些调用。 如果你反编译SpinLockSpinWaitManualResetEventSlim等,你会看到一个相当复杂的歌曲和舞蹈继续这些调用……比你发布的代码复杂得多。

再次,抛弃自定义代码,只使用ReaderWriterLockSlim而不是自定义FastReaderWriterLock类。


顺便说一句, this.lockHeld != null应该产生编译器警告,因为lockHeld是一个值类型。