4

いくつかのソース コードを確認していますが、次のコードはスレッド セーフかどうか疑問に思っていました。コンパイラまたは CPU 命令/読み取りの並べ替えについて聞いたことがあります (分岐予測と関係がありますか?)。以下の Data->unsafe_variable 変数は、別のスレッドによっていつでも変更できます。

私の質問は次のとおりです: コンパイラ/CPU が読み取り/書き込みをどのように並べ替えるかによって、以下のコードで Data->unsafe_variable を 2 回フェッチできるようになる可能性はありますか? (2 番目のスニペットを参照)

:最初のアクセスについては心配していません。「if」を通過しない限り、データはそこにある可能性があります。「if」の後にデータが別の時間にフェッチされる可能性が心配です。ここで volatile へのキャストが二重フェッチを防ぐのに役立つかどうかも疑問に思っていましたか?

int function(void* Data) {

    // Data is allocated on the heap
    // What it contains at this point is not important
    size_t _varSize = ((volatile DATA *)Data)->unsafe_variable;

    if (_varSize > x * y)
    {
        return FALSE;
    }

    // I do not want Data->unsafe_variable to be fetch once this point reached,
    // I want to use the value "supposedly" stored in _varSize
    // Would any compiler/CPU reordering would allow it to be double fetched?

    size_t size = _varSize - t * q;

    function_xy(size);

    return TRUE;
}

基本的に、セキュリティ上の理由から、プログラムがこのように動作することは望ましくありません。

    _varSize = ((volatile DATA *)Data)->unsafe_variable;
    if (_varSize > x * y)
    {
        return FALSE;
    }

    size_t size = ((volatile DATA *)Data)->unsafe_variable - t * q;
    function10(size);

ここでは単純化していますが、mutex は使用できません。しかし、揮発性キャストの代わりに最初の行の後に _ReadWriteBarrier() または MemoryBarrier() を使用する方が安全でしょうか? (VSコンパイラ)

編集:コードにもう少しコンテキストを与えます。

4

3 に答える 3

4

コードは多くの理由で壊れています。他の人がより明白なものを指摘したように、私はより微妙なものの1つを指摘します. オブジェクトは ではありませんvolatile。ポインターを揮発性オブジェクトへのポインターにキャストしても、オブジェクトが揮発性になるわけではなく、コンパイラーに嘘をつくだけです。

しかし、もっと大きなポイントがあります - あなたはこれについて完全に間違った方法で進んでいます。コードが正しいかどうか、つまり、動作が保証されているかどうかを確認する必要があります。あなたは、システムが想定していることをシステムが実行できない可能性があるすべての方法を考えるほど賢くはありません。代わりに、これらの仮定をしないでください。

CPU 読み取りの再順序付けなどについて考えるのは完全に間違っています。CPU が何をする必要があるかを期待する必要があります。失敗する可能性のある特定のメカニズムについては絶対に考えるべきではなく、動作が保証されているかどうかだけを考えるべきです。

あなたがしていることは、従業員がインフルエンザの予防接種を受けたかどうか、まだ生きているかどうかなどをチェックすることによって、従業員が出勤することが保証されているかどうかを把握しようとしているようなものです。彼が現れない可能性のあるすべての方法をチェックしたり、考えたりすることはできません。したがって、そのようなことを確認する必要があることがわかった場合、それは保証されておらず、それに依存していることは壊れています. 限目。

「CPUはこれを壊すようなことはしないから大丈夫」と言って、信頼できるコードを作ることはできません。「関連する標準によって保証されていないものにコードが依存していないことを確認します」と言うと、信頼できるコードを作成できます。

メモリ バリア、アトミック操作、ミューテックスなど、ジョブを実行するために必要なすべてのツールが提供されます。そちらをご利用ください。

あなたは、動作が保証されていないものが失敗する可能性をあらゆる方法で考えるほど賢くありません。そして、動作が保証されているものがたくさんあります。このコードを修正し、可能であれば、それを書いた人と適切な同期の使用について話し合ってください。

これは少し乱暴に聞こえますが、申し訳ありません。しかし、このような「トリック」を使用して、テスト マシンでは完全に機能したが、新しい CPU、新しいコンパイラ、または OS の新しいバージョンが登場したときに壊れたコードをあまりにも多く見てきました。これらのハックは実際の同期要件を隠してしまうため、このようなコードを修正することは信じられないほどの苦痛になる可能性があります。正しい答えは、ほとんどの場合、実際に欲しいものを明確かつ正確にコーディングすることです。そうしない理由がわからないからといって、それを手に入れることができると思い込むのではありません。

辛い経験からの貴重なアドバイスです。

于 2012-06-08T08:35:53.343 に答える
1

基準は明確です。いずれかのスレッドがオブジェクトを変更している可能性がある場合は、すべてのスレッドですべてのアクセスを同期する必要があります。そうしないと、動作が未定義になります。

于 2012-06-08T07:36:39.913 に答える
0

C++ の唯一の移植可能なソリューションは、次期 VS 2012で利用可能なC++11 アトミックです。

Cに関しては、最近のC標準がいくつかの移植可能な機能をもたらすかどうかはわかりません.

それでも、Visual Studio 向けに開発していることがわかっている場合は、C と C++ の両方に適用される、このコンパイラによって提供される保証を信頼できます。それらの一部は暗黙的 (揮発性変数へのアクセスは、いくつかのメモリ バリアが適用されることも意味します) であり、_MemoryBarrier組み込み関数を使用するなど、明示的なものもあります。

メモリ モデルのトピック全体については、Xbox 360 および Microsoft Windows のロックレス プログラミングに関する考慮事項で詳細に説明されています。これにより、概要がよくわかるはずです。注意: 入力しようとしているトピックは、難しいトピックや不快な驚きでいっぱいです。

注: volatile に依存することは移植性がありませんが、古い C / C++ 標準を使用している場合、とにかく移植性のあるソリューションはありません。したがって、必要が生じた場合に別のプラットフォームでこれを再実装する必要性に直面する準備をしてください。移植可能なスレッド コードを記述する場合、volatile はほとんど役に立たないと見なされます。

マルチスレッド プログラミングの場合、volatile が対処すると誤って考えられることが多い 2 つの重要な問題があります。

  • 原子性

  • メモリの一貫性、つまり、別のスレッドから見たスレッドの操作の順序。

于 2012-06-08T08:17:20.710 に答える