web-dev-qa-db-ja.com

C ++の原子性:神話または現実

私はMSDNの ロックレスプログラミング に関する記事を読んでいます。それは言う:

最近のすべてのプロセッサでは、自然に整列されたネイティブタイプの読み取りと書き込みはアトミックであると想定できます。メモリバスが少なくとも読み取りまたは書き込み中のタイプと同じ幅である限り、CPUはこれらのタイプを1回のバストランザクションで読み書きするため、他のスレッドがそれらを半分完了した状態で見ることはできません。

そしてそれはいくつかの例を与えます:

// This write is not atomic because it is not natively aligned.
DWORD* pData = (DWORD*)(pChar + 1);
*pData = 0;

// This is not atomic because it is three separate operations.
++g_globalCounter;

// This write is atomic.
g_alignedGlobal = 0;

// This read is atomic.
DWORD local = g_alignedGlobal;

私はたくさんの回答とコメントを読んで、C++でアトミックであることが保証されているものはなく、標準でも言及されていません。SOそして今は少し混乱しています。記事を誤解していますか? ?または、記事の執筆者は、非標準でMSVC++コンパイラに固有のことについて話しますか?

したがって、記事によると、以下の割り当てはアトミックでなければなりませんよね?

struct Data
{
    char ID;
    char pad1[3];
    short Number;
    char pad2[2];
    char Name[5];
    char pad3[3];
    int Number2;
    double Value;
} DataVal;

DataVal.ID = 0;
DataVal.Number = 1000;
DataVal.Number2 = 0xFFFFFF;
DataVal.Value = 1.2;

Trueの場合、Name[5]pad3[3]std::string Name;に置き換えると、メモリアライメントに違いが生じますか? Number2およびValue変数への割り当ては引き続きアトミックですか?

誰か説明してもらえますか?

33
ali_bahoo

この推奨事項はアーキテクチャ固有です。これは、x86およびx86_64(低レベルプログラミング)に当てはまります。また、コンパイラがコードを並べ替えないことを確認する必要があります。そのために「コンパイラのメモリバリア」を使用できます。

