29

C ++ 11標準では、マシンモデルがシングルスレッドマシンからマルチスレッドマシンに変更されました。

static int x; void func() { x = 0; while (x == 0) {} }これは、最適化されたアウトリードの典型的な例がC ++ 11では発生しなくなることを意味しますか?

編集:この例を知らない人のために(私は真剣に驚いています)、これを読んでください:https: //en.wikipedia.org/wiki/Volatile_variable

EDIT2:わかりました、私は本当に何が起こっているかを知っているすべての人がvolatileこの例を見たことを期待していました。

例のコードを使用すると、サイクルで読み取られた変数が最適化され、サイクルが無限になります。

もちろん、解決策はvolatile、コンパイラがアクセスごとに変数を読み取るように強制するを使用することです。

私の質問は、これがC ++ 11で非推奨の問題であるかどうかです。マシンモデルはマルチスレッドであるため、コンパイラは変数への同時アクセスがシステムに存在することを考慮する必要があります。

4

2 に答える 2

88

それが最適化されるかどうかは、コンパイラーとコンパイラーが何を最適化するかによって完全に異なります。C ++ 98/03メモリモデルはx、その設定と値の取得の間で変更される可能性を認識していません。

C ++ 11メモリモデル、変更される可能性があることを認識していxます。ただし、気にしません。変数への非アトミックアクセス(つまり、std::atomicsまたは適切なミューテックスを使用しない)は、未定義の動作をもたらします。したがって、C ++ 11コンパイラが、書き込みと読み取りの間で変更がないことを想定することはまったく問題ありません。x未定義の動作は、「関数がx変更を確認することはない」ことを意味する可能性があるためです。

それでは、C++11が何を言っているか見てみましょうvolatile int x;。そこにそれを入れて、他のスレッドが混乱してxいる場合でも、未定義の動作があります揮発性はスレッドの動作に影響を与えません。C ++ 11のメモリモデルは、アトミックな読み取りまたは書き込みを定義していませんx。また、アトミックでない読み取り/書き込みを適切に順序付けるために必要なメモリバリアも必要ありません。volatileいずれにせよそれとは何の関係もありません。

ああ、あなたのコードはうまくいくかもしれませんしかし、C++11はそれを保証しません。

コンパイラに通知volatileするのは、その変数からのメモリ読み取りを最適化できないということです。ただし、CPUコアには異なるキャッシュがあり、ほとんどのメモリ書き込みはすぐにメインメモリに出力されません。それらはそのコアのローカルキャッシュに保存され、書き込まれる可能性があります...最終的には

CPUには、キャッシュラインをメモリに強制的に出力し、異なるコア間でメモリアクセスを同期する方法があります。これらのメモリバリアにより、2つのスレッドが効果的に通信できます。別のコアで書き込まれた1つのコアのメモリから読み取るだけでは十分ではありません。メモリを書き込んだコアはバリアを発行する必要があり、それを読み取っているコアは、実際にデータを取得するために、それを読み取る前にそのバリアを完了している必要があります。

volatileこれのどれも保証しません。Volatileは、「ハードウェア、マップされたメモリなど」で動作します。これは、そのメモリを書き込むハードウェアが、キャッシュの問題を確実に処理するためです。CPUコアが書き込みのたびにメモリバリアを発行した場合、基本的にパフォーマンスの希望に別れを告げることができます。そのため、C ++ 11には、バリアを発行するために構成が必要な場合を示す特定の言語があります。

volatileメモリアクセス(いつ読み取るか)についてです。スレッド化とは、メモリの整合性(実際にそこに格納されているもの)に関するものです。

C ++ 11メモリモデルは、あるスレッドでの書き込みが別のスレッドで表示されるようにする操作に固有のものです。それはメモリの完全性についてであり、それは何かvolatileを処理するものではありません。また、メモリの整合性には、通常、両方のスレッドが何かを実行する必要があります。

たとえば、スレッドAがミューテックスをロックし、書き込みを実行してからロックを解除した場合、C ++ 11メモリモデルでは、スレッドBが後でロックした場合にのみ書き込みがスレッドBに表示されるようにする必要があります。実際にその特定のロックを取得するまで、そこにどのような値があるかは未定義です。このようなものは、規格のセクション1.10に詳細に記載されています。

標準に関して、あなたが引用するコードを見てみましょう。セクション1.10、p8は、スレッドを別のスレッドと「同期」させる特定のライブラリ呼び出しの機能について説明しています。他の段落のほとんどは、同期(およびその他のもの)がスレッド間の操作の順序を構築する方法を説明しています。もちろん、あなたのコードはこれを呼び出しません。同期ポイント、依存関係の順序、何もありません。

このような保護がなければ、何らかの形の同期や順序付けがなければ、1.10p21が登場します。

プログラムの実行には、異なるスレッドで2つの競合するアクションが含まれ、そのうちの少なくとも1つがアトミックではなく、どちらももう一方の前に発生しない場合、データ競合が含まれます。このようなデータ競合は、未定義の動作を引き起こします。

プログラムには、2つの競合するアクション(読み取りxと書き込みx)が含まれています。どちらもアトミックではなく、同期によって他の前に発生するように順序付けられていません。

したがって、未定義の動作を実現しました。

したがって、C ++ 11メモリモデルによってマルチスレッド動作が保証されるstd::atomic<int> x唯一のケースは、適切なミューテックスを使用するか、適切なアトミックロード/ストア呼び出しを使用する場合です。

ああ、あなたもx揮発性にする必要はありません。(非インライン)関数を呼び出すときはいつでも、その関数またはそれが呼び出す何かがグローバル変数を変更する可能性があります。したがって、ループ内の読み取りを最適化することはできません。また、同期するすべてのC ++ 11メカニズムでは、関数を呼び出す必要があります。それはまさにメモリバリアを引き起こすために起こります。xwhile

于 2012-10-14T01:17:30.123 に答える
2

Intel開発者ゾーンは、「揮発性:マルチスレッドプログラミングにはほとんど役に立たない」と述べています

volatileキーワードは、cppreference.comのこのシグナルハンドラーの例で使用されています

#include <csignal>
#include <iostream>

namespace
{
  volatile std::sig_atomic_t gSignalStatus;
}

void signal_handler(int signal)
{
  gSignalStatus = signal;
}

int main()
{
  // Install a signal handler
  std::signal(SIGINT, signal_handler);

  std::cout << "SignalValue: " << gSignalStatus << '\n';
  std::cout << "Sending signal " << SIGINT << '\n';
  std::raise(SIGINT);
  std::cout << "SignalValue: " << gSignalStatus << '\n';
}
于 2018-08-15T18:30:39.387 に答える