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

时间:2012-05-09 02:33:04

标签: c# multithreading interlocked spinlock multiprocessor

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

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

2 个答案:

答案 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

https://svn.codehaus.org/esper/esper/tagsnet/release_1.12.0_beta_1/trunk/NEsper/compat/FastReaderWriterLock.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在其高度并发的同步机制和集合中使用所有这些调用。如果你反编译SpinLockSpinWaitManualResetEventSlim等等,你会看到一个相当复杂的歌曲和舞蹈正在进行这些调用......比你发布的代码复杂得多

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


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