私はそれ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でテスト済み。
Reba McEntire が息子の Shelby Blackstock と共有しているクリスマスの伝統について学びましょう。
メーガン・マークルとマライア・キャリーが自然な髪の上でどのように結合したかについて、メーガンの「アーキタイプ」ポッドキャストのエピソードで学びましょう.
ハリー王子が家族、特にチャールズ王とウィリアム王子との関係について望んでいると主張したある情報源を発見してください。
ワイノナ・ジャッドが、母親のナオミ・ジャッドが亡くなってから初めての感謝祭のお祝いを主催しているときに、彼女が今では家長であることをどのように認識したかを学びましょう.
Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?
The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!
ロザンヌ・バーが自分自身でいるだけでABCショーの再起動を破壊するのに、60日強かかりました。ロザンヌは、ツイッターで帽子をかぶったトロールであり、白人至上主義者に傾倒し、陰謀論者を愛する「Make AmericaGreatAmerica」であり続けています。
仕事の打ち合わせをしていると、招待される人が多ければ多いほど、全体が狂いやすくなります。そのため、すべての打ち合わせには、話題を維持するための打ち合わせ「警察」が必要です。全員が集まって話し合う間今週終了する大きなプロジェクトについては、必然的に誰かが「私たちはみんなここにいるので…」と決めるでしょう。数週間後に起こっている別のプロジェクトを立ち上げるのは良い考えです。コーヒーメーカーはどうしたの?壊れていますか?そして、ジョンの新しい子犬を見たことがありますか?会話を中断して、10分間写真を見てみましょう。
5月のリリースの特に強力なラインナップに続いて、6月はNeko Case、Nas、Lykke Li、Gang Gang Danceから待望のリターンをもたらし、Drake、Oneohtrix Point Never、Zeal&Ardourからの期待された努力とSnailMailからの有望なデビューをもたらしますとジュリアーナドーティ。ヒップホップでは、間違いなくカニエの月であり、ラッパープロデューサーは毎週少なくとも1回のリリースに関与しています。
ロシアのフィギュアスケーター、カミラ・バリエバが関与したドーピング事件が整理されているため、チームは2022年北京冬季オリンピックで獲得したメダルを待っています。
何千人ものAmazonの買い物客がMulberry Silk Pillowcaseを推奨しており、現在販売中. シルクの枕カバーにはいくつかの色があり、髪を柔らかく肌を透明に保ちます。Amazonで最大46%オフになっている間にシルクの枕カバーを購入してください
ラファイエット警察署は、「不審な男性が女性に近づいた」という複数の苦情を受けて、12 月にパデュー大学の教授の捜査を開始しました。
私たちの周りの世界と同じように、言語は常に変化しています。以前の時代では、言語の変化は数年または数十年にわたって発生していましたが、現在では数日または数時間で変化する可能性があります。
認知症を患っている 91 歳のアジア人女性が最近、47 番街のアウター サンセット地区でロメオ ロレンゾ パーハムに襲われました。伝えられるところによると、被害者はサンフランシスコの通りを歩いていたところ、容疑者に近づき、攻撃を受け、暴行を受けました。
“And a river went out of Eden to water the garden, and from thence it was parted and became into four heads” Genesis 2:10. ? The heart is located in the middle of the thoracic cavity, pointing eastward.
人々にチャンスを与えることは、人生で少し遅すぎると私は信じています。寛大に。