繁体   English   中英

什么时候应该&不应该使用这个C#实用程序类来通过Interlocked控制线程

[英]When should & shouldn't I use this C# utility class to control threads via 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 <= 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();
        }
    }
}
    }

一般来说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();

在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 这个老班很痛苦。 我自己的测试(我很久以前做过)表明RWL的开销是普通旧lock 8倍。 ReaderWriterLockSlim改进了很多东西(尽管它与lock相比仍然有2倍的开销)。 在这一点上,我会说放弃自定义代码,只使用较新的ReaderWriterLockSlim类。

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

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

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

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

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

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


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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM