14

InformIT の David Chisnall の記事「Understanding C11 and C++11 Atomics」から引用した、次の一連のvolatileメモリへの書き込みについて考えてみましょう。

volatile int a = 1;
volatile int b = 2;
             a = 3;

C++98 からの私の理解では、C++98 1.9 に従って、これらの操作を並べ替えることができないということでした。

準拠する実装は、以下で説明するように、抽象マシンの観察可能な動作をエミュレート (のみ) する必要があります ... 抽象マシンの観察可能な動作は、揮発性データへの読み取りと書き込み、およびライブラリ I/O 関数の呼び出しのシーケンスです。

Chisnall は、順序の保存に関する制約は個々の変数にのみ適用されると述べ、適合する実装はこれを行うコードを生成できると書いています。

a = 1;
a = 3;
b = 2;

またはこれ:

b = 2;
a = 1;
a = 3;

C++11 は、C++98 の文言を繰り返します。

準拠する実装は、以下で説明するように、抽象マシンの観察可能な動作を (のみ) エミュレートする必要があります。

volatileしかし、 s (1.9/8)について次のように述べています。

volatile オブジェクトへのアクセスは、抽象マシンの規則に従って厳密に評価されます。

1.9/12 では、volatileglvalue (変数ab、およびc上記を含む) へのアクセスは副作用であると述べており、1.9/14 では、1 つの完全な式 (たとえば、ステートメント) の副作用は、後の副作用の前にある必要があると述べています。同じスレッドで完全な表現。これにより、Chisnall が示した 2 つの並べ替えは無効であると結論付けることができます。なぜなら、それらは抽象機械によって指示された順序付けに対応していないからです。

私は何かを見落としていますか、それともチスナルは間違っていますか?

(これはスレッドの問題ではないことに注意してください。問題は、コンパイラがvolatile単一のスレッドで異なる変数へのアクセスを並べ替えることが許可されているかどうかです。)

4

6 に答える 6

8

IMO Chisnalls の解釈 (あなたが提示したもの) は明らかに間違っています。より単純なケースは C++98 です。保持するsequence of reads and writes to volatile data必要があり、それは単一の変数ではなく、揮発性データの順序付けられた読み取りと書き込みのシーケンスに適用されます。

これは、volatile の元の動機であるメモリ マップド I/O を考慮すると明らかです。mmio では、通常、異なるメモリ位置にいくつかの関連するレジスタがあり、I/O デバイスのプロトコルでは、一連のレジスタに対する読み取りと書き込みの特定のシーケンスが必要です。レジスタ間の順序が重要です。

sequence of reads and writesマルチスレッド環境では、スレッド間でこのようなイベントの単一の明確に定義されたシーケンスが存在しないため、C++11 の表現では絶対について話すことを避けています。これらのアクセスが独立したメモリ位置に移動する場合、それは問題ではありません。しかし、その意図は、明確に定義された順序を持つ揮発性データ アクセスのシーケンスについて、規則が C++98 の場合と同じままであることであると私は信じています。そのシーケンスでアクセスされる異なる場所の数に関係なく、順序は保持されなければなりません。

それが実装に何をもたらすかは、まったく別の問題です。揮発性データへのアクセスがプログラムの外部からどのように観察可能であるか (たとえ. 実装はおそらく合理的な解釈と合理的な保証を提供する必要がありますが、何が合理的かはコンテキストによって異なります。

C++11 標準では、非同期の揮発性アクセス間のデータ競合の余地が残されているため、これらを完全なメモリ フェンスまたは同様の構造で囲む必要はありません。メモリ マップド I/O または DMA の外部インターフェイスとして実際に使用されるメモリの部分がある場合、これらの部分への揮発性アクセスが消費デバイスにどのように公開されるかを保証する実装が合理的である可能性があります。

1 つの保証はおそらく標準から推測できます ([into.execution] を参照): 型の値はvolatile std::sigatomic_t、シグナル ハンドラーでも (少なくともシングル スレッド プログラムでは)、それらへの書き込み順序と互換性のある値を持たなければなりません。

于 2013-02-09T11:45:51.217 に答える
4

あなたは正しい、彼は間違っている。別個の volatile 変数へのアクセスは、それらが個別の完全な式で発生する限り、つまり、C++98 でシーケンス ポイントと呼ばれるもので区切られている限り、または C++11 の用語では、1 つのアクセスが他のアクセスの前に順序付けられている限り、コンパイラによって並べ替えられません。

Chisnall は、スレッドセーフなコードを書くのに が役に立たない理由を説明しようとしているようです。これvolatileに依存する単純なミューテックスの実装volatileが、コンパイラの並べ替えによって壊れてしまうことを示しています。volatileスレッドセーフにとっては役に立たないというのは彼の言う通りですが、彼が挙げた理由からではありません。これは、コンパイラがvolatileオブジェクトへのアクセスを並べ替える可能性があるためではなく、CPU がそれらを並べ替える可能性があるためです。アトミック操作とメモリ バリアにより、コンパイラCPU は、スレッド セーフの必要に応じて、バリアを越えて順序を変更できなくなります。

Sutter の有益な volatile vs volatile記事の表 1 の右下のセルを参照してください。

于 2013-02-09T15:03:19.243 に答える
1

今のところ、あなたa=3の はコピーと貼り付けの間違いであり、あなたは本当にc=3.

ここでの本当の問題は、評価と、物事が別のプロセッサにどのように見えるようになるかの違いの 1 つです。標準は、評価の順序を記述します。その観点から、あなたは完全に正しいです - への割り当てが与えられ、aその順序で、割り当てはその順序で評価されなければなりません。bc

ただし、これらの値が他のプロセッサに表示される順序とは一致しない場合があります。典型的な (現在の) CPU では、その評価は値をキャッシュに書き込むだけです。ただし、ハードウェアはそこから順序を変更できるため、(たとえば) メイン メモリへの書き込みはまったく異なる順序で行われます。同様に、別のプロセッサが値を使用しようとすると、異なる順序で変化していると見なされる場合があります。

はい、これは完全に許容されます。CPU は標準で規定された順序で割り当てを評価しているため、要件は満たされています。この標準では、評価後に何が起こるかについて要件を課していません。これがここで起こることです。

追加する必要がありますが、一部のハードウェアでは十分です。たとえば、x86 はキャッシュ スヌーピングを使用するため、別のプロセッサが 1 つのプロセッサによって更新された値を読み取ろうとすると (ただし、まだキャッシュ内にのみある)、現在の値を持つプロセッサが他のプロセッサによる読み取りを保留します。現在の値が書き出されるまで、他のプロセッサが現在の値を見ることができるようにします。

ただし、すべてのハードウェアに当てはまるわけではありません。その厳密なモデルを維持することで物事が単純になりますが、一貫性を確保するための追加のハードウェアと、多数のプロセッサがある場合の単純な速度の両方の点で、かなり費用がかかります。

編集:スレッド化をしばらく無視すると、質問は少し単純になりますが、それほど多くはありません。C++11 によると、§1.9/12:

ライブラリ I/O 関数の呼び出しが返されるか、揮発性オブジェクトへのアクセスが評価されると、呼び出し (I/O 自体など) または揮発性アクセスによっていくつかの外部アクションが暗示されていても、副作用は完了したと見なされます。まだ完了していない可能性があります。

そのため、揮発性オブジェクトへのアクセスは順番に開始する必要がありますが、必ずしも順番に完了する必要はありません。残念ながら、多くの場合、外部から見えるのは完成です。そのため、通常の as-if ルールに戻ります。コンパイラは、外部から目に見える変更を生成しない限り、必要なだけ再配置できます。

于 2013-02-09T06:57:38.550 に答える
0

それはあなたのコンパイラに依存します。たとえば、Visual Studio 2005 の時点で、MSVC++ は volatile* が再配列されないことを保証します (実際、Microsoft が行ったことは、プログラマーが永久に乱用することをあきらめて想定することvolatileでしvolatileた。他のバージョンや他のコンパイラには、そのような保証がない場合があります。

簡単に言えば、それに賭けないでください。コードを適切に設計し、volatile を誤用しないでください。代わりにメモリバリアを使用するか、必要に応じて本格的なミューテックスを使用してください。C++11 のatomic型が役に立ちます。

于 2013-02-09T06:56:23.413 に答える
0

それは起こり得るように見えます。

このページに議論があります:

http://gcc.gnu.org/ml/gcc/2003-11/msg01419.html

于 2013-02-09T06:39:36.113 に答える
-2

C++98 では、命令の順序を変更できないとは言っていません。

抽象マシンの観察可能な動作は、揮発性データへの読み取りと書き込み、およびライブラリ I/O 関数の呼び出しのシーケンスです。

これは、読み取りと書き込み自体の実際のシーケンスであり、それらを生成する命令ではないことを示しています。命令がプログラムの順序で読み取りと書き込みを反映しなければならないという議論は、RAM自体への読み取りと書き込みがプログラムの順序で行われなければならないと同様に主張する可能性があり、明らかにそれは要件のばかげた解釈です。

簡単に言えば、これは何の意味もありません。読み取りと書き込みの順序を観察する「1 つの適切な場所」 (RAM バス? CPU バス? L1 キャッシュと L2 キャッシュの間? 別のスレッドから? 別のコアから?) がないため、この要件は本質的に無意味です。

スレッドを参照する前のバージョンの C++ では、別のスレッドから見た volatile 変数の動作が明確に指定されていません。そして、C++11 (賢明なことに、IMO)はこれを変更しませんでしたが、代わりに、明確に定義されたスレッド間セマンティクスを備えた適切なアトミック操作を導入しました。

メモリ マップド ハードウェアに関しては、それは常にプラットフォーム固有になります。C++ 標準は、それが適切に行われる方法に対処するふりさえしていません。たとえば、プラットフォームは、メモリ操作のサブセットのみがそのコンテキストで合法であるようなものである可能性があります。たとえば、並べ替え可能な書き込みポスティング バッファーをバイパスするものなどです。その特定のハードウェア デバイス - どうすればできるのでしょうか?

更新:人々はこの真実を好まないため、いくつかの反対票が見られます。残念ながら、それは本当です。

そのようなアクセスの順序はプログラムの観察可能な動作の一部であるという理論に基づいて、C++ 標準がコンパイラによる個別の volatile へのアクセスの順序変更を禁止している場合、CPU がそうするのを禁止するコードを発行することもコンパイラに要求します。標準では、コンパイラが行うことと、コンパイラが生成したコードが CPU に行うことを区別していません。

CPU が volatile 変数へのアクセスを並べ替えるのを防ぐためにコンパイラが命令を発行することを標準が要求しているとは誰も信じておらず、最新のコンパイラはこれを行わないため、C++ 標準がコンパイラが個別の volatile へのアクセスを並べ替えるのを禁止しているとは誰も信じるべきではありません。

于 2013-02-09T06:52:38.017 に答える