在x86架构上,存储到同一内存位置的订单总数有限,例如,请参阅this video。 C ++ 11内存模型有哪些保证?
更准确地说,
-- Initially --
std::atomic<int> x{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
x.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire);
int r2 = x.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = x.load(std::memory_order_acquire);
int r4 = x.load(std::memory_order_acquire);
是否允许结果r1==1, r2==2, r3==2, r4==1
(在x86以外的某些架构上)?如果我要memory_order
替换所有std::memory_order_relaxed
,该怎么办?
答案 0 :(得分:9)
不,不允许这样的结果。 §1.10[intro.multithread] / p8,18(引用N3936 / C ++ 14;相同的文本见N3337 / C ++ 11的第6和16段):
8对特定原子对象M的所有修改都出现在某些原子对象中 特定的总订单,称为M的修改订单。
18如果原子对象M的值计算A发生在a之前 M的值计算B,A从副作用X取其值 在M上,那么由B计算的值应该是存储的值 X或副作用Y在M上存储的值,其中Y跟随X in M的修改顺序[注意:此要求称为 读 - 读一致性。 - 结束记录]
在您的代码中有两个副作用,而p8则以某种特定的总顺序出现。在线程3中,计算要存储在r1
中的值的值计算发生在r2
之前,因此给定r1 == 1
和r2 == 2
我们知道存储由线程1在线程2以x
的修改顺序执行的存储之前。既然如此,Thread 4
如果不与p18发生冲突就无法观察r3 == 2, r4 == 1
。这与使用的memory_order
无关。
p21(N3337中的第19页)中有一条相关的注释:
[注意:有效的前四个一致性要求 禁止编译器将原子操作重新排序到单个对象, 即使两个操作都是放松的负载。这有效地使 缓存一致性保证由C ++可用的大多数硬件提供 原子操作。 - 结束记录]
答案 1 :(得分:5)
Per C ++ 11 [intro.multithread] / 6:&#34;对特定原子对象M
的所有修改都以某种特定的总顺序出现,称为M
的修改顺序。 &#34;因此,特定线程对原子对象的读取永远不会看到更老的&#34;值比线程已经观察到的值。请注意,这里没有提及内存排序,所以这个属性适用于所有这些属性 - seq_cst
到relaxed
。
在OP中给出的示例中,x
的修改顺序可以是(0,1,2)
或(0,2,1)
。在该修改顺序中观察到给定值的线程以后不能观察到较早的值。结果r1==1, r2==2
表示x
的修改顺序为(0,1,2)
,但r3==2, r4==1
表示它是(0,2,1)
,这是一个矛盾。因此,在符合C ++ 11的实现上无法获得结果。
答案 2 :(得分:0)
鉴于C ++ 11规则绝对不允许这样做,这里有一种更为定性/直观的理解方式:
如果x
没有其他商店,最终所有读者都会同意其价值。 (即两家商店中的一家排名第二)。
如果不同的线程可能不同意订单,那么他们要么永久/长期不同意该值,要么一个线程可以看到值更改第3个额外时间(幻像存储)
幸运的是,C ++ 11并不允许这两种可能性。