30

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

スレッド 1:

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

スレッド 2:

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

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

PS: これはゲームのようなアプリケーションのためのものなので、値が書き込まれている間に読み取りが行われるかどうかは実際には問題ではありません...重要なのは、変更が他のスレッドによって通知されることだけです。

4

10 に答える 10

25

はい。いいえ、たぶん。

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

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

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

于 2008-09-22T23:56:36.297 に答える
7

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

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;
  }
}
于 2008-09-22T23:46:29.597 に答える
7

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

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

于 2008-09-22T23:51:13.127 に答える
6

volatileキーワードを使用して、値がいつでも変更される可能性があることをコンパイラに通知します。

volatile int myInteger;

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

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

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

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

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

発生する原子性に関連する最後の問題は、読み取り-変更-書き込みの原子性に固有のものです。つまり、値が読み取られて値が更新され、書き込まれた場合、これがアトミックに発生することを保証するにはどうすればよいでしょうか。プロセッサが複数ある場合でも同様です。したがって、これが特定の同期オブジェクトなしで機能するには、変数にアクセスする可能性のあるすべてのスレッドがリーダーのみである必要がありますが、一度にライターになることができるのは 1 つのスレッドだけである必要があります。そうでない場合は、変数への読み取り-変更-書き込みアクションでアトミック アクションを確実に実行できるように、使用可能な同期オブジェクトが必要です。

于 2008-09-23T00:08:26.000 に答える
3

あなたのソリューションは、他の問題の中でもとりわけ、100% の CPU を使用します。「条件変数」のGoogle。

于 2008-09-22T23:25:52.563 に答える
3

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

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

そうであれば、唯一の正解は同期システムを実装することですよね?

于 2008-09-22T23:36:21.913 に答える
2

volatileキーワードを使用して、値がいつでも変更される可能性があることをコンパイラに通知します。

volatile int myInteger;
于 2008-09-22T23:24:08.977 に答える
2

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

于 2008-09-22T23:25:13.423 に答える
1

スコープが正しい場合 (「extern」、グローバルなど)、変更が通知されます。問題はいつですか?で、順番は?

問題は、パフォーマンスの最適化として、コンパイラがロジックを並べ替えて、すべての並行パイプラインを満たすようにすることができ、頻繁に行うことです。

割り当ての周りに他の命令がないため、特定の例には実際には表示されませんが、ブール割り当ての後に宣言された関数が割り当てのに実行されると想像してください。

ウィキペディアでPipeline Hazardを確認する か、Google で「コンパイラ命令の並べ替え」を検索してください。

于 2008-09-22T23:47:02.527 に答える
1

他の人が言ったように、volatileキーワードはあなたの友達です。:-)

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

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

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

以上のことから、セマフォまたは条件変数を使用することで、より良い結果が得られる可能性があります ( Jeffがほのめかしたように)。

これは、主題への合理的な導入です。

于 2008-09-22T23:53:45.617 に答える