load-acquire 应该立即看到 store-release 吗?
Posted
技术标签:
【中文标题】load-acquire 应该立即看到 store-release 吗?【英文标题】:Should load-acquire see store-release immediately? 【发布时间】:2015-06-11 11:11:53 【问题描述】:假设我们有一个简单的变量(std::atomic<int> var
)和两个线程T1
和T2
,我们有以下T1
的代码:
...
var.store(2, mem_order);
...
对于T2
...
var.load(mem_order)
...
我们还假设T2
(load) 比T1
(store) 执行123ns时间(根据C++ 标准的修改顺序晚)。
我对这种情况的理解如下(针对不同的内存顺序):
memory_order_seq_cst
- T2
加载必须加载 2
。因此,它必须有效地加载最新值(就像 RMW 操作一样)
memory_order_acquire
/memory_order_release
/memory_order_relaxed
- T2
没有义务加载 2
但可以加载任何较旧的值,唯一的限制是:该值不应早于该线程加载的最新值。因此,例如 var.load
返回 0
。
我的理解是否正确?
更新1:
如果我的推理有误,请提供 C++ 标准中的文本来证明它。不仅仅是一些架构如何工作的理论推理。
【问题讨论】:
【参考方案1】:我的理解是否正确?
没有。你误解了记忆顺序。
假设
T2
(load) 比T1
(store) 晚执行123ns...
在这种情况下,T2 将看到 T1 对任何类型的内存命令所做的事情(此外,此属性适用于任何内存区域的读/写,例如,参见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf, 1.10, p.15)。您的短语中的关键词是稍后:这意味着其他人强制这些操作的顺序。
内存订单用于其他场景:
让一些操作OP1
在存储操作之前进入线程T1
,OP2
在它之后,OP3
在加载操作之前进入线程T2
,OP4
在它之后。
//T1: //T2:
OP1 OP3
var.store(2, mem_order) var.load(mem_order)
OP2 OP4
假设线程可以观察到var.store()
和var.load()
之间的某种顺序。 其他操作的跨线程顺序可以保证什么?
-
如果
var.store
使用memory_order_release
,var.load
使用memory_order_acquire
和var.store
排序之前var.load
(即加载返回2),那么@987654340的效果@ 排序在之前 OP4
。
例如,如果 OP1
写入某个变量 var1,OP4
读取该变量,那么可以确定 OP4
将读取 OP1
之前写入的内容。这是最常用的情况。
-
如果
var.store
和var.load
都使用memory_order_seq_cst
并且var.store
排序在 var.load
之后(即加载返回0,这是存储之前的变量值),然后OP2
的效果被排序在 OP3
之后。
一些棘手的同步方案需要这种内存顺序。
-
如果
var.store
或var.load
使用memory_order_relaxed
,那么对于var.store
和var.load
的任意顺序,都可以保证无顺序的跨线程操作。
此内存顺序用于其他人确保操作顺序的情况。例如,如果线程T2
创建在T1
中的var.store
之后,那么OP3
和OP4
将在OP1
之后排序。
更新:123 ns later
暗示*someone else* force ordering
,因为计算机的处理器没有关于世界时的概念,并且没有操作在执行时精确时刻。要测量两次操作之间的时间,您应该:
-
观察在一些cpu上完成第一个操作和开始计时操作之间的顺序。
观察开始和结束计时操作之间的顺序。
观察完成计时操作和开始第二个操作之间的顺序。
这些步骤可传递地在第一个操作和第二个操作之间进行排序。
【讨论】:
我知道内存顺序是如何使用的。问题是不同的。 “您的短语中的关键词是稍后:这意味着其他人强制执行这些操作。”后来在我的短语中仅表示已经过去了一段时间。我没有在标准中找到任何证据表明load
保证它将以除默认值(seq.一致)之外的任何内存顺序加载最新值。你能指出标准保证它的条款吗?
@ixSci:查看关于later imply order
的更新答案。至于关于加载最新值的保证,这是我们对任何编译器/硬件的期望。可能,它可以在处理器的规格中找到。请注意,此保证适用于任何内存读/写,不仅适用于原子类型。
Note, that this garantee is true for any memory read/write, not only for atomic types
- 写缓冲区、失效队列和其他阻碍缓存一致性的实体呢?不,你的说法不正确。抱歉,我问的是 C++ 标准的严格证明,而不是一些理论推理。
@ixSci:C++ 标准没有定义像123ns later
这样的术语。它使用术语program order
(在单线程内)、happens-before
操作之间的关系、atomic
对象的modification order
等进行操作。参见,例如,open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf,1.10 “多线程执行和数据竞争”,p.p. 5,8
然而标准明确规定 RMW 操作在调用时始终使用最新值。 seq 也是如此。一致(如果我理解正确的话),但我没有找到任何其他内存订单的提及。 123ns later
的概念只是为了确保操作真正发生在现实生活之后,而不是任何软件模型。【参考方案2】:
没有找到任何论据来证明我的理解是错误的,我认为它是正确的,我的证明如下:
memory_order_seq_cst - T2 load 必须加载 2。
这是正确的,因为所有使用memory_order_seq_cst
的操作都应该在所有内存操作的原子变量上形成单一的总顺序。
标准摘录:
[29.9/3] 在所有 memory_order_seq_cst 上应该有一个总订单 S 操作,符合“发生在之前”的顺序和 所有受影响位置的修改订单,这样每个 memory_order_seq_cst 操作 B 从原子加载值 对象 M 观察到以下值之一 <...>
我的问题的下一点:
memory_order_acquire/memory_order_release/memory_order_relaxed - T2 是 不必加载 2 但可以加载任何旧值 <...>
我没有找到任何证据表明在修改顺序中稍后执行的加载应该看到最新的值。对于与memory_order_seq_cst
不同的任何内存顺序,我发现的存储/加载操作的唯一要点是:
[29.3/12] 实现应该使 atomic 存储对 atomic 可见 在合理的时间内加载。
和
[1.10/28] 实现应确保原子或同步操作分配的最后一个值(按修改顺序) 将在有限的时间内对所有其他线程可见。
所以我们唯一的保证是写入的变量将在一段时间内可见 - 这是相当合理的保证,但它并不意味着之前存储的立即可见性。它证明了我的第二点。
鉴于我最初的理解是正确的。
【讨论】:
对于第二种情况,不能保证写入会变得可见。标准中的“应该”表示规范性的鼓励,对实施没有约束力。 @T.C.,也许吧,但是关于我没有看到的可见性的其他声明。并且如果使用原子编写的程序应该是有意义的,这可能被视为保证。 @T.C.如果你去那里,允许任何 MT 程序没有 UB 是“非绑定”的。如果你拒绝依赖“应该”,你就不能编程,句号。【参考方案3】:123 nS 之后不会强制执行命令 T2 查看 T1 的结果。那是因为如果运行T2的物理程序计数器(晶体管等)距离运行T1的物理程序计数器(大型多核超级计算机等)超过40米,那么光速将不允许T1写入状态信息传播那么远(还)。如果用于加载/存储的物理内存与两个线程处理器相距一定距离,则会产生类似的效果。
【讨论】:
以上是关于load-acquire 应该立即看到 store-release 吗?的主要内容,如果未能解决你的问题,请参考以下文章
从 google play-store 安装后应用程序立即关闭 - Android