atomic_flag
如何实现?在我看来,在x86-64上它无论如何都等于atomic_bool
,但这只是一个猜测。 x86-64实现与arm或x86有什么不同吗?
是的,在atomic<bool>
和atomic<int>
也是无锁的普通CPU上,使用相同的指令非常类似于atomic<bool>
。 (x86和x86-64具有相同的原子操作集。)
[您可能会认为它总是使用x86 lock bts
或lock btr
来设置/重置(清除)单个位,但是做其他事情会更有效(特别是对于返回布尔值而不是布尔值的函数)分支)。对象是整个字节,因此您可以存储或交换整个字节。 (并且,如果ABI保证该值始终为0
或1
,则在将结果作为bool
返回之前,不必对它进行布尔化处理))>
GCC和clang将test_and_set
编译为字节交换,并清除为0
的字节存储。
atomic_flag
test_and_set
,我们得到(几乎)相同的asm为f.exchange(true);
#include <atomic> bool TAS(std::atomic_flag &f) { return f.test_and_set(); } bool TAS_bool(std::atomic<bool> &f) { return f.exchange(true); } void clear(std::atomic_flag &f) { //f = 0; // deleted f.clear(); } void clear_relaxed(std::atomic_flag &f) { f.clear(std::memory_order_relaxed); } void bool_clear(std::atomic<bool> &f) { f = false; // deleted }
[带有CCC和clang的x86-64,以及ARMv7和AArch64的On Godbolt。
## GCC9.2 -O3 for x86-64 TAS(std::atomic_flag&): mov eax, 1 xchg al, BYTE PTR [rdi] ret TAS_bool(std::atomic<bool>&): mov eax, 1 xchg al, BYTE PTR [rdi] test al, al setne al # missed optimization, doesn't need to booleanize to 0/1 ret clear(std::atomic_flag&): mov BYTE PTR [rdi], 0 mfence # memory fence to drain store buffer before future loads ret clear_relaxed(std::atomic_flag&): mov BYTE PTR [rdi], 0 # x86 stores are already mo_release, no barrier ret bool_clear(std::atomic<bool>&): mov BYTE PTR [rdi], 0 mfence ret
注意
xchg
也是在x86-64上进行seq_cst
存储的有效方法,通常比gcc使用的mov
+mfence
更有效。 Clang对所有这些都使用xchg
(休闲商店除外)。
有趣的是,在atomic_flag.test_and_set()
中的xchg之后,clang将布尔值重新布尔化为0/1,但是GCC在atomic<bool>
之后将其重新布尔化为0/1。 clang在TAS_bool中执行了一个奇怪的and al,1
,会将2
之类的值视为false。似乎毫无意义。 ABI保证内存中的bool
始终存储为0
或1
字节。
对于ARM,我们有ldrexb
/ strexb
交换重试循环,或者对于纯存储只有strb
+ dmb ish
。或者,AArch64可以将stlrb wzr, [x0]
用作clear
或使用assign-false来进行(零寄存器的)顺序释放存储,而无需设置障碍。
在大多数/健全的体系结构上,中断可能在执行硬件指令之后或之前发生。不在执行过程中。因此,要么指令“发生”(即带有“副作用”),要么不会发生。