内存顺序松散和原子RMW操作

6
C++标准规定对原子操作的RMW(读-修改-写)操作将作用于原子变量的最新值。因此,即使使用memory_order_relaxed与这些操作一起在多个线程中执行,也不会影响RMW操作。
我认为只有在RMW操作时存在某种内存屏障或者内存栅的情况下,这种行为才是可能的,即使所指定的内存顺序是"relaxed"。 如果我的理解有误,请纠正我并解释一下如果没有使用这样的内存屏障,这些操作如何作用于最新值。如果我的理解是正确的,那么我可以进一步假设,在弱排序架构(如ARM或Alpha)上进行的RMW操作使用Acquire-Release或Seq-CST内存顺序不应该对性能产生额外影响。谢谢您的帮助。

实现肯定取决于平台和操作系统。 - Ed Heal
2
无论内存排序如何,RMW(读改写)操作都保证在修改顺序中操作最新的值。比“relaxed”更强的内存排序定义了与 RMW 本身相关的线程间其他操作的排序方式。 - LWimsey
1
@Madhusudhan 这是一个常见的错误,但它不会。屏障(或栅栏)保留程序顺序并使其对其他CPU可见。 - LWimsey
1
@EdHeal,由于x86对所有RMW(lock-prefix)指令都有硬内存屏障,因此您无法在典型的PC上测量它。因此,顺序一致性和获取-释放之间没有区别。另一方面,松散语义将允许编译器在RMW本身上移动其他内容。 - Mysticial
1
你可以说它刷新了sb,或者等待直到sb被刷新,但效果是相同的。这是英特尔手册关于mfence的说明:它保证在栅栏之前指定的所有加载和存储在栅栏之后执行任何加载或存储之前都是全局可观察的。 - LWimsey
显示剩余12条评论
1个回答

5
这是有关原子内存顺序的一个不幸常见的误解。注意,它们并不(完全)适用于实际的原子操作,而主要适用于其周围的其他操作。
例如:
//accessible from anywhere
std::atomic<bool> flag;
int value = 0;

//code in thread 1:
value = 1;
flag.store(true, <order_write>);

//code in thread 2:
bool true_val = true;
while(!flag.compare_exchange_weak(true_val, false, <order_read>);
int my_val = value;

那这个是在做什么?线程2在等待线程1的信号,指示value已更新,然后线程2读取value<order_write><order_read>不会管理特定原子变量的行为。它管理在该原子操作之前/之后设置的其他值的行为。
为了使此代码正常工作,<order_write>必须使用至少与memory_order_release一样强的内存序。而<order_read>必须使用至少与memory_order_acquire一样强的内存序。
这些内存序会影响如何传输value(或更具体地说,原子写入之前设置的内容)。

是否需要像内存屏障这样的东西才能"操作最新值"的条件呢?

大多数体系结构不太可能使用全局内存屏障实现实际的原子修改。只有非松散内存序才能做到这一点:它们对写入者和读取者施加通用内存屏障。
如果原子操作需要内存屏障才能正常工作,它们通常会使用本地内存屏障。也就是说,一个特定于原子变量地址的屏障。
因此,可以合理地假设,与松散内存序相比,非松散内存序会对性能产生更大的影响。当然这并不保证,但这是一个相当好的一阶近似值。
原子实现是否可能在任何原子操作上使用全局内存屏障? 是的。但如果一个实现为基本原子类型采用这种方式,那么体系结构可能没有其他选择。因此,如果您的算法需要原子操作,您确实没有其他选择。

我已经了解了你回答的第一部分。也许在我的问题中,我应该澄清一下我的意思是“使用屏障会使松散排序的行为类似于acq-rel”。然而,我不熟悉本地内存屏障。我会进一步阅读并在之后再来查看这里。 - MS Srikkanth
@Madhusudhan 在C++中,不存在本地或全局内存屏障这样的东西。 - LWimsey
@LWimsey:如果你想要讲的更技术一些,其实在C++中并不存在任何类型的“内存屏障”。我这里使用的术语只是为了区分“CPU/编译器必须执行的使一个特定的内存地址可用的操作”和“CPU/编译器必须执行的使所有 内存地址可用的操作”。实际上,原子RMW是前者;而内存排序操作则是后者。 - Nicol Bolas
C++标准将它们称为栅栏。 - LWimsey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接