`atomic_compare_exchange_strong_explicit()` -- 当参数 success 和 failure 不相等时,各种组合有什么作用?

`atomic_compare_exchange_strong_explicit()` -- what do the various combinations of `success` and `failure` parameter do, when not equal?

atomic_compare_exchange_strong_explicit() 函数有两个 memory_order 参数,successfailureatomic_compare_exchange_weak_explicit() 也是如此)。取消选择 C11/C18 标准,我发现 successfailure 的允许值为:

    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

标准还说:

Further, if the comparison is true, memory is affected according to the value of success, and if the comparison is false, memory is affected according to the value of failure. These operations are atomic read-modify-write operations (5.1.2.4).

您的 ARM、POWER-PC 和其他 "LL/SC" 设备执行 Load-Link/Cmp/Store-Conditional 序列来实现 atomic-cmp-exchange,其中 Load-Link 可能是也可能不是 _acquire 并且存储条件可能是也可能不是 _release

所以我可以理解:success = _acq_rel and failure = _acquire.

我无法理解的是(除其他外):成功 = _acq_rel 和失败 = _relaxed。当然,为了实现 _acq_rel,Load-Link 必须是 _acquire 吗?如果 cmp 失败,那么降级到 _relaxed 肯定为时已晚?

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

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

在某些 ISA 上的 asm 中实现获取负载的一种方法是普通负载 后跟 栅栏,例如在 ARMv8 引入 ldar / ldaxr 之前,在像 PowerPC 或 ARM 这样的 ISA 上。 如果失败排序不包含获取,则可以跳过后面的围栏。

一个 LL/SC CAS_weak 在没有真正的 ISA 的伪 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

这(我认为)可能是 mem.compare_exchange_weak(r1, r2, mo_acquire, mo_relaxed); 在某些机器上的有效实现。

这只是一个获取操作,因此整个 RMW 可以使用较早的操作重新排序(LL/SC 的性质使它们在全局顺序中粘在一起)。术前无门槛,术前无门槛。

lwsync 是一个 PowerPC 屏障指令,它阻止除 StoreLoad 之外的所有重新排序(即不刷新存储缓冲区)。 https://preshing.com/20120913/acquire-and-release-semantics/ and https://preshing.com/20120930/weak-vs-strong-memory-models/


为了在大多数 ISA 上实现 CAS(..., acq_rel, relaxed),我们还会 运行 在 LL/SC 之前 一个屏障(将它与之前的操作,创建发布部分)。这甚至会在失败路径上执行,但 不会 创建获取语义。它不会将负载与以后的操作分开。

AFAIK 如果比较失败,您不想在 LL 和 SC 之间设置围栏以便跳过。这将延长事务并使其有更多机会因其他线程的 activity 而失败。

与往常一样,在真正的 asm 之上实现 C++ 内存模型时,您会做一些必要的事情,但不会更强,因为底层 ISA 提供的原语有限制。 大多数 ISA 不会使 CAS(acq_rel,松弛)故障路径实际上像普通松弛负载一样便宜,要么是因为这是不可能的,要么是因为它会损害正常情况下的性能。但是在某些情况下,它仍然可以 更少 比失败方需要获取语义的代价要小。

一些 ISA(如 ARM)显然只有完整的屏障(dsb ish),所以即使 acq_rel 最终也会耗尽存储缓冲区。 (因此 ARMv8 引入获取加载和顺序释放存储 非常 很好,因为它完美匹配 C++ seq-cst 语义并且可能比屏障便宜得多。)