X86の低レベルのアトミック読み取りおよび書き込みについては、インテルリファレンスマニュアル「インテル®64およびIA-32アーキテクチャーソフトウェア開発者マニュアル」第3A巻( http://www.intel.com/Assets/PDF/ manual/253668.pdf )、セクション8.1.1

8.1.1保証されたアトミック操作

Intel486プロセッサ(およびそれ以降の新しいプロセッサ)は、次の基本的なメモリ操作が常にアトミックに実行されることを保証します。

  • バイトの読み取りまたは書き込み
  • 16ビット境界に揃えられたワードの読み取りまたは書き込み
  • 32ビット境界に整列されたダブルワードの読み取りまたは書き込み

Pentiumプロセッサ(およびそれ以降の新しいプロセッサ)は、次の追加のメモリ操作が常にアトミックに実行されることを保証します。

  • 64ビット境界に整列されたクアッドワードの読み取りまたは書き込み
  • 32ビットデータバス内に収まるキャッシュされていないメモリ位置への16ビットアクセス

P6ファミリプロセッサ(およびそれ以降の新しいプロセッサ)は、次の追加のメモリ操作が常にアトミックに実行されることを保証します。

  • キャッシュライン内に収まるキャッシュメモリへのアラインされていない16、32、および64ビットアクセス

このドキュメントには、Core2などの新しいプロセッサのアトミックな説明も含まれています。 すべての非整列操作がアトミックになるわけではありません。

他のIntelマニュアルでは、このホワイトペーパーを推奨しています。

http://software.intel.com/en-us/articles/developing-multithreaded-applications-a-platform-consistent-approach/

29
osgx

私はあなたが引用を誤解していると思います。

Atomicityは、特定の命令(このアーキテクチャに固有)を使用して、特定のアーキテクチャで保証できます。 MSDNの記事では、C++組み込み型での読み取りと書き込みは、x86アーキテクチャではアトミックであると予想されると説明されています。

ただし、C++標準はアーキテクチャが何であるかを想定していないため、標準はそのような保証を行うことはできません。実際、C++は、ハードウェアサポートがはるかに制限されている組み込みソフトウェアで使用されています。

C++ 0xは、std::atomicテンプレートクラスを定義します。これにより、タイプに関係なく、読み取りと書き込みをatomic操作に変換できます。コンパイラーは、標準に準拠した方法で対象となる型の特性とアーキテクチャーに基づいて、アトミック性を取得するための最良の方法を選択します。

新しい標準では、MSVC InterlockExchangeと同様の多くの操作も定義されており、ハードウェアによって提供される最速の(ただし安全な)利用可能なプリミティブにコンパイルされます。

12
Matthieu M.

C++標準は、アトミックな動作を保証するものではありません。ただし、実際には、記事に記載されているように、単純なロードおよびストア操作はアトミックになります。

need原子性の場合は、それについて明示し、何らかのロックを使用することをお勧めします。

*counter = 0; // this is atomic on most platforms
*counter++;   // this is NOT atomic on most platforms
3
James

単純なワードサイズの操作のアトミック性に依存する場合は、予想とは異なる動作をする可能性があるため、十分に注意してください。マルチコアアーキテクチャでは、読み取りと書き込みの順序が乱れることがあります。これを防ぐには、メモリバリアが必要になります。 (詳細 ここ )。

アプリケーション開発者にとっての結論は、OSがアトミックであることを保証するプリミティブを使用するか、適切なロックを使用することです。

2
doron

IMOの記事には、基盤となるアーキテクチャに関するいくつかの前提条件が組み込まれています。 C++にはアーキテクチャに関する最小限の要件しかないため、たとえばアトミック性についての保証は標準では提供できません。たとえば、バイトは少なくとも8ビットである必要がありますが、バイトが9ビットであるアーキテクチャを使用できますが、理論的にはint 16 ...です。

したがって、コンパイラがx86アーキテクチャに固有の場合は、特定の機能を使用できます。

注意:構造体は通常、デフォルトでネイティブのWord境界に揃えられます。 #pragmaステートメントでこれを無効にできるため、パディングフィルは必要ありません

1
king_nak

彼らが乗り越えようとしているのは、ハードウェアによってネイティブに実装されたデータ型がハードウェア内で更新されるため、別のスレッドから読み取っても「部分的に」更新された値が得られないことだと思います。

32ビット以上のマシンで32ビット整数を考えてみましょう。 1命令サイクルで書き込みまたは読み取り完全にですが、より大きなサイズのデータ​​型、たとえば32ビットマシンの64ビット整数はより多くのサイクルを必要とするため、理論的にはそれらを書き込むスレッドはこれらのサイクルの間に、値は有効な状態ではありません。

文字列は高レベルの構造であり、ハードウェアに実装されていないため、文字列を使用してもアトミックにはなりません。編集:文字列への変更についてあなた(didnt)が何を意味するかについてのあなたのコメントによると、別の回答で述べられているように、コンパイラはデフォルトでフィールドを整列するので、後に宣言されたフィールドに違いはありません。

それが標準にない理由は、記事で述べられているように、これは最新のプロセッサがどのように命令を実装するかについてであるということです。標準のC/C++コードは、16ビットまたは64ビットのマシンでもまったく同じように機能するはずです(パフォーマンスの違いがあります)が、64ビットのマシンでのみ実行すると仮定すると、64ビット以下のものはすべてアトミックです。 (SSEなどのタイプは別として)

1
Aaron Gage

記事で言及されているatomicityは、実用性がほとんどないと思います。これは、有効な値を読み書きできますが、おそらく古くなっていることを意味します。したがって、intを読み取ると、古い値から2バイト、別のスレッドによって現在書き込まれている新しい値から他の2バイトではなく、完全に読み取られます。

共有メモリにとって重要なのは、メモリバリアです。また、C++ 0x atomicタイプ、mutexesなどの同期プリミティブによって保証されています。

1
Andriy Tylychko

char Name[5]std::string Nameに変更しても違いはないと思います個々の文字の割り当てにのみ使用している場合インデックス演算子は基になる参照を直接返すためですキャラクター。完全な文字列の割り当てはアトミックではありません(char配列では実行できないため、とにかくこの方法で使用することを考えていなかったと思います)。