14

在 x86 架构上,存储到同一内存位置具有总顺序,例如,请参阅此视频。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 以外的某些架构上)?如果我将 all 替换memory_orderstd::memory_order_relaxed怎么办?

4

3 回答 3

10

不,这样的结果是不允许的。§1.10 [intro.multithread]/p8, 18(引用 N3936/C++14;在 N3337/C++11 的第 6 段和第 16 段中可以找到相同的文本):

8 对特定原子对象 M 的所有修改都以某种特定的总顺序发生,称为M的修改顺序。

18 如果原子对象 M 的值计算 A 发生在 M 的值计算 B 之前,并且 A 从 M 上的副作用 X 获取其值,则 B 计算的值应为 X 存储的值或值由 M 上的副作用 Y 存储,其中 Y 按照 M 的修改顺序跟随 X。 [注意:此要求称为读-读一致性。——<em>尾注]

在您的代码中有两个副作用,并且通过 p8 它们以特定的总顺序发生。在线程 3 中,计算要存储的值的值计算r1发生在 的之前,r2所以我们知道线程 1 执行的存储在 的修改顺序中先于线程 2 执行的存储。既然如此,不与p18发生冲突就无法观察。这是不管用的。r1 == 1r2 == 2xThread 4r3 == 2, r4 == 1memory_order

p21(N3337 中的 p19)中有一条相关的注释:

[注意:前面的四个一致性要求实际上不允许编译器将原子操作重新排序到单个对象,即使这两个操作都是宽松的负载。这有效地使大多数硬件提供的缓存一致性保证可用于 C++ 原子操作。——<em>尾注]

于 2014-12-06T16:19:48.430 回答
6

根据 C++11 [intro.multithread]/6:“对特定原子对象的所有修改都以M某个特定的总顺序发生,称为 . 的修改顺序M。” 因此,特定线程对原子对象的读取永远不会看到比线程已经观察到的值“旧”的值。请注意,这里没有提到内存排序,所以这个属性对所有这些都成立 - seq_cstthrough 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 的实现上不可能出现这种结果。

于 2014-12-06T16:20:00.827 回答
1

鉴于 C++11 规则绝对不允许这样做,这里有一种更定性/直观的方式来理解它:

如果没有更多的商店到x,最终所有的读者都会同意它的价值。(即两家商店之一排在第二位)。

如果不同的线程可能对订单有不同意见,那么他们要么永久/长期不同意该值,要么一个线程可以看到该值第三次额外更改(幻像存储)。

幸运的是,C++11 不允许这些可能性中的任何一种。

于 2018-06-03T17:46:48.367 回答