std :: atomicとは正確には何ですか?

186
Noname 2015-08-13 16:00.

私はそれstd::atomic<>が原子オブジェクトであることを理解しています。しかし、どの程度アトミックですか?私の理解では、操作はアトミックである可能性があります。オブジェクトをアトミックにすることは正確にはどういう意味ですか?たとえば、次のコードを同時に実行している2つのスレッドがある場合:

a = a + 12;

それでは、操作全体(たとえばadd_twelve_to(int))はアトミックですか?または、変数atomic(so operator=())に変更が加えられていますか?

3 answers

206
Mateusz Grzejek 2015-08-13 16:55.

std :: atomic <>の各インスタンス化と完全な特殊化は、未定義の動作を発生させることなく、異なるスレッドが(それらのインスタンスで)同時に操作できるタイプを表します。

アトミックタイプのオブジェクトは、データの競合がない唯一のC ++オブジェクトです。つまり、あるスレッドがアトミックオブジェクトに書き込み、別のスレッドがアトミックオブジェクトから読み取る場合、動作は明確に定義されています。

さらに、アトミックオブジェクトへのアクセスは、スレッド間同期を確立し、で指定された非アトミックメモリアクセスを順序付ける場合がありますstd::memory_order

std::atomic<>C ++より前の11回では、(たとえば)MSVCまたはGCCの場合はアトミックブルチンとのインターロック関数を使用して実行する必要があった操作をラップします。

また、同期と順序の制約を指定std::atomic<>するさまざまなメモリ順序を許可することで、より詳細な制御が可能になります。C ++ 11アトミックとメモリモデルについて詳しく知りたい場合は、次のリンクが役立つ場合があります。

一般的なユースケースでは、おそらくオーバーロードされた算術演算子またはそれらの別のセットを使用することに注意してください。

std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

演算子構文ではメモリの順序を指定できないため、これらの操作はで実行されstd::memory_order_seq_cstます。これは、C ++ 11のすべてのアトミック操作のデフォルトの順序です。これにより、すべてのアトミック操作間の逐次一貫性(グローバルな順序の合計)が保証されます。

ただし、場合によっては、これは必須ではない可能性があるため(無料では何も提供されません)、より明示的な形式を使用することをお勧めします。

std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

さて、あなたの例:

a = a + 12;

単一のアトミック操作に評価されません。結果はa.load()(アトミック自体)になり、この値12a.store()(またアトミック)の間に最終結果が加算されます。前に述べたように、std::memory_order_seq_cstここで使用されます。

ただし、と書くとa += 12、(前に述べたように)アトミック操作になり、とほぼ同等になりa.fetch_add(12, std::memory_order_seq_cst)ます。

あなたのコメントについて:

レギュラーにintはアトミックロードとストアがあります。それを包むことのポイントはatomic<>何ですか?

あなたの声明は、ストアやロードにアトミック性のそのような保証を提供するアーキテクチャにのみ当てはまります。これを行わないアーキテクチャがあります。また、通常、アトミックstd::atomic<>であるためには、ワード/ドワードに整列されたアドレスで操作を実行する必要があります。これは、追加の要件なしで、すべてのプラットフォームでアトミックであることが保証されているものです。さらに、次のようなコードを記述できます。

void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

アサーション条件は常に真になる(したがって、トリガーされることはない)ため、whileループの終了後にデータの準備ができていることを常に確認できることに注意してください。それは理由です:

  • store()フラグへの変換は、sharedDataが設定された後に実行され(generateData()常に有用なものを返すと想定し、特に決して返さないNULL)、std::memory_order_release順序を使用します。

memory_order_release

このメモリ順序でのストア操作は、解放 操作を実行します。現在のスレッドでの読み取りまたは書き込みは、このストアので並べ替えることはできません 。現在のスレッドでのすべての書き込みは、同じアトミック変数を取得する他のスレッドで表示されます

  • sharedDatawhileループが終了した後に使用されるため、afterfromload()フラグはゼロ以外の値を返します。順序をload()使用しstd::memory_order_acquireます:

std::memory_order_acquire

このメモリ順序でのロード操作は、影響を受けるメモリ位置で取得操作を実行します。このロードのに、現在のスレッドでの読み取りまたは書き込みを並べ替えることはできません。同じアトミック変数をリリースする他のスレッドでのすべての書き込みは、現在のスレッドに表示されます

これにより、同期を正確に制御でき、コードがどのように動作するか、しないか、しないか、動作しないかを明示的に指定できます。保証が原子性自体である場合、これは不可能です。特に、リリース-消費順序のような非常に興味深い同期モデルに関しては。

21
Tony Delroy 2015-08-13 16:42.

