web-dev-qa-db-ja.com

C ++スレッド、共有データ

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

スレッド1:

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

スレッド2:

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

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

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

28
fabiopedrosa

はい。いいえ、たぶん。

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

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

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

24
puetzk

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

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

7
Niall

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

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;
  }
}
7

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

volatile int myInteger;

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

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

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

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

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

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

6
Tall Jeff

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

3
Jeff

クリス・ジェスター-ヤングは次のように指摘しました。

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

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

3
fabiopedrosa

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

volatile int myInteger;
2
Adam Pierce

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

2
Lou Franco

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

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

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

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

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

This は主題への合理的な紹介です。

1

スコープが正しい場合(「外部」、グローバルなど)、変更が通知されます。問題はいつですか?そして、どのような順序で?

問題は、コンパイラでき、頻繁に再注文することですパフォーマンスの最適化として、すべての並行パイプラインを埋めるためのロジック。

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

チェックアウト パイプラインハザード ウィキペディアで、またはグーグルで「コンパイラ命令の並べ替え」を検索してください

1
kervin