12

アトミック命令とはどういう意味ですか?

以下はどのようにして Atomic になりますか?

TestAndSet

int TestAndSet(int *x){
   register int temp = *x;
   *x = 1;
   return temp;
}

ソフトウェアの観点から、ノンブロッキング同期プリミティブを使用したくない場合、どうすれば命令の原子性を確保できますか? ハードウェアでのみ可能ですか、それともアセンブリ レベルのディレクティブの最適化を使用できますか?

4

5 に答える 5

14

一部のマシン命令は本質的にアトミックです。たとえば、ネイティブ プロセッサのワード サイズの適切にアラインされた値の読み取りと書き込みは、多くのアーキテクチャでアトミックです。

これは、ハードウェア割り込み、他のプロセッサ、およびハイパースレッドが読み取りまたは格納を中断したり、同じ場所への部分的な値の読み取りまたは書き込みを行うことができないことを意味します。

アトミックな読み取りと書き込みなどのより複雑なことは、明示的なアトミック マシン命令 (x86 の LOCK CMPXCHG など) によって実現できます。

ロックおよびその他の高レベルの構造は、これらのアトミック プリミティブに基づいて構築されており、通常は単一のプロセッサ ワードのみを保護します。

いくつかの巧妙な並行アルゴリズムは、ポインターの読み取りと書き込みだけを使用して構築できます。たとえば、単一のリーダーとライターの間で共有されるリンクリスト、または努力によって複数のリーダーとライターの間で共有されるリンクされたリストなどです。

于 2009-11-19T10:01:32.563 に答える
11

以下は、意味を理解するのに役立つかもしれない原子性に関する私のメモの一部です。メモは末尾にリストされている情報源からのものであり、私が持っている箇条書きの箇条書きではなく、より完全な説明が必要な場合は、それらのいくつかを読むことをお勧めします. 誤りがあれば訂正しますので、ご指摘ください。

意味 :

  • 「小さな部分に分割できない」という意味のギリシャ語から
  • 「アトミック」操作は常に実行されたかどうかが監視されますが、途中で実行されることはありません。
  • アトミック操作は、完全に実行するか、まったく実行しない必要があります。
  • マルチスレッドのシナリオでは、変数は「途中で変更された」値なしで、変更されていない状態から変更された状態に直接移行します。

例 1 : アトミック操作

  • 異なるスレッドで使用される次の整数を考慮してください。

     int X = 2;
     int Y = 1;
     int Z = 0;
    
     Z = X;  //Thread 1
    
     X = Y;  //Thread 2
    
  • 上記の例では、2 つのスレッドが X、Y、および Z を使用しています。

  • 各読み取りと書き込みはアトミックです
  • スレッドは競合します:
    • スレッド 1 が勝つ場合、Z = 2
    • スレッド 2 が勝った場合、Z=1
    • Zは間違いなくこれら2つの値のいずれかになります

例 2 : 非アトミック操作 : ++/-- 操作

  • インクリメント/デクリメント式を考えてみましょう:

    i++;  //increment
    i--;  //decrement
    
  • 操作は次のように変換されます。

    1. 私を読む
    2. 読み取り値の増減
    3. 新しい値を i に書き戻します
  • 操作はそれぞれ 3 つのアトミック操作で構成されており、それ自体はアトミックではありません
  • 別々のスレッドで i を 2 回インクリメントしようとすると、インターリーブしてインクリメントの 1 つが失われる可能性があります。

例 3 - 非アトミック操作: 4 バイトを超える値

  • 次の不変の struct を考えてみましょう:
  struct MyLong
   {
       public readonly int low;
       public readonly int high;

       public MyLong(int low, int high)
       {
           this.low = low;
           this.high = high;
       }
   }
  • タイプ MyLong の特定の値を持つフィールドを作成します。

    MyLong X = new MyLong(0xAAAA, 0xAAAA);   
    MyLong Y = new MyLong(0xBBBB, 0xBBBB);     
    MyLong Z = new MyLong(0xCCCC, 0xCCCC);
    
  • スレッド セーフを使用せずに、別のスレッドでフィールドを変更します。

    X = Y; //Thread 1                                  
    Y = X; //Thread 2
    
  • .NET では、値の型をコピーするときに、CLR はコンストラクターを呼び出さず、一度に 1 つのアトミック操作でバイトを移動します。

  • このため、2 つのスレッドでの操作は 4 つのアトミック操作になりました。
  • スレッド セーフが適用されていない場合、データが破損する可能性があります
  • 次の操作の実行順序を考慮してください。

    X.low = Y.low;      //Thread 1 - X = 0xAAAABBBB            
    Y.low = Z.low;      //Thread 2 - Y = 0xCCCCBBBB              
    Y.high = Z.high;    //Thread 2 - Y = 0xCCCCCCCC             
    X.high = Y.high;    //Thread 1 - X = 0xCCCCBBBB   <-- corrupt value for X
    
  • 何らかのロックを追加して操作をアトミックにすることなく、32 ビット オペレーティング システム上の複数のスレッドで 32 ビットを超える値を読み書きすると、上記のようにデータが破損する可能性があります。

