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)和两个线程T1T2,我们有以下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在存储操作之前进入线程T1OP2在它之后,OP3在加载操作之前进入线程T2OP4在它之后。

//T1:                         //T2:
OP1                           OP3
var.store(2, mem_order)       var.load(mem_order)
OP2                           OP4

假设线程可以观察到var.store()var.load() 之间的某种顺序。 其他操作的跨线程顺序可以保证什么?

    如果var.store使用memory_order_releasevar.load使用memory_order_acquirevar.store排序之前var.load(即加载返回2),那么@987654340的效果@ 排序在之前 OP4

例如,如果 OP1 写入某个变量 var1,OP4 读取该变量,那么可以确定 OP4 将读取 OP1 之前写入的内容。这是最常用的情况。

    如果var.storevar.load 都使用memory_order_seq_cst 并且var.store 排序 var.load 之后(即加载返回0,这是存储之前的变量值),然后OP2 的效果被排序 OP3之后。

一些棘手的同步方案需要这种内存顺序。

    如果var.storevar.load 使用memory_order_relaxed,那么对于var.storevar.load 的任意顺序,都可以保证无顺序的跨线程操作。

此内存顺序用于其他人确保操作顺序的情况。例如,如果线程T2 创建在T1 中的var.store 之后,那么OP3OP4 将在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

调度后立即反应 Redux Store 值

在 redux store 更新后立即执行自定义回调

应用在 iPad 的 App Store 中不可见

在 react js 功能组件中调度操作后如何立即在 redux store 中使用更新的值

我应该向 App Store 发布一个包含打印语句的应用程序吗?