私はそれstd::atomic<>
が原子オブジェクトであることを理解しています。しかし、どの程度アトミックですか?私の理解では、操作はアトミックである可能性があります。オブジェクトをアトミックにすることは正確にはどういう意味ですか?たとえば、次のコードを同時に実行している2つのスレッドがある場合:
a = a + 12;
それでは、操作全体(たとえばadd_twelve_to(int)
)はアトミックですか?または、変数atomic(so operator=()
)に変更が加えられていますか?
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()
(アトミック自体)になり、この値12
とa.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
このメモリ順序でのストア操作は、解放 操作を実行します。現在のスレッドでの読み取りまたは書き込みは、このストアの後で並べ替えることはできません 。現在のスレッドでのすべての書き込みは、同じアトミック変数を取得する他のスレッドで表示されます
sharedData
while
ループが終了した後に使用されるため、afterfromload()
フラグはゼロ以外の値を返します。順序をload()
使用しstd::memory_order_acquire
ます:
std::memory_order_acquire
このメモリ順序でのロード操作は、影響を受けるメモリ位置で取得操作を実行します。このロードの前に、現在のスレッドでの読み取りまたは書き込みを並べ替えることはできません。同じアトミック変数をリリースする他のスレッドでのすべての書き込みは、現在のスレッドに表示されます。
これにより、同期を正確に制御でき、コードがどのように動作するか、しないか、しないか、動作しないかを明示的に指定できます。保証が原子性自体である場合、これは不可能です。特に、リリース-消費順序のような非常に興味深い同期モデルに関しては。
私はそれ
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;
}
コンパイル、実行、逆アセンブル:
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が選択した理由はわかりません。inc
add
add
ARMv8は、新しいCPUでLDAXR + STLXRまたはLDADDのいずれかを使用できますプレーンCでスレッドを開始するにはどうすればよいですか?
Ubuntu 19.10 AMD64、GCC 9.2.1、Lenovo ThinkPadP51でテスト済み。
特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。
ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。
ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。
カリフォルニア州カレキシコでは、6月上旬に気温が約115度に達すると、人々はダウンタウンを歩きます。私たちは夏にたった2週間で、熱波はすでに米国を灼熱しています。
庵野秀明は、ついに新世紀エヴァンゲリオンを締めくくった後、自分のやりたいことを何でもしているようだ(少なくとも今のところ)。彼はウルトラマン映画を作るようになり、現在は仮面ライダー1号を監督しており、生涯にわたる2つのスーパーヒーローの執着のために新しい章を作成することを彼に任せています。
io9の最新かつ最高のオタク玩具ニュースの定期的なまとめであるToyAisleへようこそ。今週は、オビ=ワンの水曜日をさらに2つのハズブロフィギュアで締めくくります。Super7はスプリングフィールドに戻り、さらに多くのシンプソンズアルティメットを目指します。ダースベイダーをバップする勇気はありますか?見てみな!プレイメイトは、初期の頃からのシンプソンズのフィギュアの信じられないほど豊富なラインで素晴らしい仕事をしましたが、Super7のシンプソンズアルティメットほど詳細でアクセサリー化されていませんでした!数字—彼らがもっと早く彼らを追い出し始めてくれることを願っています。
その悪名高い5,000ドルのスターウォーズをテーマにした飲み物に何が入っているのか知りたいと思っているなら、それはもはや完全な謎ではありません。ハイパースペースラウンジのディズニーウィッシュクルーズ船で独占的に入手できるカイバークリスタルは、豪華に提示されたテーマカクテルで、提供されるカムトノに付属しているはずですが、残念ながらそうではありません。銀河系で最も高価なトップシェルフセレクション。
Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday
シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。
オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。
1.ドラマを見た後、起業する考えはありますか?あなたのビジネスはボトルネックに遭遇しましたか?方向性がなくてわからない場合は、ドラマを追いかけて行くことを心からお勧めします。(?)ブラフではなく、最も完璧なビジネス例を隠すドラマがあります。2.ブレイキング・バッドとその弁護士ドラマ「ブレイキング・バッド」を見た友人たちは、演劇の中で、穏やかな表情で、弁護士のソウル・グッドマンに深く感銘を受けなければなりません。口を開けて、感覚の弱い傭兵の性格を持っています。道徳の面で、サル・グッドマンは無意識のうちに劇に欠かせない役割を果たし、彼自身のシリーズ「絶望的な弁護士」(ベター・コール・ソール)を生み出しました。ウェントウのテキストとビデオは、劇中のソウル・グッドマンのテレビコマーシャルです。製品(サービス)、競争戦略、市場ポジショニング、ブランド名、ターゲット顧客グループ、コミュニケーション軸から広告まで、サル・グッドマンの役割のビジネス設定は、「最低」と見なすことができる超超超超超超完全です。ブランドコミュニケーションのコスト」「変化」のモデル。なぜ?私の分析をご覧ください。3.ソウル・グッドマンの「事業戦略」1.基本情報ブランド名:Saul Goodman製品:法律相談サービス対象顧客:麻薬中毒、飲酒運転、事故など。法律知識の欠如は、一般的に公立弁護士にしか余裕がなく、真面目な弁護士も「特別な法律を持つ消費者」を避けます。恐れてはいけない「ニーズ」。コミュニケーションの主軸:この国のすべての男性、女性、子供は有罪判決を受けるまで無実だと思います。地域:アルバカーキ市スローガン:Thrallに電話したほうがいいです!(ベター・コール・ソール)広告:2つの可能性のある犯罪状況をシミュレートします+サウルの主張+サウルのスローガン2をより適切に呼び出します。