質問

2つのスレッドが実行されているアプリケーションがあります...あるスレッドからグローバル変数を変更すると、もう一方のスレッドもその変更に気づくという確実性はありますか?同期や相互排除システムは導入されていません...しかし、このコードは常に機能するはずです (グローバルなコードを想像してください) ブール 名前付き データ更新済み):

スレッド 1:

while(1) {
    if (dataUpdated)
        updateScreen();
    doSomethingElse();
}

スレッド 2:

while(1) {
    if (doSomething())
        dataUpdated = TRUE;
}

gcc のようなコンパイラは、グローバル値をチェックせず、コンパイル時の値のみを考慮する方法でこのコードを最適化しますか (同じスレッドで変更されることがないため)?

追伸:これはゲームのようなアプリケーションのため、値の書き込み中に読み取りがあるかどうかは実際には問題ではありません。重要なのは、変更が他のスレッドに認識されることだけです。

役に立ちましたか?

解決

はい。いいえ。多分。

まず、他の人が述べたように、dataUpdated を揮発性にする必要があります。それ以外の場合、コンパイラーは、(doSomethingElse がコンパイラーに触れていないことをコンパイラーが認識できるかどうかに応じて) ループからの読み取りを自由に解除できる可能性があります。

次に、プロセッサと注文のニーズによっては、メモリ バリアが必要になる場合があります。volatile は、他のプロセッサが変更を最終的に認識することを保証するには十分ですが、変更が実行された順序で認識されることを保証するには十分ではありません。あなたの例にはフラグが 1 つしかないため、この現象は実際には示されていません。メモリバリアが必要で使用する場合は、揮発性メモリは必要なくなります。

揮発性物質は有害であると考えられる そして Linux カーネルのメモリバリア 根本的な問題に関する十分な背景がある。スレッド化専用に書かれた同様のものを実際には知りません。ありがたいことに、スレッドはハードウェア周辺機器ほど頻繁にこれらの懸念を引き起こしませんが、あなたが説明した種類のケース(完了を示すフラグ、フラグが設定されている場合は他のデータが有効であると推定される)はまさに順序付けが行われる種類のことです重要です...

他のヒント

次に、ブースト条件変数を使用する例を示します。

bool _updated=false;
boost::mutex _access;
boost::condition _condition;

bool updated()
{
  return _updated;
}

void thread1()
{
  boost::mutex::scoped_lock lock(_access);
  while (true)
  {
    boost::xtime xt;
    boost::xtime_get(&xt, boost::TIME_UTC);
    // note that the second parameter to timed_wait is a predicate function that is called - not the address of a variable to check
    if (_condition.timed_wait(lock, &updated, xt))
      updateScreen();
    doSomethingElse();
  }
}

void thread2()
{
  while(true)
  {
    if (doSomething())
      _updated=true;
  }
}

ロックを使用してください。共有データにアクセスするには、常にロックを使用してください。変数を揮発性としてマークすると、コンパイラがメモリ読み取りを最適化することは防止されますが、次のような他の問題は防止できません。 記憶の並べ替え. 。ロックがないと、doSomething() でのメモリ書き込みが updateScreen() 関数で表示されるという保証はありません。

他の唯一の安全な方法は、 メモリーフェンス, たとえば、Interlocked* 関数を使用して明示的または暗黙的に実行します。

使用 揮発性の キーワードを使用して、値がいつでも変更できることをコンパイラに示唆します。

volatile int myInteger;

上記により、変数へのアクセスは特定の最適化を行わずにメモリとの間で行われることが保証され、その結果、同じプロセッサ上で実行されているすべてのスレッドは、コードが読み取ったものと同じセマンティクスで変数への変更を「認識」します。

Chris Jester-Young 氏は、マルチプロセッサ システムでは、このような変数値の変更に対する一貫性の問題が発生する可能性があると指摘しました。これは考慮事項であり、プラットフォームによって異なります。

実際、プラットフォームに関して考慮すべき考慮事項が 2 つあります。それらは、メモリ トランザクションの一貫性と原子性です。