プロセッサ操作

  • 最新のすべてのプロセッサでは、自然に整列されたネイティブ型の読み取りと書き込みは、次の限りアトミックであると想定できます。

    • 1 : メモリ バスは、読み取りまたは書き込み対象のタイプと少なくとも同じ幅です。
    • 2 : CPU はこれらの型を 1 回のバス トランザクションで読み書きするため、他のスレッドがこれらの型を半分完了した状態で見ることができなくなります。
  • x86 および X64 では、8 バイトを超える読み取りと書き込みがアトミックであるという保証はありません。

  • プロセッサ ベンダーは、ソフトウェア開発者マニュアルで各プロセッサのアトミック操作を定義します。
  • シングル プロセッサ/シングル コア システムでは、標準のロック技術を使用して CPU 命令が中断されるのを防ぐことができますが、これは非効率的です。
  • 可能であれば、割り込みを無効にすることは、別のより効率的な解決策です
  • マルチプロセッサ/マルチコア システムでもロックを使用できますが、単一の命令を使用したり、割り込みを無効にしたりするだけでは、アトミック アクセスは保証されません。
  • システム内の他のプロセッサが同時にメモリにアクセスするのを防ぐために、使用される命令がバス上で「LOCK」信号をアサートすることを保証することによって、原子性を実現できます。

言語の違い

C#

  • C# は、最大 4 バイトを使用する組み込み値型の操作がアトミックであることを保証します。
  • 4 バイトを超える値型 (double、long など) の操作は、アトミックであることが保証されていません。
  • CLI は、プロセッサの自然なポインター サイズ (またはそれより小さいサイズ) の値型の変数の読み取りと書き込みがアトミックであることを保証します。
    • 例 - CLR の 64 ビット バージョンの 64 ビット OS で C# を実行すると、64 ビット double と long integer の読み取りと書き込みがアトミックに実行されます。
  • アトミック操作の作成:
    • .NET は、Interlocked クラスを System.Threading 名前空間の一部として提供します。
    • Interlocked Class は、インクリメント、比較、交換などのアトミック操作を提供します。
using System.Threading;             

int unsafeCount;                          
int safeCount;                           

unsafeCount++;                              
Interlocked.Increment(ref safeCount);

C++

  • C++ 標準はアトミックな動作を保証しない
  • すべての C / C++ 操作は、コンパイラまたはハードウェア ベンダーによって特に指定されていない限り、非アトミックであると想定されます (32 ビット整数の割り当てを含む)。
  • アトミック操作の作成:
    • C++ 11 同時実行ライブラリには、Atomic Operations Library () が含まれています。
    • Atomic ライブラリは、任意のタイプで使用できるテンプレート クラスとしてアトミック タイプを提供します。
    • アトミック型に対する操作はアトミックであるため、スレッドセーフです

struct AtomicCounter
{

   std::atomic< int> value;   

   void increment(){                                    
       ++value;                                
   }           

   void decrement(){                                         
       --value;                                                 
   }

   int get(){                                             
       return value.load();                                    
   }      

}

ジャワ

  • Java は、最大 4 バイトを使用する組み込み値型に対する操作がアトミックであることを保証します。
  • volatile long および double への代入もアトミックであることが保証されます
  • Java は、java.util.concurrent.atomic を介して単一変数でロックフリーのスレッドセーフ プログラミングをサポートするクラスの小さなツールキットを提供します。
  • これにより、compare-and-swap (CAS) (compare and set とも​​呼ばれる) などの低レベルのアトミック ハードウェア プリミティブに基づくアトミック ロックフリー操作が提供されます。
    • CAS フォーム - boolean compareAndSet(expectedValue, updateValue );
      • このメソッドは、現在 expectedValue を保持している場合、変数を updateValue にアトミックに設定し、成功時に true を報告します
