2009-09-09 8 views
18

私は、Threading.Interlockedクラスが提供するアトミック性を十分に理解しています。私は理解していませんが、なぜAdd関数が2つのオーバーロードしか提供しないのですか?1つはIntegers用、もう1つはLongs用です。 Doublesやそれ以外の数値型はなぜですか?Doublesをパラメータとして受け入れるInterlocked.Addのオーバーロードがないのはなぜですか?

明らかに、Doubleを変更する意図した方法はCompareExchangeです。 Doubleを変更するのはIntegerを変更するよりも複雑な操作なので、これをお勧めします。なぜなら、なぜCompareExchangeとAddが両方ともIntegerを受け入れることができるのであれば、両方ともDoublesを受け入れることができないのはなぜか分かりません。

答えて

24

Interlockedクラスは、Windows API Interlocked **関数をラップします。

これらは、x86用のLOCK命令プレフィックスを使用して、ネイティブプロセッサAPIをラップします。

BT、BTS、BTR、BTC、XCHG、XADD、ADC、SBB、AND、SUBを追加し、OR、XOR、NOT、NEG、INC、DEC

:それだけで次の手順を前置サポートしています

これらは、インターロックされたメソッドにマップされていることにも注意してください。残念ながら、非整数型のADD関数はここではサポートされていません。 64ビットのプラットフォームでは、64ビット長の追加がサポートされています。

ここには素晴らしい記事discussing lock semantics on the instruction levelがあります。

+2

リンクはもうアクティブではありません。ここにインターネットアーカイブからのバックアップがありますhttps://web.archive.org/web/20160319061137/http://www.codemaestro.com/reviews/8 –

-1

アダム・ロビンソンが指摘したように、Int64型を取るInterlocked.Incrementためのオーバーロードがありますが、ノート:

Readメソッドと増分の64ビット 過負荷、デクリメント、 とメソッドを追加します。 System.IntPtr の長さが64ビットであるシステムでは、実際にはアトミックなアトミックなものです。他のシステムでは、 これらの方法は、互いに に関して原子であるが、データにアクセスする他の手段に関しては原子ではない。 したがって、32ビットの システムでスレッドセーフであるためには、 の64ビット値へのアクセスは、Interlockedクラスの のメンバーを介して行わなければなりません。

1

私には2つの理由があると考えられます。

  1. .Netが対象とするプロセッサは、整数型のインターロック付きインクリメントのみをサポートします。私はこれがx86上のLOCKプレフィックスだと考えています。他のプロセッサにはおそらく同様の命令が存在します。
  2. 浮動小数点数に1を加算すると、それが十分大きい場合に同じ数が返される可能性があります。そのため、増分を呼び出すことができるかどうかはわかりません。おそらくフレームワークの設計者は、この場合直観的でない動作を避けようとしています。
+0

あなたの2番目の点について:はい、私はInterlocked.Adcdについて質問していますが、Interlocked.Incrementではありません。 –

7

Reed Copseyによると、Interlocked操作は(Windows API関数を介して)x86/x64プロセッサーによって直接サポートされる命令にマップされます。これらの関数の1つがXCHGであるとすれば、ターゲットの位置のビットが何を表しているかを実際に気にせずにアトミックなXCHG演算を行うことができます。つまり、交換している64ビットの浮動小数点数が実際には64ビットの整数で、XCHG命令ではその違いを知ることはできません。したがって、.Netはインターロックを提供することができます。floatとdoubleの交換関数は、それぞれ整数とlong整数であると "偽装"することによって機能します。

しかしながら、他の操作のすべてが実際に宛先の個々のビットで動作するか、および値は、実際の整数表現しない限り、それらは動作しません(いくつかの場合には、ビット列を。)

21

その他ました「なぜ?」と対処した。しかしCompareExchangeプリミティブを使用して、独自のAdd(ref double, double)をロールバックすることは簡単です:

public static double Add(ref double location1, double value) 
{ 
    double newCurrentValue = location1; // non-volatile read, so may be stale 
    while (true) 
    { 
     double currentValue = newCurrentValue; 
     double newValue = currentValue + value; 
     newCurrentValue = Interlocked.CompareExchange(ref location1, newValue, currentValue); 
     if (newCurrentValue == currentValue) 
      return newValue; 
    } 
} 

CompareExchangeは、現在の値がcurrentValueに等しい場合、newValueするlocation1の値を設定します。スレッドセーフなアトミックな方法で実行するので、ロックに頼らずに、それだけで頼りにすることができます。

なぜwhile (true)ループですか?このようなループは、楽観的並列アルゴリズムを実装する際の標準です。現在の値がcurrentValueと異なる場合、CompareExchangelocation1に変更されません。私はcurrentValuelocation1を初期化しました - 不揮発性の読み取りを行っています(古くなっているかもしれませんが、値はCompareExchangeでチェックされます)。現在の値(まだ)がlocationから読み取られた場合、CompareExchangenewValueに値を変更します。そうでない場合は、CompareExchangeによって返される新しい現在の値でCompareExchangeを再試行する必要があります。

もう一度値を変更すると次のCompareExchangeの時刻まで別のスレッドで値が変更されますが、もう一度やり直す必要があります。これは理論上永遠に続くことができます。値が複数のスレッドから常に変更されていない限り、CompareExchangeは、現在の値がまだ不揮発性の読み取り値であるlocation1が得られた場合、または異なる場合は2度だけ呼び出される可能性が最も高いです。

+0

これはもう少し説明できますか?なぜループするのですか? CompareExchangeの役割は何ですか? – Mzn

+1

@Mzn私の答えに追加されました。それがそれを説明することを望む。 –

+2

これは優れた答えです!少なくとも今、私は、楽観的な並行性が意味することを知っていると思います。「物事は最終的には正しいことを前提とした並行コードの作成」私たちがwhile(true)の場合、これは永遠にループしないことを意味します(私たちは合理的に楽観的です)。どうもありがとう! – Mzn

関連する問題