假设我们有一个简单的变量(std::atomic<int> var
)和2个线程T1
和T2
,我们有以下代码用于T1
:
...
var.store(2, mem_order);
...
和T2
...
var.load(mem_order)
...
另外,我们假设T2
(加载)在时间上比T1
(存储)晚 123ns(根据C++标准在修改顺序中)。我对这种情况的理解如下(针对不同的内存顺序):
-
memory_order_seq_cst
-T2
负载有义务加载2
.因此,它必须有效地加载最新值(就像 RMW 操作一样) -
memory_order_acquire
/memory_order_release
/memory_order_relaxed
-T2
没有义务加载2
但可以加载任何较旧的值,但唯一的限制是:该值不应早于该线程加载的最新值。因此,例如var.load
返回0
.
我的理解正确吗?
UPDATE1:
如果我的推理有误,请提供C++标准中的文本来证明它。不仅仅是对某些架构如何工作的理论推理。
我的理解正确吗?
不。你误解了记忆顺序。
假设
T2
(加载)比T1
(存储)晚执行 123ns...
在这种情况下,T2 将看到 T1 对任何类型的内存顺序执行的操作(此外,此属性应用于任何内存区域的读/写,例如参见 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4431.pdf,1.10,第 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
之前排序(即 load 返回 2),则OP1
的效果在OP4
之前排序。
例如,如果OP1
写入某个变量 var1,OP4
读取该变量,那么可以确信OP4
将读取OP1
之前写入的内容。这是最常用的情况。
- 如果
var.store
和var.load
都使用memory_order_seq_cst
并且var.store
在var.load
之后排序(即,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 上的时间计数操作之间的顺序。
- 观察开始和结束时间计数操作之间的顺序。
- 观察完成时间计数操作和第二个操作开始之间的顺序。
这些步骤在第一个操作和第二个操作之间传递排序。
没有找到任何论据来证明我的理解是错误的,我认为它是正确的,我的证明如下:
memory_order_seq_cst - T2 负载必须加载 2。
这是正确的,因为所有使用 memory_order_seq_cst
的操作都应该形成所有内存操作的原子变量的单个总顺序。标准摘录:
[29.9/3] 所有订单上应有一个总订单 S memory_order_seq_cst 操作,与"发生之前"顺序一致,并且 所有受影响位置的修改订单,以便每个 memory_order_seq_cst从原子加载值的操作 B 对象 M 观察到以下值之一<...>
我问题的下一点:
memory_order_acquire/memory_order_release/memory_order_relaxed - T2 是 没有义务加载 2,但可以加载任何较旧的值<...>
我没有找到任何可能表明修改顺序稍后执行的负载应该看到最新值的证据。对于具有与memory_order_seq_cst
不同的内存顺序的存储/加载操作,我发现的唯一要点是:
[29.3/12] 实现应使原子存储对原子可见 在合理的时间内加载。
和
[1.10/28] 实现应确保原子或同步操作分配的最后一个值(按修改顺序) 将在有限的时间内对所有其他线程可见。
因此,我们唯一的保证是编写的变量将在一段时间内可见 - 这是非常合理的保证,但这并不意味着可以立即看到以前的商店。它证明了我的第二点。
鉴于所有这些,我最初的理解是正确的。
123 nS 后来不会强制对 T2 进行排序,查看 T1 的结果。 这是因为如果运行T2的物理程序计数器(晶体管等)距离运行T1(大型多核超级计算机等)的物理程序计数器超过40米,那么光速将不允许T1写入的状态信息传播那么远(还)。 如果用于加载/存储的物理内存与两个线程处理器相距一定距离,则会产生类似的效果。