3

我试图了解如何编写此类背后的逻辑,以及何时应该和不应该使用它。任何见解将不胜感激

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

2 回答 2

4

SpinLocks通常是一种保持等待线程唤醒的锁形式(在紧密循环中反复循环检查条件 - 想想“妈妈我们到了吗? ”)而不是依赖于更重、更慢的内核模式信号。它们通常用于预期等待时间非常短的情况,在这种情况下,它们的性能优于为传统锁创建和等待操作系统句柄的开销。虽然它们比传统锁产生更多的 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 之前就已经存在,这就解释了对自旋自旋锁的需求。

于 2012-05-09T03:01:58.697 回答
1

看来原作者想要一个更快的ReaderWriterLock. 这个老班慢得令人痛苦。我自己的测试(我很久以前做过)表明 RWL 的开销是普通 old 的 8 倍左右lockReaderWriterLockSlim改进了很多东西(尽管与 相比,它仍然有 ~ 2 倍的开销lock)。在这一点上,我会说放弃自定义代码并使用较新的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它是一个值类型。

于 2012-05-09T03:29:43.597 回答