27

このSystem.Threading.Interlockedオブジェクトでは、アトミック操作として加算 (減算) と比較が可能です。等価性だけでなく、アトミックな比較として GreaterThan/LessThan も行う CompareExchange は非常に価値があるようです。

仮説上Interlocked.GreaterThanの IL の機能ですか、それとも CPU レベルの機能ですか? 両方?

他のオプションがない場合、C++ またはダイレクト IL コードでそのような機能を作成し、その機能を C# に公開することは可能ですか?

4

7 に答える 7

57

から他のアトミック操作を構築InterlockedCompareExchangeできます。

public static bool InterlockedExchangeIfGreaterThan(ref int location, int comparison, int newValue)
{
    int initialValue;
    do
    {
        initialValue = location;
        if (initialValue >= comparison) return false;
    }
    while (System.Threading.Interlocked.CompareExchange(ref location, newValue, initialValue) != initialValue);
    return true;
}
于 2012-10-24T20:09:46.380 に答える
3

この実装についてどう思いますか:

// this is a Interlocked.ExchangeIfGreaterThan implementation
private static void ExchangeIfGreaterThan(ref long location, long value)
{
    // read
    long current = Interlocked.Read(ref location);
    // compare
    while (current < value)
    {
        // set
        var previous = Interlocked.CompareExchange(ref location, value, current);
        // if another thread has set a greater value, we can break
        // or if previous value is current value, then no other thread has it changed in between
        if (previous == current || previous >= value) // note: most commmon case first
            break;
        // for all other cases, we need another run (read value, compare, set)
        current = Interlocked.Read(ref location);
    }
}
于 2012-11-10T15:05:05.853 に答える
3

ここで行った後の投稿の更新: 追加のロック オブジェクトを使用して、より優れた比較を行うためのより良い方法を見つけました。ロックとインターロックを一緒に使用できることを検証するために、多くの単体テストを作成しましたが、これは一部のケースのみです。

コードの仕組み: Interlocked は、読み取りまたは書き込みがアトミックであるメモリ バリアを使用します。大なり比較をアトミック操作にするためには、同期ロックが必要です。したがって、現在のルールは、このクラス内では、この同期ロックなしで他の操作が値を書き込むことはありません。

このクラスで得られるのは、非常に高速に読み取ることができるインターロックされた値ですが、書き込みにはもう少し時間がかかります。このアプリケーションでは、読み取りが約 2 ~ 4 倍高速です。

ビューとしてのコードは次のとおりです。

ここを参照してください: http://files.thekieners.com/blogcontent/2012/ExchangeIfGreaterThan2.png

ここにコピー&ペーストするコードとして:

public sealed class InterlockedValue
{
    private long _myValue;
    private readonly object _syncObj = new object();

    public long ReadValue()
    {
        // reading of value (99.9% case in app) will not use lock-object, 
        // since this is too much overhead in our highly multithreaded app.
        return Interlocked.Read(ref _myValue);
    }

    public bool SetValueIfGreaterThan(long value)
    {
        // sync Exchange access to _myValue, since a secure greater-than comparisons is needed
        lock (_syncObj)
        {
            // greather than condition
            if (value > Interlocked.Read(ref  _myValue))
            {
                // now we can set value savely to _myValue.
                Interlocked.Exchange(ref _myValue, value);
                return true;
            }
            return false;
        }
    }
}
于 2012-11-15T06:46:51.470 に答える
2

これは実際には正しくありませんが、同時実行性には次の 2 つの形式があると考えると便利です。

  1. ロックフリー同時実行
  2. ロックベースの同時実行

ソフトウェア ロック ベースの同時実行性は、スタック上のどこか (多くの場合、カーネル) でロック フリーのアトミック命令を使用して実装されるため、これは正しくありません。ただし、ロック フリーのアトミック命令はすべて、最終的にメモリ バスでハードウェア ロックを取得することになります。したがって、実際には、ロックフリーの同時実行とロックベースの同時実行は同じです。

しかし、概念的には、ユーザー アプリケーションのレベルでは、これらは 2 つの異なる方法です。

ロック ベースの同時実行は、コードの重要なセクションへのアクセスを "ロック" するという考えに基づいています。1 つのスレッドがクリティカル セクションを "ロック" すると、他のスレッドは同じクリティカル セクション内でコードを実行できなくなります。これは通常、「ミューテックス」を使用して行われます。このミューテックスは、OS スケジューラと連携し、ロックされたクリティカル セクションに入るのを待機している間、スレッドを実行不能にします。もう 1 つのアプローチは、クリティカル セクションが使用可能になるまで、スレッドをループ内でスピンさせ、何の役にも立たない「スピン ロック」を使用することです。

