70

私がレビューしたコードベースで、次のイディオムを見つけました。

void notify(struct actor_t act) {
    write(act.pipe, "M", 1);
}
// thread A sending data to thread B
void send(byte *data) {
    global.data = data;
    notify(threadB);
}
// in thread B event loop
read(this.sock, &cmd, 1);
switch (cmd) {
    case 'M': use_data(global.data);break;
    ...
}

「保持してください」と、私のチームのシニアメンバーである作者に言いました。「ここにはメモリバリアはありませんglobal.data。キャッシュからメインメモリにフラッシュされる保証はありません。スレッドAとスレッドBが実行される場合2つの異なるプロセッサ-このスキームは失敗する可能性があります。」

シニアプログラマーはニヤリと笑い、靴ひもを結ぶ方法を5歳の男の子に説明するかのようにゆっくりと説明しました。彼の長いあごひげを引っ掻くために一時停止しました、「しかし、私たちはこのイディオムにバグがあったことはありません」。

「しかし、それは本の中で述べています...」

「静かに!」と彼はすぐに私を黙らせました。「理論的には保証されないかもしれませんが、実際には、関数呼び出しを使用したという事実は事実上メモリバリアです。コンパイラは命令global.data = dataを並べ替えません。関数呼び出しでそれを使用する人は誰でも、x86アーキテクチャは、スレッドBがパイプからコマンドを読み取るまでに、他のCPUがこのグローバルデータを確実に認識できるようにします。実際の問題は十分にありますので、ご安心ください。偽の理論上の問題に余分な労力を費やす必要はありません。

「私の少年は安心してください。やがて、本当の問題を博士号を取得する必要のある非問題から分離することが理解できるでしょう。」

彼は正しいですか?それは実際には問題ではありませんか(x86、x64、ARMなど)?

それは私が学んだすべてに反します、しかし彼は長いあごひげと本当にスマートな外見を持っています!

彼が間違っていることを証明するコードを見せてくれれば、追加のポイントがあります!

4

4 に答える 4

12

メモリバリアは、命令の並べ替えを防ぐためだけのものではありません。命令を並べ替えなくても、キャッシュコヒーレンスに問題が発生する可能性があります。並べ替えについては、コンパイラと設定によって異なります。ICCは、並べ替えに対して特に積極的です。プログラム全体の最適化を伴うMSVCも可能です。

共有データ変数がとして宣言されvolatileいる場合、仕様に含まれていなくても、ほとんどのコンパイラは変数からの読み取りと書き込みの周りにメモリ変数を生成し、並べ替えを防ぎます。これは正しい使用方法ではなく、そのvolatile目的でもありません。

(投票が残っている場合は、ナレーションの質問を+1します。)

于 2012-05-22T08:21:34.937 に答える
10

実際には、関数呼び出しはコンパイラの障壁です。つまり、コンパイラはグローバルメモリアクセスを呼び出しを超えて移動しません。これに対する警告は、コンパイラが知っている関数です。たとえば、組み込み関数、インライン関数(IPOに注意してください!)などです。

したがって、これを機能させるには、理論的にはプロセッサのメモリバリア(コンパイラのバリアに加えて)が必要です。ただし、グローバル状態を変更するシステムコールである読み取りと書き込みを呼び出しているので、カーネルはそれらの実装のどこかでメモリバリアを発行すると確信しています。ただし、そのような保証はないため、理論的には障壁が必要です。

于 2012-05-22T08:24:32.580 に答える
2

基本的なルールは次のとおりです。コンパイラは、グローバル状態をコーディングしたとおりに表示する必要がありますが、特定の関数がグローバル変数を使用していないことを証明できる場合は、選択した方法でアルゴリズムを実装できます。

結果として、従来のコンパイラは、別のコンパイルユニット内の関数を、それらの関数の内部を見ることができなかったため、常にメモリバリアとして扱っていました。ますます、最近のコンパイラは、これらの障壁を打ち破り、何年も正常に機能しているにもかかわらず、不十分に記述されたコードを失敗させる「プログラム全体」または「リンク時間」最適化戦略を成長させています。

問題の関数が共有ライブラリにある場合、その内部を表示することはできませんが関数がC標準で定義されている関数である場合は、その必要はありません-関数が何をするかはすでにわかっています- -ですから、それらにも注意する必要があります。コンパイラはカーネル呼び出しを認識しませんが、コンパイラが認識できないもの(インラインアセンブラ、またはアセンブラファイルへの関数呼び出し)を挿入するという行為自体がメモリバリアを作成することに注意してください。

あなたの場合、notifyコンパイラが内部を見ることができないブラックボックス(ライブラリ関数)であるか、認識可能なメモリバリアが含まれているため、おそらく安全です。

実際には、これを乗り越えるには非常に悪いコードを書かなければなりません。

于 2012-05-22T09:34:07.373 に答える
2

実際には、彼は正しく、この特定のケースではメモリバリアが暗示されています。

しかし、要点は、その存在が「議論の余地がある」場合、コードはすでに複雑すぎて不明確であるということです。

本当にみんな、ミューテックスまたは他の適切な構造を使用してください。これは、スレッドを処理し、保守可能なコードを作成するための唯一の安全な方法です。

また、send()が複数回呼び出された場合、コードが予測できないなど、他のエラーが発生する可能性があります。

于 2012-05-28T16:32:12.997 に答える