简体   繁体   English

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

[英]When should & shouldn't I use this C# utility class to control threads via Interlocked

I'm trying to understand the logic behind how this class was written, and when I should and shouldn't use it. 我试图理解这个类的编写背后的逻辑,以及何时应该而且不应该使用它。 Any insight would be appreciated 任何见解将不胜感激

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

Update: I found a sample usage in my source code... This indicates how to use the above object, though I don't understand "why" 更新:我在源代码中找到了一个示例用法...这表明如何使用上面的对象,虽然我不明白“为什么”

    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 in general are a form of lock that keeps waiting threads awake (cycling over and over on a check condition in a tight loop- think " Mommy are we there yet? ") rather than relying on heavier, slower, kernel mode signals. 一般来说SpinLocks是一种锁定形式,可以让等待的线程保持清醒状态(在紧密的循环中反复循环 - 想想“ 妈咪我们还在那里吗? ”)而不是依靠更重,更慢的内核模式信号。 They are usually intended for situations where the expected wait time is very short, where they outperform the overhead of creating and waiting on an OS handle for a traditional lock. 它们通常用于预期等待时间非常短的情况,其中它们优于创建和等待传统锁的OS句柄的开销。 They incur more CPU cost than a traditional lock though, so for more than a very short wait time a traditional lock (like the Monitor class or the various WaitHandle implementations) is preferred. 虽然它们比传统锁具有更高的CPU成本,但是对于超过非常短的等待时间,传统的锁(如Monitor类或各种WaitHandle实现)是首选。

This short wait time concept is demonstrated in your code above: 上面的代码演示了这个短暂的等待时间概念:

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

There is a perfectly good SpinLock built into the BCL , however it is only in v4.0+, so if you are working in an older version of the .NET framework or on code that was migrated from an older version, someone may have written their own implementation. 在BCL中内置了一个非常好的SpinLock,但它只在v4.0 +中,所以如果你使用的是旧版本的.NET框架,或者是从旧版本迁移的代码,有人可能会写他们自己的实施。

To answer your question: You should use the built-in SpinLock if you are writing new code on .NET 4.0 or higher. 回答你的问题:如果你在.NET 4.0或更高版本上编写新代码,你应该使用内置的SpinLock。 For code on 3.5 or older, especially if you are extending Nesper, I'd argue that this implementation is time-tested and appropriate. 对于3.5或更早的代码,特别是如果你扩展Nesper,我认为这个实现是经过时间考验和适当的。 Only use a SpinLock where you know that the time a thread may wait on it is very small, as in the example above. 只使用一个SpinLock,你知道线程可能等待的时间非常小,如上例所示。

EDIT: Looks like your implementation came from Nesper- the .NET port of the Esper CEP library: 编辑:看起来你的实现来自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/SpinLock.cs

and

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

I can confirm that Nesper existed long before the .NET framework 4, so that explains the need for a home-spun SpinLock. 我可以确认Nesper早在.NET框架4之前就已存在,因此这解释了对自制SpinLock的需求。

It appears that the original author wanted a faster version of ReaderWriterLock . 原作者似乎想要一个更快版本的ReaderWriterLock This old class was painfully slow. 这个老班很痛苦。 My own tests (which I did a long time ago) indicates that RWL had ~8x the overhead of a plain old lock . 我自己的测试(我很久以前做过)表明RWL的开销是普通旧lock 8倍。 ReaderWriterLockSlim improved things quite a bit (though it still has ~2x the overhead as compared to lock ). ReaderWriterLockSlim改进了很多东西(尽管它与lock相比仍然有2倍的开销)。 At this point I would say ditch the custom code and just use the newer ReaderWriterLockSlim class. 在这一点上,我会说放弃自定义代码,只使用较新的ReaderWriterLockSlim类。

But, for what it is worth let me explain some of that custom SpinLock code. 但是,值得让我解释一下自定义的SpinLock代码。

  • Interlocked.CompareExchange is .NET's version of a CAS operation. Interlocked.CompareExchange是.NET的CAS操作版本。 It is the most fundamental synchronization primitive. 这是最根本同步原语。 You can literally build everything else from this single operation including your own custom Monitor -like class, reader writer locks, etc. Obviously it was used here to create a spin lock. 您可以从这个单一操作构建其他所有内容,包括您自己的自定义类似于Monitor的类,读取Monitor编写器锁等。显然,这里使用它来创建自旋锁。
  • Thread.Sleep(0) yields to any thread of with the same or higher priority on any processor. Thread.Sleep(0)产生任何处理器上具有相同或更高优先级的任何线程。
  • Thread.Sleep(1) yields to any thread on any processor. Thread.Sleep(1)产生任何处理器上的任何线程。
  • Thread.SpinWait puts the thread into a tight loop for the specified number of iterations. Thread.SpinWait将线程置于指定迭代次数的紧密循环中。

Although it was not used in the code you posted there is another useful mechanism for creating spin locks (or other low lock strategies). 虽然它没有在您发布的代码中使用,但还有另一种有用的机制来创建自旋锁(或其他低锁策略)。

  • Thread.Yield yields to any thread on the same processor. Thread.Yield产生于同一处理器上的任何线程。

Microsoft uses all of these calls in their highly concurrent synchronization mechanisms and collections. Microsoft在其高度并发的同步机制和集合中使用所有这些调用。 If you decompile SpinLock , SpinWait , ManualResetEventSlim , etc. you will see a fairly complex song-and-dance going on with these calls...much more complex than the code you posted. 如果您反编译SpinLockSpinWaitManualResetEventSlim等,您将看到一个相当复杂的歌曲和舞蹈正在进行这些调用......比您发布的代码复杂得多。

Again, ditch the custom code and just use ReaderWriterLockSlim instead of that custom FastReaderWriterLock class. 再次抛弃自定义代码,只使用ReaderWriterLockSlim而不是自定义FastReaderWriterLock类。


By the way, this.lockHeld != null should produce a compiler warning since lockHeld is a value type. 顺便说一句, this.lockHeld != null应该产生一个编译器警告,因为lockHeld是一个值类型。

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

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