`atomic_compare_exchange_strong_explicit()` -- 当不相等时,`success` 和 `failure` 参数的各种组合会做什么?

Chr*_*all 3 c assembly atomic stdatomic

atomic_compare_exchange_strong_explicit()函数采用两个memory_order参数successfailure(与 一样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肯定为时已晚吗?

组合的预期含义是什么successfailure(当它们不相等时)的预期含义是什么?

[我总是有可能被标准迷惑了,事实上,内存顺序可能只是内存顺序failure的读取一半。]success

Pet*_*des 5

在某些 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 语义,并且可能比屏障便宜得多。)