私はそれstd::atomic<>がオブジェクトをアトミックにすることを理解しています。

これは視点の問題です...任意のオブジェクトに適用してその操作をアトミックにすることはできませんが、(ほとんどの)整数型とポインターに提供されている特殊化を使用できます。

a = a + 12;

std::atomic<>これを(テンプレート式を使用して)単一のアトミック操作に単純化するのではなく、代わりにoperator T() const volatile noexceptメンバーがアトミックload()を実行しa、次に12が追加され、をoperator=(T t) noexcept実行しstore(t)ます。

std::atomic 多くのISAがハードウェアを直接サポートしているために存在します

C ++標準が述べstd::atomicていることは、他の回答で分析されています。

それではstd::atomic、別の種類の洞察を得るために何がコンパイルされるかを見てみましょう。

この実験の主なポイントは、最新のCPUはアトミック整数演算(x86のLOCKプレフィックスなど)を直接サポートしており、std::atomic基本的にこれらの命令へのポータブルインターフェイスとして存在することです。x86x86アセンブリでの「ロック」命令とはどういう意味ですか?aarch64では、LDADDが使用されます。

このサポートは、次のような、より一般的な方法への高速な代替することができますstd::mutexよりも遅いことのコストで、より複雑なマルチ命令セクションは、原子することができます、std::atomicので、std::mutexそれが可能futexに遅いから放出されたユーザランドの指示よりも方法ですLinuxではシステムコールをstd::atomic、参照:std :: mutexはフェンスを作成しますか?

使用するプリプロセッサ定義に応じて異なる同期メカニズムを使用して、複数のスレッドにわたってグローバル変数をインクリメントする次のマルチスレッドプログラムについて考えてみましょう。

main.cpp

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

size_t niters;

#if STD_ATOMIC
std::atomic_ulong global(0);
#else
uint64_t global = 0;
#endif

void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
#if LOCK
        __asm__ __volatile__ (
            "lock incq %0;"
            : "+m" (global),
              "+g" (i) // to prevent loop unrolling
            :
            :
        );
#else
        __asm__ __volatile__ (
            ""
            : "+g" (i) // to prevent he loop from being optimized to a single add
            : "g" (global)
            :
        );
        global++;
#endif
    }
}

int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
        nthreads = std::stoull(argv[1], NULL, 0);
    } else {
        nthreads = 2;
    }
    if (argc > 2) {
        niters = std::stoull(argv[2], NULL, 0);
    } else {
        niters = 10;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i].join();
    uint64_t expect = nthreads * niters;
    std::cout << "expect " << expect << std::endl;
    std::cout << "global " << global << std::endl;
}

GitHubアップストリーム

コンパイル、実行、逆アセンブル:

comon="-ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic main.cpp -pthread"
g++ -o main_fail.out                    $common
g++ -o main_std_atomic.out -DSTD_ATOMIC $common
g++ -o main_lock.out       -DLOCK       $common

./main_fail.out       4 100000
./main_std_atomic.out 4 100000
./main_lock.out       4 100000

gdb -batch -ex "disassemble threadMain" main_fail.out
gdb -batch -ex "disassemble threadMain" main_std_atomic.out
gdb -batch -ex "disassemble threadMain" main_lock.out

非常に可能性の高い「間違った」競合状態の出力main_fail.out

expect 400000
global 100000

他の決定論的な「正しい」出力:

expect 400000
global 400000

の分解main_fail.out

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     mov    0x29b5(%rip),%rcx        # 0x5140 <niters>
   0x000000000000278b <+11>:    test   %rcx,%rcx
   0x000000000000278e <+14>:    je     0x27b4 <threadMain()+52>
   0x0000000000002790 <+16>:    mov    0x29a1(%rip),%rdx        # 0x5138 <global>
   0x0000000000002797 <+23>:    xor    %eax,%eax
   0x0000000000002799 <+25>:    nopl   0x0(%rax)
   0x00000000000027a0 <+32>:    add    $0x1,%rax
   0x00000000000027a4 <+36>:    add    $0x1,%rdx
   0x00000000000027a8 <+40>:    cmp    %rcx,%rax
   0x00000000000027ab <+43>:    jb     0x27a0 <threadMain()+32>
   0x00000000000027ad <+45>:    mov    %rdx,0x2984(%rip)        # 0x5138 <global>
   0x00000000000027b4 <+52>:    retq

の分解main_std_atomic.out

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a6 <threadMain()+38>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock addq $0x1,0x299f(%rip)        # 0x5138 <global>
   0x0000000000002799 <+25>:    add    $0x1,%rax
   0x000000000000279d <+29>:    cmp    %rax,0x299c(%rip)        # 0x5140 <niters>
   0x00000000000027a4 <+36>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a6 <+38>:    retq   

