对不同线程中同一位置的两次轻松写入是否总是以相同的顺序被其他线程看到

Will two relaxed writes to the same location in different threads always be seen in the same order by other threads?

本文关键字:线程 是否 顺序 其他 位置 两次      更新时间:2023-10-16

在 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 以外的某些体系结构上(?如果我用std::memory_order_relaxed替换所有memory_order怎么办?

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

8 对特定原子对象M的所有修改都发生在某些 特定的总阶,称为M的修改阶

18 如果原子对象 M 的值计算 A 发生在 M 的值计算 B,A 从副作用 X 中获取其值 在 M 上,则 B 计算的值应为 X 或 M 上的副作用 Y 存储的值,其中 Y 在 X 之后 M. [ 注意:此要求称为 读读连贯性。—尾注 ]

在你的代码中有两个副作用,到 p8 时,它们以某种特定的总顺序出现。在线程 3 中,计算要存储在r1中的值的值计算发生在 r2 之前,因此给定r1 == 1r2 == 2我们知道线程 1 执行的存储先于线程 2 执行的存储,修改顺序为 x。在这种情况下,Thread 4无法在不违反p18的情况下观察r3 == 2, r4 == 1。这与使用memory_order无关。

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

[注:上述四项一致性要求有效 不允许编译器将原子操作重新排序为单个对象, 即使这两个操作都是松弛负载。这有效地使 大多数硬件提供的缓存一致性保证可供C++ 原子操作。—尾注 ]

根据 C++11 [intro.multithread]/6:"对特定原子对象的所有修改M以某种特定的总顺序发生,称为M的修改顺序。因此,特定线程对原子对象的读取永远不会看到比线程已经观察到的值"旧"的值。请注意,此处没有提到内存排序,因此此属性适用于所有内存排序 - seq_cstrelaxed

在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 的实现上不可能产生该结果。

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

如果没有其他商店可以x,最终所有读者都会同意它的价值。 (即两家商店中的一家排名第二(。

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

幸运的是,C++11不允许这两种可能性。