在这种情况下,原子读取操作memory_order_seq_cst读取哪个值
Which value does atomic read operation with memory_order_seq_cst read in this situation?
我已经阅读了 c++11 标准中有关内存排序的章节,并对规则感到困惑。根据C++11标准(ISO/IEC JTC1 SC22 WG21 N3690),29.3 3,据说:
所有memory_order_seq_cst操作的总顺序 S 应与所有受影响位置的"发生之前"顺序和修改顺序一致,因此从原子对象 M 加载值的每个memory_order_seq_cst操作 B 都观察到以下值之一:
— 最后一次修改 A 的结果 M 在 S 中 B 之前, 如果它存在,或者
——如果A存在,那么在对B的可见副作用序列中M进行某种修改的结果,这种修改不是memory_order_seq_cst的,也不会在A之前发生,或者
——如果A不存在,那么M在对B的可见副作用序列中对M进行某种修改的结果,这是不memory_order_seq_cst的。
因此,请考虑以下情况:
有 4 个原子操作A、B、C、D。 从代码:
- 所有这些都是对同一原子变量的操作
- A和B是任何顺序的写入操作(可以放宽)
- C是带有memory_order_seq_cst的写入操作
- D是带memory_order_seq_cst的读取操作
- A是D之前发生的最后一个写入操作
- A,B,C没有相互发生之前的关系。
- D,B,C没有相互发生之前的关系。
考虑恰好发生以下排序的执行:
- C出现在D之前,用于memory_order_seq_cst操作的单个总订单
- 此变量的修改顺序类似于A->B->C
这是可能的代码
using namespace std;
atomic_bool go(false);
atomic_int var(0);
void thread1()
{
while (!go) {}
var.store(1, memory_order_relaxed); // A
this_thread::yield();
cout << var.load(memory_order_seq_cst) << endl; // D
}
void thread2()
{
while (!go) {}
var.store(2, memory_order_seq_cst); // C
}
void thread3()
{
while (!go) {}
var.store(3, memory_order_relaxed); // B
}
int main() {
thread t1(thread1);
thread t2(thread2);
thread t3(thread3);
go = true;
t1.join();
t2.join();
t3.join();
}
读取操作D是否有可能读取操作 B 写入的值,给定 A、B、C 的修改顺序var
?
如果不可能,哪些规则排除了这种可能性?
如果可能,这意味着memory_order_seq_cst
可以读取"在最后一次写入之前"写入"的值memory_order_seq_cst
。这是C++标准的"错误",还是在并非一切都seq_cst时故意设计的?
在这种情况下,D 可能会从 A、B 或 C 读取。
考虑一个包含四个节点的图形:A、B、C 和 D。 和边(sc:顺序一致(总)排序(
C --sc--> D),sb:在之前/之前发生排序(A --sb--> D),mo:修改顺序(A --mo--> B --mo--> C)和rf:读取自(? --rf--> D))。图中的射频边与C++记忆模型不一致有两个原因:因果关系和因为您无法从隐藏的视觉副作用中读取。
如果你暂时忽略sc边缘,那么 - 只有一个原子变量,图上唯一的因果限制是没有涉及rf边和(定向)sb边的循环(这是我研究的结果)。在这种情况下,甚至不存在这样的循环,因为你只有一个射频边沿 - 所以,没有任何理由你不能从三个写入中的任何一个读取。
但是,您同时指定确切的修改顺序(恕我直言,这并不重要,您应该只对程序的可能结果感兴趣)以及一个 sc 边缘。我们仍然需要研究这些是否与三种可能的射频边缘中的每一个兼容,以便从隐藏的视觉副作用中读取。
请注意,如果给定的 rf 边沿的写入节点被释放并且读取节点被获取,则会引入同步;sc 是释放/获取的,因此后者为真,前者仅在从节点 C 读取时为真。但是,同步意味着永远不会超过(按修改顺序)写入之前的所有内容都必须在读取之后的所有内容之前发生;读取后什么都没有,所以整个同步无关紧要。
此外,听写修改顺序(A --mo--> B --mo--> C)与指示的总sc顺序(C --sc--> D)没有因果关系,因为D是读取而不是修改顺序子图的一部分。唯一不允许的(由于因果关系)是涉及 sc 和 mo 边缘的有向循环。
现在,作为一个实验,假设我们也使节点 A 成为 sc。然后我们需要将 A 放入总排序中,因此 A --sc--> C --sc--> D,C --sc--> A --sc--> D 或 C --sc--> D --sc--> A,但我们有 A --mo--> C,因此后两者是不允许的(会导致(因果)循环),唯一可能的排序是:A --sc--> C --sc--> D。现在不再可能从 A 读取,因为这会导致以下子图:
A --sc--> C
| /
| /
| /
rf sc
| /
| /
| /
v v
D
并且 C 中的写入将始终覆盖 A 在被 D 读取之前写入的值(又名 A 是 D 隐藏的视觉副作用)。
如果 A 不是 sc(就像原始问题中的情况一样),那么只有在以下情况下才不允许此 rf (因为隐藏的 vse)
A --hb--> C
| /
| /
| /
rf sc
| /
| /
| /
v v
D
其中'hb'代表Happen Before(出于同样的原因;那么A是D隐藏的视觉副作用,因为C总是在D读取之前覆盖A写入的值)。
然而,在原始问题中,线程 1 和 2 之间没有先行发生,因为这种同步需要在两个线程之间(或栅栏或任何会导致额外同步的东西)之间出现另一个 rf 边。
最后,是的,这是预期行为,而不是标准中的错误。
编辑
引用您引用的标准:
— M 的最后一次修改的结果,在 S 中 B 之前,如果 它存在,或
这里的A是你的C,B是你的D。这里提到的 A 确实存在,即节点 C (C --sc--> D)。所以这一行说可以读取节点 C 写入的值。
— 如果 A 存在,则在可见光中对 M 进行某种修改的结果 关于B的副作用序列不是
memory_order_seq_cst
,这不会发生在 A 之前,或者
同样,这里的A是你的C,它存在。那么"在可见的副作用序列中对M(var)的一些修改的结果,相对于B(你的D)没有memory_order_seq_cst
">是你的A。正如我们已经确定的那样,您的 A 不会先于您的 C(他们的 A)发生。因此,这表示可以从您的 A 读取写入的值。
— 如果 A 不存在,则 M 在 相对于B的可见副作用序列不是
memory_order_seq_cst
.
这在这里无关紧要,并且仅适用于在 B(您的 D)之前的总排序 M 的 S (var) 中没有写入的情况。
即使将A->B->C作为"修改顺序">,B也是一种可能性,因为它"不memory_order_seq_cst,并且在[C]之前不会发生"。
可见副作用序列(1.10.14)的标准定义支持这一点(强调我的):
关于 M 的值计算 B,对原子对象 M 的可见副作用序列是M修改顺序中副作用的最大连续子序列,其中第一个副作用相对于B是可见的,并且对于每个副作用,B 不是发生在它之前的情况。由评估 B 确定的原子对象 M 的值应是M相对于B的可见序列中的某个操作存储的值。
因此,即使有明确的修改顺序,您的负载也可以产生A、B或C。
- 理解boost::asio-async_read在无需读取内容时的行为
- 使用新行和不使用新行读取文件
- 读取文件并输入到矢量中
- 用c++从输入文件中读取另一行
- 读取文件的最后一行并输入到链接列表时出错
- 在进程中对同一管道进行读取和写入时C++管道出现问题
- 无法找到/读取配置文件.conf-FileIOException
- 如何使用Luacneneneba API正确读取字符串和表参数
- C++将文本文件中的数据读取到结构数组中
- 正在将csv文件读取为双精度矢量
- 为什么 sscanf 无法从一个字符串中读取uint64_t和字符?
- 为什么在读取文件大小时文件IO速度会发生变化
- 正在读取二进制文件(is_open)
- 如何在c++中从文本文件中逐行读取整数
- SSH通过/sbin/SSH无法读取RSA密钥文件(从控制台运行)
- 独立读取-修改-写入顺序
- 从文本文件中读取时钟时间和事件时间并进行处理
- 如何从文本文件中读取值和数组
- 为什么文件名被设置为一个点,而不是在读取矢量中的文件名时
- 一种在C++中读取TXT配置文件的简单方法