ロックフリー同時実行は、ハードウェアによってアトミックに実行されることが保証されているアトミック命令 (特に CPU によってサポートされている) を使用するという考えに基づいています。Interlocked.Increment は、ロックフリー同時実行の良い例です。アトミックインクリメントを行う特別な CPU 命令を呼び出すだけです。

ロックフリーの同時実行は困難です。クリティカル セクションの長さと複雑さが増すにつれて、特に難しくなります。クリティカル セクションの任意のステップは、一度に任意の数のスレッドで同時に実行でき、非常に異なる速度で移動できます。それにもかかわらず、システム全体の結果が正しいままであることを確認する必要があります。インクリメントのようなものは単純です (cs は 1 つの命令にすぎません)。より複雑なクリティカル セクションでは、物事が非常に複雑になる可能性があります。

ロックベースの同時実行も困難ですが、ロックフリーの同時実行ほど困難ではありません。コードの任意の複雑な領域を作成し、いつでも 1 つのスレッドだけがそれを実行していることを知ることができます。

ただし、ロックフリーの同時実行には 1 つの大きな利点があります。速度です。正しく使用すると、ロックベースの同時実行よりも桁違いに高速になる可能性があります。スピン ループは、何もせずに CPU リソースを浪費するため、長時間実行されるクリティカル セクションには適していません。ミューテックスは、多くのオーバーヘッドが発生するため、小さなクリティカル セクションには適していません。最低でもモードの切り替えが必要で、最悪の場合は複数のコンテキストの切り替えが必要です。

マネージド ヒープの実装を検討してください。「new」が呼び出されるたびにOSを呼び出すのは恐ろしいことです。アプリのパフォーマンスを損なう可能性があります。ただし、ロックフリーの同時実行を使用すると、インターロックされたインクリメントを使用してジェネレーション 0 のメモリ割り当てを実装することができます (CLR がそれを行うかどうかはわかりませんが、そうでない場合は驚くでしょう。それは巨大になる可能性があります)。節約。

永続的なスタックや avl ツリーなど、ロックのないデータ構造など、他の用途もあります。彼らは通常「cas」(比較と交換)を使用します。

ただし、ロックベースの同時実行とロックフリーの同時実行が実際に同等である理由は、それぞれの実装の詳細によるものです。

スピンロックは通常、ループ条件でアトミック命令 (通常は cas) を使用します。ミューテックスは、実装時にスピン ロックまたは内部カーネル構造のアトミック更新のいずれかを使用する必要があります。

アトミック命令は、ハードウェア ロックを使用して実装されます。

いずれにせよ、どちらにも一連のトレードオフがあり、通常はパフォーマンスと複雑さが中心です。ミューテックスは、ロック フリー コードよりも速くも遅くもなります。ロック フリー コードは、mutex よりも複雑な場合もあれば、複雑でない場合もあります。使用する適切なメカニズムは、特定の状況によって異なります。

さて、あなたの質問に答えるために:

未満の場合にインターロック比較交換を行ったメソッドは、ロックを使用していないことを呼び出し元に暗示します。インクリメントや比較交換と同じように、単一の命令で実装することはできません。ループ内でインターロックされた比較交換を使用して、(より少ない計算をするために) 減算を実行してシミュレートできます。ミューテックスを使用して行うこともできます (ただし、これはロックを意味するため、名前に「インターロック」を使用すると誤解を招く可能性があります)。「シミュレートされたインターロック経由のcas」バージョンを構築するのは適切ですか? 場合によります。コードが非常に頻繁に呼び出され、スレッドの競合がほとんどない場合、答えはイエスです。そうでない場合は、適度に高い定数係数を使用した O(1) 操作を無限 (または非常に長い) ループに変えることができます。この場合、ミューテックスを使用することをお勧めします。

ほとんどの場合、それだけの価値はありません。

于 2012-10-24T03:28:40.143 に答える
1

すべてのインターロックされた操作は、ハードウェアで直接サポートされています。

インターロックされた操作とアトミックデータ型は異なります。アトミック型はライブラリレベルの機能です。一部のプラットフォームおよび一部のデータ型では、アトミックはインターロックされた命令を使用して実装されます。この場合、それらは非常に効果的です。

プラットフォームにインターロックされた操作がまったくない場合、または特定のデータ型で使用できない場合、ライブラリは適切な同期(crit_sect、mutexなど)を使用してこれらの操作を実装します。

Interlocked.GreaterThan本当に必要かどうかはわかりません。それ以外の場合は、すでに実装されている可能性があります。あなたがそれが役立つことができる良い例を知っているなら、私はここの誰もがこれを聞いて喜んでいると確信しています。

于 2012-10-24T02:17:14.860 に答える
0

より大きい/より小さい、等しいは、すでに不可分操作です。これは、アプリケーションの安全な並行動作には対応していません。

それらをInterlockedファミリーの一部にすることには意味がないので、問題は、実際に何を達成しようとしているのかということです。

于 2012-10-24T02:17:04.643 に答える