6

アトミック::ロードがメモリバリアとしても機能し、以前のすべての非アトミック書き込みが他のスレッドから見えるようになると仮定するのは間違っていますか?

説明する:

volatile bool arm1 = false;
std::atomic_bool arm2 = false;
bool triggered = false;

スレッド 1:

arm1 = true;
//std::std::atomic_thread_fence(std::memory_order_seq_cst); // this would do the trick 
if (arm2.load())
    triggered = true;

スレッド 2:

arm2.store(true);
if (arm1)
    triggered = true;

両方の「トリガー」を実行した後、真になると予想しました。arm1 をアトミックにすることを提案しないでください。要点は、atomic::load の動作を調査することです。

私はメモリ順序のさまざまな緩和されたセマンティクスの正式な定義を完全には理解していないことを認めなければなりませんが、順次一貫性のある順序付けは、「すべてのスレッドがすべての変更を観察する単一の全体的な順序が存在する」ことを保証するという点で非常に簡単だと思いました同じ順番で。」これは、デフォルトのメモリ順序 std::memory_order_seq_cst を持つ std::atomic::load がメモリ フェンスとしても機能することを意味します。これは、「順次一貫性のある順序付け」の下にある次のステートメントによってさらに裏付けられます。

すべてのマルチコア システムで完全な順次順序付けを行うには、完全なメモリ フェンス CPU 命令が必要です。

しかし、以下の簡単な例は、MSVC 2013、gcc 4.9 (x86)、および clang 3.5.1 (x86) では、アトミック ロードが単純にロード命令に変換される場合に当てはまらないことを示しています。

#include <atomic>

std::atomic_long al;

#ifdef _WIN32
__declspec(noinline)
#else
__attribute__((noinline))
#endif
long load() {
    return al.load(std::memory_order_seq_cst);
}

int main(int argc, char* argv[]) {
    long r = load();
}

gcc では次のようになります。

load():
   mov  rax, QWORD PTR al[rip]   ; <--- plain load here, no fence or xchg
   ret
main:
   call load()
   xor  eax, eax
   ret

基本的に同じである msvc と clang は省略します。ARM の gcc では、期待どおりの結果が得られます。

load():
     dmb    sy                         ; <---- data memory barrier here
     movw   r3, #:lower16:.LANCHOR0
     movt   r3, #:upper16:.LANCHOR0
     ldr    r0, [r3]                   
     dmb    sy                         ; <----- and here
     bx lr
main:
    push    {r3, lr}
    bl  load()
    movs    r0, #0
    pop {r3, pc}

これは学術的な質問ではありません。コード内で微妙な競合状態が発生し、std::atomic の動作に関する私の理解が疑問視されました。

4

2 に答える 2

3

ため息、これはコメントするには長すぎました:

アトミックの意味は「システムの残りの部分に瞬時に発生するように見えること」ではないでしょうか?

あなたがそれをどのように考えるかに応じて、私はそれに対してイエスとノーと言います. はいSEQ_CST。ただし、アトミック ロードの処理方法については、C++11 標準の 29.3 を確認してください。具体的には、29.3.3 は非常に優れた読み物であり、29.3.4 は具体的に探しているものである可能性があります。

アトミック オブジェクト M の値を読み取るアトミック操作 B の場合、B の前にシーケンスされた memory_order_seq_cst フェンス X がある場合、B は、合計順序 S で X に先行する M の最後の memory_order_seq_cst 変更、または後の変更のいずれかを観察します。 M を変更順序で示します。

基本的にSEQ_CST、標準と同じようにグローバルな順序を強制しますが、読み取りは「アトミック」制約に違反することなく古い値を返すことができます。

「絶対最新値の取得」を達成するには、ハードウェア コヒーレンシ プロトコルを強制的にロックする操作を実行する必要があります ( lockx86_64 の命令)。アセンブリの出力を見ると、これがアトミックな比較交換操作の動作です。

于 2015-03-04T02:39:17.237 に答える
2

アトミック::ロードがメモリバリアとしても機能し、以前のすべての非アトミック書き込みが他のスレッドから見えるようになると仮定するのは間違っていますか?

はい。読み取りが「無効な」値をロードできないことを強制するだけであり、そのステートメントの周りのコンパイラまたは cpuatomic::load(SEQ_CST)によって書き込みもロードも並べ替えられない可能性があります。常に最新の値が得られるわけではありません。

繰り返しますが、バリアは特定の時点で最新の値が表示されることを保証せず、並べ替えを防ぐだけなので、コードにデータ競合があると予想されます。

Thread1 が Thread2 による書き込みを認識せず、したがって settriggeredを認識しないこと、および Thread2 が Thread1 による書き込みを認識しないこと (繰り返しtriggeredますが、設定しない) は完全に有効です。

2 つのスレッドが共有値の書き込みと読み取りを行うため、一貫性を維持するために各スレッドにバリアが必要になります。これはコードのコメントに基づいてすでにわかっているようですので、「アトミック/マルチスレッド操作の意味を正確に説明することになると、C++ 標準はやや誤解を招く可能性があります」のままにしておきます。

あなたが C++ を書いているとしても、私の意見では、基礎となるアーキテクチャーで何をしているのかを考えるのが最善です。

うまく説明できていませんが、よろしければ詳しく教えていただければ幸いです。

于 2015-03-01T17:00:05.773 に答える