释放顺序的概念在实践中有用吗?

时间:2019-05-07 23:40:36

标签: c++ multithreading multiprocessing memory-barriers memory-model

C / C ++原子语义仅保证通过执行释放“简单”写(无读-修改-写)操作的最后一个线程执行的内存操作(通过事前发生关系)。 (在写操作和RMW操作之间存在强烈的语义区分。)

考虑

int x, y;
atomic<int> a;

线程1:

x = 1;
a.store(1,memory_order_release);

线程2:

y = 2;
if (a.load(memory_order_relaxed) == 1))
  a.store(2,memory_order_release);

然后观察a == 2意味着线程2操作(y == 2)的可见性,而不是线程1的可见性(一个人甚至无法读取x)。

据我所知,多线程的实际实现使用围栏(有时是发布存储)的概念,但没有发生在高级C / C ++概念上的事前或发布顺序;我看不到这些概念映射到哪些真正的硬件细节。

a中的值2全局可见时,真正的实现如何不能保证线程1内存操作的可见性?

换句话说,发布顺序定义有什么好处吗?为什么释放顺序不按修改顺序扩展到每个后续修改?

请特别考虑傻线程3:

if (a.load(memory_order_relaxed) == 2))
  a.store(2,memory_order_relaxed);

傻线程3可以抑制任何真实硬件上的可见性保证吗?换句话说,如果值2在全局范围内可见,那么如何使其再次在全局范围内可见会破坏任何顺序?

我真正的多处理思维模型是否正确?在某些CPU上可以部分显示一个值,但注意另一个吗?

(当然,我假设轻松写的是非疯狂的语义,因为时光倒流的写使得C / C ++的语言语义绝对是毫无意义的,不像Java这样的安全语言总是具有受限的语义。没有真正的实现可以使疯狂,非因果的宽松语义。)

1 个答案:

答案 0 :(得分:1)

让我们首先回答您的问题:

  

为什么发行顺序不按修改顺序扩展到每个后续修改?

因为这样,我们将失去一些潜在的优化。例如,考虑以下线程:

x = 1;                            // #1
a.store(1,memory_order_relaxed);  // #2

在当前规则下,编译器可以对#1和#2重新排序。但是,在释放序列的扩展之后,不允许编译器对这两行进行重新排序,因为另一个线程(例如线程2)可能会引入以#2开头并以释放操作为尾的释放序列,因此有可能进行一些读取-另一个线程中的-acquire操作将与#2同步。


您举了一个具体的示例,并声称所有实现将产生特定的结果,而语言规则并不保证该结果。这不是问题,因为语言规则旨在处理所有情况,而不仅仅是您的特定示例。当然,可以改进语言规则,以保证特定示例的预期结果,但这不是一件容易的事。至少如上所述,仅仅扩展发布序列的定义是不可接受的解决方案。