import java.util.concurrent.atomic.AtomicInteger;

public class Counter
{
     private AtomicInteger value= new AtomicInteger();

     public int increment(){
         return value.incrementAndGet();  
     }

     public int getValue(){
         return value.get();
     }
}

ソース
http://www.evernote.com/shard/s10/sh/c2735e95-85ae-4d8c-a615-52aadc305335/99de177ac05dc8635fb42e4e6121f1d2

于 2014-03-02T05:00:57.803 に答える
8

原子はギリシャ語の ἄτομος (atomos) に由来し、「分割できない」を意味します。 (注意: 私はギリシャ語を話せないので、本当は別の意味かもしれませんが、語源を挙げているほとんどの英語話者はこのように解釈しています。:-)

コンピューティングでは、これは操作が発生することを意味します。完了する前に目に見える中間状態はありません。そのため、CPU がハードウェア (IRQ) にサービスを提供するために中断された場合、または別の CPU が同じメモリを読み取っている場合、結果には影響しません。これらの他の操作は、完了または開始されていないことを観察します。

例として...変数を何かに設定したいとしましょう。ただし、それが以前に設定されていない場合のみです。あなたはこれをする傾向があるかもしれません:

if (foo == 0)
{
   foo = some_function();
}

しかし、これを並行して実行するとどうなるでしょうか。プログラムが fetchを実行fooし、それをゼロと見なしている間に、スレッド 2 が来て同じことを行い、値を何かに設定する可能性があります。元のスレッドに戻ると、コードはまだfooゼロであると考えており、変数が 2 回割り当てられています。

このような場合、CPU は、アトミック エンティティとして比較と条件付き割り当てを実行できるいくつかの命令を提供します。したがって、テストと設定、比較と交換、およびロードリンク/ストア条件。これらを使用してロックを実装できます (OS と C ライブラリがこれを行っています)。または、プリミティブに依存して何かを行う 1 回限りのアルゴリズムを作成することもできます。(ここにはクールなことが行われていますが、ほとんどの単なる人間は、間違いを犯すことを恐れてこれを避けています。)

于 2009-11-19T10:17:37.917 に答える
2

原子性は、共有リソースを含む任意の形式の並列処理 (さまざまなアプリケーションの連携またはデータの共有を含む) がある場合の重要な概念です。

問題は例でよく説明されています。ファイルを作成する 2 つのプログラムがあるとしますが、そのファイルがまだ存在しない場合に限られます。2 つのプログラムのいずれでも、いつでもファイルを作成できます。

もしそうなら(あなたの例にあるので、私はCを使います):

 ...
 f = fopen ("SYNCFILE","r");
 if (f == NULL) {
   f = fopen ("SYNCFILE","w");
 }
 ...

読み取り用に開いてから書き込み用に開いている間に、他のプログラムがファイルを作成していないことを確認することはできません。

自分でこれを行う方法はありません。通常、この目的のために同期プリミティブを提供するオペレーティング システム、またはアトミックであることが保証されている別のメカニズム (たとえば、ロック操作がアトミックであるリレーショナル データベース、またはプロセッサの「テストおよび設定」命令のような低レベルのメカニズム)。

于 2009-11-19T10:06:52.280 に答える
-4

原子性は OS によってのみ保証されます。OS は、基盤となるプロセッサ機能を使用してこれを実現します。

したがって、独自の testandset 関数を作成することは不可能です。(インライン asm スニペットを使用して、testandset ニーモニックを直接使用できるかどうかはわかりませんが (このステートメントは OS 特権でのみ実行できる可能性があります))

編集: この投稿の下のコメントによると、ASM ディレクティブを直接使用して独自の「bittestandset」関数を作成することが可能です (インテル x86 上)。ただし、これらのトリックが他のプロセッサでも機能するかどうかは明らかではありません。

私は私の主張を支持します:あなたがアトモイックなことをしたいのなら、OSの機能を使い、自分でやらないでください

于 2009-11-19T10:00:22.737 に答える