アトミック性は、実際にはシングル プロセッサ プラットフォームとマルチ プロセッサ プラットフォームの両方で考慮すべき事項です。この問題は、変数が本質的にマルチバイトである可能性が高く、1 つのスレッドが値の部分的な更新を確認できるかどうかが問題であるために発生します。つまり:一部のバイトが変更され、コンテキストが切り替わり、スレッドの中断により無効な値が読み取られました。単一変数の場合、自然なマシン語サイズ以下であり、自然に位置合わせされている場合は、問題になりません。具体的には、 整数 type は、アライメントされている限り、この点に関して常に OK である必要があります。これがコンパイラのデフォルトのケースです。

コヒーレンシに関して、これはマルチプロセッサ システムでは潜在的な懸念事項です。問題は、システムがプロセッサ間で完全なキャッシュ コヒーレンシを実装しているかどうかです。実装されている場合、これは通常、ハードウェアの MESI プロトコルを使用して行われます。質問ではプラットフォームについては述べられていませんでしたが、Intel x86 プラットフォームと PowerPC プラットフォームはどちらも、通常マップされたプログラム データ領域についてはプロセッサ間でキャッシュ コヒーレントです。したがって、複数のプロセッサがある場合でも、スレッド間の通常のデータ メモリ アクセスでは、この種の問題は問題になりません。

アトミック性に関連して発生する最後の問題は、読み取り、変更、書き込みのアトミック性に固有のものです。つまり、値が読み取られて値が更新され、書き込まれる場合、これが複数のプロセッサー間であってもアトミックに行われることをどのように保証しますか。したがって、これが特定の同期オブジェクトなしで機能するには、変数にアクセスするすべての潜在的なスレッドが読み取り専用である必要がありますが、同時に書き込みできるスレッドは 1 つだけであることが予想されます。そうでない場合は、変数への読み取り、変更、書き込みアクションにおけるアトミック アクションを確実に実行できるようにするために、同期オブジェクトが必要になります。

ソリューションでは CPU が 100% 使用されるなど、さまざまな問題が発生します。「条件変数」でGoogle検索してください。

Chris Jester-Young 氏は次のように指摘しました。

これは Java 1.5 以降のメモリ モデルでのみ機能します。C++ 標準はスレッド化に対応しておらず、volatile はプロセッサ間のメモリの一貫性を保証しません。これにはメモリバリアが必要です

そうなると、唯一の本当の答えは同期システムを実装することですよね?

使用 揮発性の キーワードを使用して、値がいつでも変更できることをコンパイラに示唆します。

volatile int myInteger;

いいえ、それは確実ではありません。変数を volatile として宣言すると、コンパイラーは読み取り時に常にメモリから変数をロードするコードを生成することになります。

スコープが正しい場合 (「extern」、グローバルなど)。)その後、変化に気づくでしょう。問題はいつ?そしてどのような順序で?

問題は、コンパイラが できる そして頻繁に 意思 パフォーマンスの最適化として、ロジックを再順序付けしてすべての同時パイプラインを満たすようにします。

割り当ての周りに他の命令がないため、特定の例では実際には表示されませんが、bool assign の実行後に宣言された関数を想像してください。 前に 課題。

チェックアウト パイプラインの危険性 ウィキペディアで検索するか、Google で「コンパイラー命令の並べ替え」を検索してください。

他の人も言っているように、 volatile キーワードは友達です。:-)

gcc ですべての最適化オプションを無効にすると、コードが機能することがわかるでしょう。この場合(私は信じています)、すべてを揮発性として扱い、その結果、変数は操作ごとにメモリ内でアクセスされます。

何らかの最適化をオンにすると、コンパイラはレジスタに保持されているローカル コピーを使用しようとします。関数によっては、変数の変更が断続的にのみ表示されるか、最悪の場合、まったく表示されないことを意味する場合があります。

キーワードを使用する volatile この変数の内容がいつでも変更される可能性があり、変更する必要があることをコンパイラに示します。 ない ローカルにキャッシュされたコピーを使用します。

以上のことをすべて踏まえた上で、より良い結果が見つかるかもしれません(でほのめかされているように) ジェフ) セマフォまたは条件変数を使用します。

これ この主題への適切な導入です。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top