Chr*_*all 3 c assembly atomic stdatomic
该atomic_compare_exchange_strong_explicit()
函数采用两个memory_order
参数success
和failure
(与 一样atomic_compare_exchange_weak_explicit()
)。取消选择 C11/C18 标准,我发现success
和 的允许值为failure
:
success = memory_order_relaxed failure = memory_order_relaxed
success = _release failure = _relaxed
success = _consume failure = _relaxed
or _consume
success = _acquire failure = _relaxed
or _acq_rel or _consume (?)
or _acquire
success = _seq_cst failure = _relaxed
or _consume (?)
or _acquire
or _seq_cst
Run Code Online (Sandbox Code Playgroud)
标准还说:
进一步地,如果比较为真,则按照成功的值影响内存,如果比较为假,则按照失败的值影响内存。这些操作是原子读-修改-写操作 (5.1.2.4)。
您的 ARM、POWER-PC 和其他“LL/SC”设备执行 Load-Link/Cmp/Store-Conditional 序列来实现atomic-cmp-exchange,其中 Load-Link 可能会也可能不会 _acquire,而Store-Conditional 则可能是 _acquire。可能是也可能不是_release。
所以我可以理解:成功= _acq_rel和失败= _acquire。
我无法理解的是(除其他外): success = _acq_rel和 failure = _relaxed。当然,为了实现_acq_rel,加载链接必须是_acquire吗?如果 cmp 失败,那么降级到肯定为时已晚_relaxed肯定为时已晚吗?
组合的预期含义是什么success
和failure
(当它们不相等时)的预期含义是什么?
[我总是有可能被标准迷惑了,事实上,内存顺序可能只是内存顺序failure
的读取一半。]success
在某些 ISA 上的 asm 中实现获取加载的一种方法是简单加载,后跟栅栏,例如在 ARMv8 引入ldar
/之前的 ISA 上,如 PowerPC 或 ARM ldaxr
。 如果失败排序不包括获取,则可以跳过后面的栅栏。
对于没有真正的 ISA 的情况,LL/SC CAS_weak 在伪 asm 中可能看起来像这样:
ll r0, mem
cmp r0, r1
jne .fail # early-out compare fail
sc r2, mem # let's pretend this sets CF condition code on SC failure
jc .fail # jump if SC failed
lwsync # LoadLoad, StoreStore, and LoadStore but not StoreLoad
... CAS success path
.fail: # we jump here without having executed any barriers
... CAS failure path
Run Code Online (Sandbox Code Playgroud)
mem.compare_exchange_weak(r1, r2, mo_acquire, mo_relaxed);
(我认为)这可能是某些类型机器上的有效实现。
这只是一个获取操作,因此整个 RMW 可以与早期操作一起重新排序(LL/SC 的性质使它们在全局顺序中粘在一起)。手术前没有障碍,手术后才有障碍。
lwsync
是一个PowerPC屏障指令,它阻止除StoreLoad之外的所有重新排序(即不刷新存储缓冲区)。 https://preshing.com/20120913/acquire-and-release-semantics/和https://preshing.com/20120930/weak-vs-strong-memory-models/
为了在大多数 ISA 上实现,我们还在LL/SC之前CAS(..., acq_rel, relaxed)
运行一个屏障(将其与早期操作分开,创建发布部分)。即使在失败路径上也会执行,但不会创建获取语义。它不会将负载与后续操作分开。
AFAIK,如果比较失败,您不想在 LL 和 SC之间放置栅栏,以便可以跳过。这将延长事务并使其更有可能因其他线程的活动而失败。
与往常一样,当在真实汇编之上实现 C++ 内存模型时,您会做一些与必要一样强大的事情,但考虑到底层 ISA 提供的原语的限制,不会更强大。 大多数 ISA 不会使 CAS(acq_rel,relaxed) 故障路径实际上像普通的宽松负载一样便宜,要么因为这是不可能的,要么因为它会损害正常情况下的性能。但在某些方面,它仍然比故障方需要获取语义的成本要低。
一些 ISA(例如 ARM)显然只有完整的屏障 ( dsb ish
),因此即使 acq_rel 最终也会耗尽存储缓冲区。(因此 ARMv8 引入获取加载和顺序释放存储非常好,因为它完全匹配 C++ seq-cst 语义,并且可能比屏障便宜得多。)