(我不久前开始写这篇文章,但陷入了停滞;我不确定它是否能构成完整的答案,但我认为其中一些内容可能值得发布。我认为@LWimsey 的评论可以更好地抓住问题的核心比我写的答案。)
是的,很安全。
请记住,as-if 规则的应用方式是,真实机器上的执行必须始终生成与 C++ 抽象机上可能的执行相匹配的结果。对于优化来说,在目标上进行 C++ 抽象机允许不可能执行的某些执行是合法的。例如,即使是针对 x86 进行编译,也无法进行所有 IRIW 重新排序,无论编译器是否喜欢。 (见下文;某些 PowerPC 硬件是唯一可以在实践中做到这一点的主流硬件。)
我认为专门针对 RMW 的措辞的原因是它将负载与 ISO C++ 要求每个原子对象单独存在的“修改顺序”联系起来。 (或许。)
Remember that the way C++ formally defines its ordering model is in terms of synchronizes-with, and existence of a modification order for each object (that all threads can agree on). Not like hardware where there is a notion of coherent caches1 creating a single coherent view of memory that each core accesses. The existence of coherent shared memory (typically using MESI to maintain coherence at all times) makes a bunch of things implicit, like the impossibility of reading "stale" values. (Although HW memory models do typically document it explicitly like C++ does).
因此,转变是安全的。
ISO C++ 在另一节的注释中确实提到了一致性的概念:http://eel.is/c++draft/intro.races#14
由评估 B 确定的原子对象 M 的值应是由修改 M 的某些副作用 A 存储的值,其中 B 不会在 A 之前发生。
[注14:此类副作用的集合还受到此处描述的其余规则的限制,特别是受到下面的一致性要求的限制。——尾注]
...
[注19:上述四项一致性要求有效
禁止编译器将原子操作重新排序到单个对象,
即使这两个操作都是松弛负载。这有效地使得
大多数可用于 C++ 的硬件提供缓存一致性保证
原子操作。——尾注]
[注 20:原子负载观察到的值取决于
“发生在”关系之前,这取决于观察到的值
大量的原子。预期的阅读是必须存在一个
原子负载与他们观察到的修改的关联,
连同适当选择的修改顺序和“发生
如上所述导出的“之前”关系,满足所得结果
此处施加的限制。 ——尾注]
因此 ISO C++ 本身指出缓存一致性提供了一些排序,并且 x86 具有一致性缓存。 (我并没有完整地论证这是enough下单了,抱歉。 LWimsey 关于修改命令中最新的含义的评论是相关的。)
(在许多 ISA(但不是全部)上,内存模型也排除了IRIW 重新排序当您存储两个独立的对象时。 (例如,在 PowerPC 上,2 个读取器线程可能对 2 个存储到 2 个存储的顺序不一致separate对象)。很少有实现可以创建这样的重新排序:如果共享缓存是only数据在核心之间传输的方式(就像在大多数 CPU 上一样),这会创建存储顺序。)
此转换是否有可能导致执行 RMW 操作的线程改为加载早于最新值的值?
特别是在 x86 上,这很容易推理。 x86 有一个强有序记忆模型(TSO = 总存储顺序 = 程序顺序 + 具有存储转发的存储缓冲区)。
脚注 1:所有内核std::thread
可以运行具有一致的缓存。对于跨所有 ISA 的所有实际 C++ 实现都是如此,而不仅仅是 x86-64。有一些异构板具有单独的 CPU 共享内存而没有缓存一致性,但同一进程的普通 C++ 线程不会在这些不同的内核上运行。看这个答案了解更多详情。