2

次のようなステートメントがある場合はどうすればよいでしょうか。

private int sharedValue = 0;

public void SomeMethodOne()
{
   lock(this){sharedValue++;}
}

public void SomeMethodTwo()
{
   lock(this){sharedValue--;}
}

そのため、スレッドがロック状態になるには、まず別のスレッドがそのスレッドで動作しているかどうかを確認する必要があります。そうでない場合は、メモリに入ることができ、何かをメモリに書き込む必要があります。これは、読み取りと書き込みが必要なため、アトミックではありません。

では、一方のスレッドがロックを読み取っているときに、もう一方のスレッドがその所有権をロックに書き込むことができないのはなぜでしょうか?

簡単に説明すると、なぜ 2 つのスレッドが両方とも同時にロックできないのでしょうか?

4

2 に答える 2

2

基本的にロックの仕組みを尋ねているようです。ロックが構築されていない状態で、どうすればロックがアトミックな方法で内部状態を維持できるでしょうか? 最初は鶏が先か卵が先かの問題のように思えませんか?

この魔法はすべて、コンペア アンド スワップ(CAS) 操作によって実現されます。CAS 操作は、2 つの重要な処理を行うハードウェア レベルの命令です。

  • メモリ バリアが生成されるため、命令の並べ替えが制限されます。
  • メモリアドレスの内容を別の値と比較し、それらが等しい場合は元の値を新しい値に置き換えます。これはすべてアトミックに行われます。

最も基本的なレベルでは、これがトリックの達成方法です。別のスレッドが書き込み中に、他のすべてのスレッドが読み取りをブロックされているわけではありません。それはそれについて考える完全に間違った方法です。実際に起こることは、すべてのスレッドが同時にライターとして機能することです。戦略は悲観的よりも楽観的です。すべてのスレッドは、CAS と呼ばれるこの特殊な種類の書き込みを実行することによって、ロックを取得しようとします。実際には、Interlocked.CompareExchange(ICX) メソッドを介して .NET の CAS 操作にアクセスできます。すべての同期プリミティブは、この 1 つの操作から構築できます。

Monitorようなクラス (lockキーワードが舞台裏で使用するクラス) を完全に C# でゼロから作成する場合は、Interlocked.CompareExchangeメソッドを使用してそれを行うことができます。これは非常に単純化された実装です。これは、.NET Framework が行う方法ではないことに注意してください。1以下のコードを提示する理由は、舞台裏で CLR マジックを必要とせずに純粋な C# コードで実行する方法を示し、Microsoft がそれをどのように実装したかについて考えさせるためです

public class SimpleMonitor
{
    private int m_LockState = 0;

    public void Enter()
    {
        int iterations = 0;
        while (!TryEnter())
        {
            if (iterations < 10) Thread.SpinWait(4 << iterations);
            else if (iterations % 20 == 0) Thread.Sleep(1);
            else if (iterations % 5 == 0) Thread.Sleep(0);
            else Thread.Yield();
            iterations++;
        }
    }

    public void Exit()
    {
        if (!TryExit())
        {
            throw new SynchronizationLockException();
        }
    }

    public bool TryEnter()
    {
        return Interlocked.CompareExchange(ref m_LockState, 1, 0) == 0;
    }

    public bool TryExit()
    {
        return Interlocked.CompareExchange(ref m_LockState, 0, 1) == 1;
    }
}

この実装は、いくつかの重要なことを示しています。

  • これは、ICX 操作を使用してロック状態をアトミックに読み取りおよび書き込みする方法を示しています。
  • 待機がどのように発生するかを示します。

ロックが取得されるのを待っている間にThread.SpinWait、 、Thread.Sleep(0)Thread.Sleep(1)およびをどのように使用したかに注意してください。Thread.Yield待機戦略は非常に単純化されていますが、BCL で既に実装されている実際のアルゴリズムに近似しています。Enter上記の方法では、重要なビットを見つけやすくするために、意図的にコードを単純にしました。これは、私が通常これを実装する方法ではありませんが、重要な点を理解してくれることを願っています。

SimpleMonitorまた、上記には多くの問題があることに注意してください。ここに挙げたのはほんの一部です。

  • ネストされたロックは処理しません。
  • 実際のクラスのようWaitPulseメソッドは提供しません。Monitorそれらを正しく行うのは本当に難しいです。

1 CLR は、各参照型に存在する特別なメモリ ブロックを実際に使用します。このメモリブロックは「同期ブロック」と呼ばれます。はMonitor、このメモリ ブロック内のビットを操作して、ロックを取得および解放します。このアクションには、カーネル イベント オブジェクトが必要な場合があります。詳細については、Joe Duffy のブログを参照してください。

于 2013-10-28T02:10:47.480 に答える
1

lockC# では、Monitor実際にロックに使用されるオブジェクトを作成するために使用されます。

詳細についてMonitorは、http: //msdn.microsoft.com/en-us/library/system.threading.monitor.aspxを参照してください。のEnter方法では、Monitor一度に 1 つのスレッドだけがクリティカル セクションに入ることができます。

オブジェクトのロックを取得します。このアクションは、クリティカル セクションの開始も示します。別のロックされたオブジェクトを使用してクリティカル セクション内の命令を実行していない限り、他のスレッドはクリティカル セクションに入ることができません。

thisところで、 ( )でのロックは避ける必要がありますlock(this)。クリティカル セクションを保護するには、クラス (静的または非静的) でプライベート変数を使用する必要があります。上記と同じリンクで詳細を読むことができますが、その理由は次のとおりです。

同期するオブジェクトを選択するときは、プライベート オブジェクトまたは内部オブジェクトのみをロックする必要があります。外部オブジェクトをロックすると、デッドロックが発生する可能性があります。これは、無関係なコードが同じオブジェクトを選択して別の目的でロックする可能性があるためです。

于 2013-10-28T01:27:29.647 に答える