の分解main_lock.out

Dump of assembler code for function threadMain():
   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a5 <threadMain()+37>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock incq 0x29a0(%rip)        # 0x5138 <global>
   0x0000000000002798 <+24>:    add    $0x1,%rax
   0x000000000000279c <+28>:    cmp    %rax,0x299d(%rip)        # 0x5140 <niters>
   0x00000000000027a3 <+35>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a5 <+37>:    retq

結論:

  • 非アトミックバージョンは、グローバルをレジスタに保存し、レジスタをインクリメントします。

    したがって、最後に、同じ「間違った」値でグローバルに4回の書き込みが発生する可能性が非常に高くなります100000

  • std::atomicにコンパイルされlock addqます。LOCKプレフィックスは、次のincメモリをアトミックにフェッチ、変更、および更新します。

  • 明示的なインラインアセンブリのLOCKプレフィックスは、の代わりに使用されるstd::atomicことを除いて、とほぼ同じものにコンパイルされます。INCが1バイト小さいデコードを生成したことを考えると、GCCが選択した理由はわかりません。incaddadd

ARMv8は、新しいCPUでLDAXR + STLXRまたはLDADDのいずれかを使用できますプレーンCでスレッドを開始するにはどうすればよいですか?

Ubuntu 19.10 AMD64、GCC 9.2.1、Lenovo ThinkPadP51でテスト済み。

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

あなたが危険なほど過熱しているかどうかを知る方法

あなたが危険なほど過熱しているかどうかを知る方法

カリフォルニア州カレキシコでは、6月上旬に気温が約115度に達すると、人々はダウンタウンを歩きます。私たちは夏にたった2週間で、熱波はすでに米国を灼熱しています。

庵野秀明がシン・ウルトラマンのモーションキャプチャ俳優として最高の人生を送るのを見る

庵野秀明がシン・ウルトラマンのモーションキャプチャ俳優として最高の人生を送るのを見る

庵野秀明は、ついに新世紀エヴァンゲリオンを締めくくった後、自分のやりたいことを何でもしているようだ(少なくとも今のところ)。彼はウルトラマン映画を作るようになり、現在は仮面ライダー1号を監督しており、生涯にわたる2つのスーパーヒーローの執着のために新しい章を作成することを彼に任せています。

今週のおもちゃのニュースは間違いなくあなたのウーキーを曲げました

今週のおもちゃのニュースは間違いなくあなたのウーキーを曲げました

io9の最新かつ最高のオタク玩具ニュースの定期的なまとめであるToyAisleへようこそ。今週は、オビ=ワンの水曜日をさらに2つのハズブロフィギュアで締めくくります。Super7はスプリングフィールドに戻り、さらに多くのシンプソンズアルティメットを目指します。ダースベイダーをバップする勇気はありますか?見てみな!プレイメイトは、初期の頃からのシンプソンズのフィギュアの信じられないほど豊富なラインで素晴らしい仕事をしましたが、Super7のシンプソンズアルティメットほど詳細でアクセサリー化されていませんでした!数字—彼らがもっと早く彼らを追い出し始めてくれることを願っています。

ディズニーの不条理な$5,000スターウォーズカクテルの実際の内容は次のとおりです

ディズニーの不条理な$5,000スターウォーズカクテルの実際の内容は次のとおりです

その悪名高い5,000ドルのスターウォーズをテーマにした飲み物に何が入っているのか知りたいと思っているなら、それはもはや完全な謎ではありません。ハイパースペースラウンジのディズニーウィッシュクルーズ船で独占的に入手できるカイバークリスタルは、豪華に提示されたテーマカクテルで、提供されるカムトノに付属しているはずですが、残念ながらそうではありません。銀河系で最も高価なトップシェルフセレクション。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

Seguindo Todos os Protocolos (2022), de Fábio Leal

Seguindo Todos os Protocolos (2022), de Fábio Leal

Chico quer transar. Até aí, tudo bem.

多元宇宙—Junø

多元宇宙—Junø

チェーン間アカウントがJunoに登場します。異なるブロックチェーン間でスマートコントラクトの構成可能性と真の相互運用性を提供します。

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

#brand【ベター・コール・ソール!アメリカのテレビシリーズ「ブレイキング・バッド」に最高のビジネス例が隠されている】・・・ルールクリエイティブ

1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「​​ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。

メインネットガイド— Arbitrum Odyssey Week 2

メインネットガイド— Arbitrum Odyssey Week 2

最新のアップデートを受け取るために私たちに従ってください。ニュースレター:https://www。

Language