考虑 的定义compare_and_exchange_strong_explicit
:
_Bool atomic_compare_exchange_strong_explicit( volatile A* obj,
C* expected, C desired,
memory_order succ,
memory_order fail );
我对 的用例感到困惑memory_order fail
。考虑到 x86,很明显可能lock cmpxchg
会失败,但也明确定义了(强调我的)
读取或写入不能用 I/O 指令、锁定 指令或序列化指令重新排序
这使得这种memory_order fail
无关紧要,因为lock
在任何情况下都能保证顺序一致性。
例子:
#include <stdatomic.h>
void fail_seqcst(volatile int *p, int *expected, int *desirable){
atomic_compare_exchange_strong_explicit(p, expected, desirable, memory_order_release, memory_order_seq_cst);
}
void fail_relaxed(volatile int *p, int *expected, int *desirable){
atomic_compare_exchange_strong_explicit(p, expected, desirable, memory_order_release, memory_order_relaxed);
}
编译为:
fail_relaxed:
mov ecx, edx
mov eax, DWORD PTR [rsi]
lock
cmpxchg DWORD PTR [rdi], ecx
mov DWORD PTR [rsi], eax
sete al
movzx eax, al
ret
fail_seqcst:
mov ecx, edx
mov eax, DWORD PTR [rsi]
lock
cmpxchg DWORD PTR [rdi], ecx
mov DWORD PTR [rsi], eax
sete al
movzx eax, al
ret
memory_order_relaxed
编译器是否可以进行任何优化来区分和memory_order_seq_cst
在这种情况下的代码x86
?或者也许有一种架构可以使这种差异变得显着?
失败顺序仅与非 x86 的 asm 相关,其中通常需要单独的屏障指令,只能将其放置在成功情况的执行路径中。
它可以允许编译时重新排序,尽管当前的 x86 编译器可能不这样做。在 CAS 尝试分支的代码中,如果仅在 CAS 中使用其结果,则 CAS 之前的宽松加载(或非原子读取)可能会被移至故障路径中。但只有当 CAS 失败命令不是释放操作,只有获取或放松时。(
.load(acquire)
或者更强的不能以这种方式重新排序,因为它也会发生在成功路径之前,并且之前的 CAS 本身可能会排除某些值。)CAS_weak 和 CAS_strong 的所有可能选择都会
memory_order
生成与 相同的 x86 asmatomic_compare_exchanges_strong(ptr, &expected, desired, seq_cst)
,即lock cmpxchg
。无论成功还是失败,
lock cmpxchg
都是一个完整的屏障atomic_thread_fence(seq_cst)
(与弱排序 ISA(例如 AArch64)上的 seq_cst 加载、存储或 RMW 不同,其中 SC RMW不会阻止后来的宽松存储与较早的宽松存储重新排序,例如后一个可以通过 RMW 的存储侧重新排序,而前一个可以通过 RMW 的负载侧重新排序)。操作与 ISO C++ 和某些真实 ISA 中的栅栏不同。因此它比 ISO C++ 要求的 seq_cst 操作强得多,就像完整的栅栏一样强。x86
lock cmpxchg
甚至会在失败时弄脏缓存行(再次与LL/SC实现不同,如果比较为假,则在存储尝试上进行分支。)尽管 LL 可能会尝试使缓存行进入独占状态,因此它可能即使它避免了以后的写回,对于争用来说也好不了多少。我不确定这是如何运作的。就 CPU 而言,
lock cmpxchg
只是不同的 ALU 和标志设置与始终成功的操作(如lock add
或 )xchg
。 这些都是完整的障碍,事实上,lock or dword [rsp], 0
大多数编译器都是通过虚拟或类似的方式实现的,atomic_thread_fence(seq_cst)
因为它比mfence
.英特尔甚至将其记录为始终存储,因此处理器永远不会在不产生锁定写入的情况下产生锁定读取,尽管同一句话谈到“处理器总线的接口”。普通(可缓存)内存区域上的对齐
lock
ed 操作仅获取缓存锁(延迟 MESI 响应),而不是 CPU 内核外部总线上实际可见的任何内容。因此,这可能不再是真正有意义的文档,至少是自 1995 年 P6(Pentium Pro)以来的一个好像的东西。