1

重複の可能性:
Interlocked.CompareExchange<Int> が等値の代わりに GreaterThan または LessThan を使用している

Interlocked.CompareExchange は、値と被比較対象が等しい場合にのみ値を交換することを知ってい
ます.このようなことを達成するために等しくない場合にそれらを交換するにはどうすればよいですか?

if (Interlocked.CompareExchange(ref count, count + 1, max) != max)
    // i want it to increment as long as it is not equal to max
        {
           //count should equal to count + 1
        }
4

2 に答える 2

6

マークが投稿したもののより効率的 (バスのロックと読み取りが少ない) で単純化された実装:

static int InterlockedIncrementAndClamp(ref int count, int max)
{
    int oldval = Volatile.Read(ref count), val = ~oldval;

    while(oldval != max && oldval != val)
    {   
        val = oldval;
        oldval = Interlocked.CompareExchange(ref count, oldval + 1, oldval);
    }

    return oldval + 1;
}

競合が非常に多い場合は、一般的なケースを単一のアトミック インクリメント命令に減らすことで、スケーラビリティをさらに改善できる可能性があります。CompareExchange と同じオーバーヘッドですが、ループの可能性はありません。

static int InterlockedIncrementAndClamp(ref int count, int max, int drift)
{
    int v = Interlocked.Increment(ref count);

    while(v > (max + drift))
    {
        // try to adjust value.
        v = Interlocked.CompareExchange(ref count, max, v);
    }

    return Math.Min(v, max);
}

ここでは、を超える値countまで上げることができます。しかし、まだ までしか戻りません。これにより、ほとんどの場合、オペレーション全体を 1 つのアトミック インクリメントに折りたたむことができ、最大のスケーラビリティが可能になります。値を超えた場合にのみ複数の操作が必要になります。これは、非常にまれにするのに十分な大きさにすることができます。driftmaxmaxdrift

インターロックメモリアクセスと非インターロックメモリアクセスが連携して動作することについての Marc の懸念に応えて:

具体的にはvolatilevs Interlocked:volatileは通常のメモリ操作ですが、最適化されていないものであり、他のメモリ操作に関して並べ替えられていないものです。この特定の問題は、これらの特定のプロパティのいずれにも関係していないため、実際には、非インターロック対インターロックの相互運用性について話している.

.NET メモリ モデルは、基本的な整数型 (マシンのネイティブ ワード サイズまで) の読み取りと書き込みを保証し、参照はアトミックです。Interlocked メソッドもアトミックです。.NET には「アトミック」の定義が 1 つしかないため、相互に互換性があることを明示的に特殊なケースにする必要はありません。

Volatile.Read保証されていないことの 1 つは、可視性です。常にロード命令を取得しますが、CPU は、別の CPU によってメモリに置かれたばかりの新しい値ではなく、ローカル キャッシュから古い値を読み取る可能性があります。MOVNTPSx86 では、ほとんどの場合 (例外のような特別な命令)、これについて心配する必要はありませんが、他のアーキテクチャでは非常に可能性があります。

要約すると、これは に影響を与える可能性のある 2 つの問題を説明していVolatile.Readます。まず、16 ビット CPU で実行しているint可能性があります。第二に、アトミックであっても、可視性のために古い値を読み取っている可能性があります。

しかし、影響Volatile.Readを与えるということは、それらがアルゴリズム全体に影響を与えるという意味ではなく、これらから完全に保護されています。

最初のケースは、非アトミックな方法で同時に書き込みを行っている場合にのみ、私たちを悩ませます。countこれは、(A[0] を書き込む; CAS A[0:1] を書き込む; A[1] を書き込む) という結果になる可能性があるためです。書き込みはすべてcount保証されたアトミック CAS で行われるため、これは問題ではありません。読んでいるときに間違った値を読み取ると、次の CAS でキャッチされます。

考えてみると、2 番目のケースは実際には、読み取りと書き込みの間で値が変化する通常のケースの特殊化にすぎません。読み取りは、要求する前に行われます。この場合、最初のInterlocked.CompareExchange呼び出しは与えられたものとは異なる値を報告し、Volatile.Read成功するまでループを開始します。

Volatile.Read必要に応じて、競合が少ない場合の純粋な最適化と考えることができます。で初期化できoldval0それでも問題なく動作します。を使用Volatile.Readすると、2 つではなく 1 つの CAS のみを実行する可能性が高くなります (これは、命令が進むにつれて、特にマルチ CPU 構成では非常にコストがかかります)。

しかし、そうです、Marc が言うように、時にはロックの方がシンプルな場合もあります!

于 2012-11-25T23:46:50.570 に答える
4

ただし、「等しくない場合の比較」はありません。最初に値を自分でテストし、スレッド競合が発生しない場合にのみ更新を行うことができます。これは、多くの場合、2 番目のテストが失敗した場合にループする必要があることを意味します。擬似コード:

bool retry;
do {
    retry = false;
    // get current value
    var val = Interlocked.CompareExchange(ref field, 0, 0);
    if(val != max) { // if not maxed
        // increment; if the value isn't what it was above: redo from start
        retry = Interlocked.CompareExchange(ref field, val + 1, val) != val;
    }        
} while (retry);

しかし、率直に言って、ロックの方が簡単です。

于 2012-11-25T23:28:11.747 に答える