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は一般に、重くて遅いカーネルモードの信号に依存するのではなく、待機中のスレッドをスリープ状態にしない(タイトなループでチェック条件で何度も循環する-「ママはもうそこにいますか? 」と考えてください)ロックの形式です。これらは通常、予想される待機時間が非常に短く、従来のロックの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 Frameworkで作業している場合、または古いバージョンから移行されたコードで作業している場合は、誰かが書いた可能性があります。独自の実装。

質問に答えるには: .NET 4.0以降で新しいコードを作成する場合は、組み込みのSpinLockを使用する必要があります。3.5以前のコードの場合、特にNesperを拡張している場合は、この実装は十分にテストされており、適切であると私は主張します。上記の例のように、スレッドが待機する時間が非常に短いことがわかっている場合にのみ、SpinLockを使用してください。

編集:あなたの実装はNesper-EsperCEPライブラリの.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が.NETFramework4よりずっと前に存在していたことを確認できるので、自家製のSpinLockの必要性を説明しています。

于 2012-05-09T03:01:58.697 に答える
1

元の作成者は のより高速なバージョンを望んでいたようですReaderWriterLock。この古いクラスは痛々しいほど遅かったです。私自身のテスト (私がずっと前に行った) は、RWL のオーバーヘッドが普通の古い .xml の最大 8 倍であることを示していlockます。ReaderWriterLockSlim大幅に改善されました (ただし、 に比べてオーバーヘッドが最大 2 倍になりますlock)。この時点で、カスタム コードを捨てて、新しいReaderWriterLockSlimクラスを使用するだけです。

SpinLockしかし、その価値があるので、そのカスタムコードのいくつかを説明させてください。

  • Interlocked.CompareExchangeCAS操作の .NET バージョンです。これは最も基本的な同期プリミティブです。独自のカスタムのようなクラス、リーダー ライター ロックなどを含む、この 1 つの操作から文字通り他のすべてを構築できますMonitor。明らかに、ここではスピン ロックを作成するために使用されました。
  • Thread.Sleep(0)任意のプロセッサで同じかそれ以上の優先度を持つ任意のスレッドに譲ります。
  • Thread.Sleep(1)任意のプロセッサ上の任意のスレッドに譲ります。
  • Thread.SpinWait指定された反復回数の間、スレッドをタイトなループに入れます。

投稿したコードでは使用されていませんが、スピン ロック (または他の低ロック戦略) を作成するための別の便利なメカニズムがあります。

  • Thread.Yield同じプロセッサ上の任意のスレッドに譲ります。

Microsoft は、高度な同時同期メカニズムとコレクションでこれらすべての呼び出しを使用します。SpinLockSpinWait、などを逆コンパイルManualResetEventSlimすると、これらの呼び出しでかなり複雑な歌と踊りが行われていることがわかります...投稿したコードよりもはるかに複雑です。

繰り返しますが、カスタム コードを捨てて、そのカスタムクラスReaderWriterLockSlimの代わりに使用してください。FastReaderWriterLock


ちなみに、は値型であるthis.lockHeld != nullため、コンパイラの警告を生成する必要があります。lockHeld

于 2012-05-09T03:29:43.597 に答える