有没有可能具有memory_order_relaxed的存储永远不会到达其他线程?

Is it possible that a store with memory_order_relaxed never reaches other threads?

提问人:Carlo Wood 提问时间:5/3/2017 最后编辑:JonasCarlo Wood 更新时间:12/13/2019 访问量:1211

问:

假设我有一个线程 A,它使用 写入 。如果没有任何其他同步方法,其他线程需要多长时间才能看到这一点,使用 ?考虑到标准给出的 C/C++ 内存模型的当前定义,写入的值是否有可能完全保持线程本地?atomic_int x = 0;x.store(1, std::memory_order_relaxed);x.load(std::memory_order_relaxed);x

我手头的实际情况是线程 B 经常读取 an 以检查它是否必须退出;显然,我不介意在线程 B 看到atomic_bool设置之前调用 join(),也不介意线程 B 在调用 join() 之前已经看到更改并退出执行。但我想知道:在两边使用,是否可以“永远”调用 join() 并阻止,因为更改永远不会传播到线程 B?atomic_boolmemory_order_relaxed

编辑

我联系了Mark Batty(数学验证并随后修复C++内存模型需求的大脑)。本来是关于别的东西(原来是cppmem和他的论文中一个已知的bug;所以幸运的是我没有完全自欺欺人,并借此机会也问了他这个问题;他的回答是:

问:从理论上讲,这样的存储 [没有(任何后续)释放操作的 memory_order_relaxed] 永远不会到达另一个线程吗?
Mark:从理论上讲,是的,但我认为没有观察到这一点。
问:换句话说,休闲商店没有意义 除非您将它们与某些发布操作(和 在另一个线程上获取),假设您希望另一个线程 看到了吗?
Mark:几乎所有的用例都使用发布和获取,是的。

C++ C++11 内存屏障 松弛原子

评论

1赞 Carlo Wood 5/4/2017
编辑更像是一个答案;但由于这不是我的答案,我决定将其添加为编辑而不是答案。我希望有些人会觉得这位专家的意见有用。
0赞 curiousguy 12/14/2019
这个问题是专门针对 C++11 的吗?
0赞 Carlo Wood 12/19/2019
它是关于 C++ 11 中引入的 C++ 内存模型。在实践中,任何对内存的写入都将在几微秒内对所有其他线程可见,甚至可能更快,即使您不包括将缓存刷新到内存的汇编指令。最值得注意的是,在英特尔上,宽松存储和发布存储之间根本没有区别(关于汇编和硬件 - 编译器重新排序不包括在此备注中)。
0赞 curiousguy 12/19/2019
哪些实现会生成“将缓存刷新到内存”的指令?在哪些情况下?
0赞 Carlo Wood 12/21/2019
据我所知,什么都没有。这没有意义。你所能做的就是添加一个内存围栏(或任何其他“memory_order_release”操作),这至少可以确保在后续写入内存之前,所有内容都会被刷新到内存中。

答:

9赞 Igor Tandetnik 5/3/2017 #1

这就是关于此事的所有标准,我相信:

[介绍.多线程]/25实现应确保原子或同步操作分配的最后一个值(按修改顺序)在有限的时间内对所有其他线程可见。

评论

1赞 Peter Cordes 12/13/2019
在实践中,std::thread 启动线程的硬件具有连贯的缓存,不需要软件刷新,因此可见性时间 = 存储缓冲区提交存储的时间。发生这种情况时,其他内核将看到存储线程中的 MESI 失效/RFO,然后必须自己执行共享请求以获取新值的副本。请参阅何时将易失性与多线程一起使用?,了解有关 ISO C++ 被编写为在缓存一致性硬件上运行这一事实的更多详细信息,并且没有缓存一致性硬件运行几乎不合理。
0赞 Peter Cordes 11/24/2022
我的回答是,如果您用“memory_order_relaxed”检查,为什么使用“memory_order_seq_cst”设置停止标志? 还引用了 33.5.4 顺序和一致性 [atomics.order] - 11.实现应在合理的时间内使原子存储对原子负载可见。因此,这是两个应该的要求,一个是“有限期”,另一个是“合理时间”。该标准基本上将其作为实施质量因素;真正的硬件为我们提供了低延迟。
-2赞 LWimsey 5/3/2017 #2

这是标准在 29.3.12 中所说的:

实现应在合理的时间内使原子存储对原子负载可见。

不能保证 a 将在另一个线程中可见,没有保证的时序,并且与内存顺序没有正式的关系。store

当然,在每个常规架构上,a 都会变得可见,但在不支持缓存一致性的罕见平台上,它可能永远不会对 .
在这种情况下,您必须执行原子读取-修改-写入操作才能获取修改顺序中的最新值。
storeload

评论

0赞 Carlo Wood 5/4/2017
你确定这是关于std::memory_order_relaxed(也是)的吗?我可以想象,即使只是发布/获取存储/读取,这句话也是必要的,因为在这种情况下,我们知道订购,但仍然没有提到时间;即,将两台单核 PC 并排放置,如果不是因为这个评论;),它们将遵守标准。
1赞 LWimsey 5/4/2017
@CarloWood 当然可以。.一个常见的误解是,内存排序与变量本身的可见性有关;它不是..(如果松弛原子从未被其他核心看到,它们有什么用? 语义指定与操作相关的其他内存操作的顺序(以及可见性)。如果一个变量在另一个线程中不可见,则它所命令的内存操作也不会可见。atomicacquire/releaseatomicatomic
0赞 curiousguy 12/12/2018
"但在罕见的平台上“你能举个例子吗?
0赞 LWimsey 12/12/2018
@curiousguy我不能举个例子,但缓存一致性是一个可选功能。您可能会在嵌入式世界中发现非缓存一致性体系结构。
2赞 xskxzr 2/16/2019
这条规则难道不能保证商店在另一个线程中可见吗,因为合理的时间肯定排除了无限的时间?
0赞 curiousguy 12/13/2019 #3

在实践中

如果没有任何其他同步方法,需要多长时间 在其他线程可以看到这一点之前,使用 ?x.load(std::memory_order_relaxed);

没时间了。这是一个正常的写入,它进入存储缓冲区,因此它将在不到眨眼的时间内在 L1d 缓存中可用。但这只是在运行汇编指令

编译器可以对指令进行重新排序,但是没有一个合理的编译器会在任意长的循环中对原子操作进行重新排序

理论上

问:从理论上讲,这样的商店[没有(任何后续)发布操作]永远不会到达另一个 线?memory_order_relaxed

Mark:理论上,是的,

你应该问他,如果重新添加“以下发布围栏”会发生什么。或者使用原子存储释放操作。

为什么不重新订购这些并延迟时间呢?(如此之长,以至于在实践中似乎是永恒的)

写入 x 的值是否有可能完全保持线程本地 鉴于 C/C++ 内存模型的当前定义, 标准给?

如果一个虚构的、特别反常的实现想要延迟原子操作的可见性,为什么它只对宽松的操作这样做呢?它完全可以用于所有原子操作。

或者永远不要运行一些线程。

或者将一些线程运行得太慢,以至于您会认为它们没有运行。