我已经阅读了关于c ++ 11标准中的内存排序的章节,并且被规则搞糊涂了。根据C ++ 11标准(ISO / IEC JTC1 SC22 WG21 N3690),29.3 3,它说:
所有memory_order_seq_cst操作上应该有一个总订单S,与所有受影响位置的“发生之前”订单和修改订单一致,这样每个从原子对象M加载值的memory_order_seq_cst操作B都会观察到其中一个以下值:
- S中B的最后修改A的结果,如果存在,或者 - 如果A存在,则在可见序列中对M进行一些修改的结果关于B的副作用不是memory_order_seq_cst而是在A之前没有发生,或者是 - 如果A不存在,则对于B的副作用的可见序列中的M的一些修改的结果是不是memory_order_seq_cst。
因此,请考虑以下情况:
有4个原子操作 A , B , C , D 。
这是可能的代码
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 写入的值?如果不可能,有哪些规则排除这种可能性?如果可能,这意味着memory_order_seq_cst可以读取最后一次memory_order_seq_cst写入“之前写入”的值。这是c ++标准中的“bug”,还是故意设计的?
答案 0 :(得分:1)
即使 A -> B -> C 作为“修改订单” , B 是有可能的,因为它“不是memory_order_seq_cst,并且不会在[ C ]” 之前发生。
可见的副作用序列(1.10.14)的标准定义支持这一点(强调我的意思):
相对于 M 的值计算 B ,原子对象 M 上可见的副作用序列 em>是按 M 的修改顺序排列的副作用的最大连续子序列,其中第一个副作用对于 B 可见,并且对于每个侧面实际上,不是 B 发生在它之前。由评估 B 确定的原子对象 M 的值应为 some 操作在可见序列中存储的值。 > M 关于 B 。
因此,即使有明确的修改订单,您的负载也可以产生 A , B 或 C 。
答案 1 :(得分:0)
在这种情况下,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)读取。
图表中的rf边缘与C ++内存模型不一致有两个原因:因果关系,因为你无法从隐藏的视觉副作用中读取。
如果你暂时忽略sc边缘,那么 - 只有一个原子变量,图上唯一的因果限制是没有涉及rf边和(有向)sb边的循环(这是来自{的结果{3}})。在这种情况下,甚至不存在这样的循环,因为你只有一个rf边缘 - 所以,没有任何理由你无法从三次写入中的任何一次读取。
但是,你指定两者,确切的修改顺序(不是这对imho来说很重要,你应该只对程序的可能结果感兴趣),以及一个sc边缘。我们仍然需要调查这些是否与三个可能的rf边缘中的每一个相兼容,以便从隐藏的视觉副作用中读取。
注意,如果给定的rf边缘的写节点被释放并且读取节点被获取,则引入同步; sc是释放/获取,所以后者是真的而前者只有在从节点C读取时才是真的。然而,同步意味着永远不会超过(按修改顺序)写入之前的所有内容必须在读取之后的所有内容之前发生;阅读后没有任何内容,因此整个同步并不重要。
此外,口述的修改顺序(A-em - > B -mo - > C)与指示的总sc排序(C-sc-gt; D)不因果不一致,因为D是读取而不是修改顺序子图的一部分。唯一不允许的(因为因果关系)是涉及sc和mo边缘的有向循环。
现在,作为一个实验,假设我们也将节点A设为sc。然后我们需要在总排序中加上A,因此A - sc - > C --sc - &gt; D,C - sc - &gt; A --sc - &gt; D或C --sc - &gt; D --sc - &gt; A,但我们有A - 莫 - > C,因此后两者是不允许的(会导致(因果)循环)并且唯一可能的排序是:A - sc - &gt; C --sc - &gt; 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
其中&#39; hb&#39;代表Happens-Before(出于同样的原因;然后A是D的隐藏视觉副作用,因为在D读取之前C将始终覆盖A写入的值)。
在原始问题中,在线程1和2之间没有发生 - 然而,因为这样的同步将需要两个线程之间的另一个rf边缘(或栅栏或任何会导致额外同步的rf)。
最后,是的,这是预期的行为,而不是标准中的错误。
修改强>
引用您引用的标准:
— the result of the last modification A of M that precedes B in S, if it exists, or
这里的A是你的C,这里的B是你的D.这里提到的A存在,即节点C(C -sc-gt; D)。所以这一行表示可以读取节点C写的值。
— if A exists, the result of some modification of M in the visible sequence of side effects with respect to B that is not memory_order_seq_cst and that does not happen before A, or
同样,这里的A是你的C而且它存在。然后,对于B(你的D)的副作用的可见序列中的M(var)的一些修改的结果是 not memory_order_seq_cst&#34;是你的A.而且正如我们已经建立的那样,你的A不会发生在你的C(他们的A)之前。因此,这表示可以读取从A中写入的值。
— if A does not exist, the result of some modification of M in the visible sequence of side effects with respect to B that is not memory_order_seq_cst.
这与此处无关,只有在B(您的D)之前的M(var)的总排序S中没有写入时才适用。