c++ - 释放序列的概念在实践中有用吗?
问题描述
C++ 原子语义仅保证执行释放写入(简单或读取-修改-写入)操作的最后一个线程执行的内存操作的可见性(通过先发生关系)。
考虑
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++ 概念的先发生或释放序列;我看不出这些概念映射到什么真实的硬件细节。
a
当 2 in 的值是全局可见时,真正的实现怎么能不保证线程 1 内存操作的可见性?
换句话说,发布序列定义有什么好处吗?为什么发布序列不会扩展到修改顺序中的每个后续修改?
特别考虑愚蠢的线程3:
if (a.load(memory_order_relaxed) == 2))
a.store(2,memory_order_relaxed);
silly-thread 3 能否抑制任何真实硬件上的任何可见性保证?换句话说,如果值 2 是全局可见的,如何使它再次全局可见会破坏任何排序?
我的真实多处理心智模型不正确吗?可以在某些 CPU 上部分可见的值但注意另一个值吗?
(当然,我假设宽松写入的非疯狂语义,因为回溯到时间的写入使 C++ 的语言语义绝对荒谬,不像像 Java 这样总是具有有限语义的安全语言。没有真正的实现可以有疯狂的、非因果的宽松语义。)
解决方案
我们先回答你的问题:
为什么发布序列不会扩展到修改顺序中的每个后续修改?
因为如果是这样,我们将失去一些潜在的优化。例如,考虑线程:
x = 1; // #1
a.store(1,memory_order_relaxed); // #2
在当前规则下,编译器能够重新排序#1 和#2。但是,在 release-sequence 扩展之后,编译器不允许对这两行重新排序,因为像你的线程 2 这样的另一个线程可能会引入一个以 #2 为首,以释放操作为尾的释放序列,因此可能有些读- 另一个线程中的获取操作将与#2 同步。
你举了一个具体的例子,并声称所有的实现都会产生一个特定的结果,而语言规则并不能保证这个结果。这不是问题,因为语言规则旨在处理所有情况,而不仅仅是您的具体示例。当然,可以改进语言规则,以便它可以保证您的具体示例的预期结果,但这不是一项简单的工作。至少,正如我们上面所讨论的,简单地扩展释放序列的定义并不是一个公认的解决方案。
推荐阅读
- amazon-web-services - 配置 AWS 实例以使用 IPv6
- apache-spark - 使用自定义模式时在 pyspark 数据框中设置列长度
- python - 按组检查不重复的Python
- directwrite - Direct2D - 如何绘制尽可能接近 GDI 渲染的文本
- javascript - Javascript - 标准日期和循环日期不匹配
- javascript - 如何从用户输入设置我的 javascript 变量
- android - 使用 react-native 添加依赖项时出错
- xml - 如何将 maven pom.xml 转换为 build.gradle 文件?
- yii2 - 如何设置 swiftmailer 模板的路径
- linux - 无法在 bash 中分配变量。获取命令未找到错误