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

Posted

技术标签:

【中文标题】其他线程是不是总是以相同的顺序看到不同线程中对同一位置的两次轻松写入?【英文标题】:Will two relaxed writes to the same location in different threads always be seen in the same order by other threads?其他线程是否总是以相同的顺序看到不同线程中对同一位置的两次轻松写入? 【发布时间】:2015-02-04 15:38:14 【问题描述】:

在 x86 架构上,存储到同一内存位置具有总顺序,例如,请参阅 this video。 C++11内存模型有哪些保证?

更准确地说,在

-- Initially --
std::atomic<int> x0;

-- 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 会怎样?

【问题讨论】:

独立写入两个不同位置的相关跟进:Will two atomic writes to different locations in different threads always be seen in the same order by other threads?. 【参考方案1】:

不,这样的结果是不允许的。 §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 或由 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++ 可用的大多数硬件提供的缓存一致性保证 原子操作。 —尾注 ]

【讨论】:

你能帮我理解p18吗? value computation 是 load-from-atomic 的同义词,side effect 是 store-to-atomic 的同义词吗? @TobiasBrüll 负载是一个值计算;商店是一个副作用。 还存在哪些其他类型的值计算?还有什么其他副作用? @TobiasBrüll 在(非易失性限定)原子对象上?我真的想不出任何,至少与 p18 相关。通常,值计算包括确定对象的身份和获取存储的值;副作用包括通过 volatile glvalue 访问对象、修改对象、调用库 I/O 函数或调用执行上述任何操作的函数 ([intro.execution]/p12)。【参考方案2】:

根据 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 的实现上,这种结果是不可能的。

【讨论】:

【参考方案3】:

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

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

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

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

【讨论】:

以上是关于其他线程是不是总是以相同的顺序看到不同线程中对同一位置的两次轻松写入?的主要内容,如果未能解决你的问题,请参考以下文章

使用Redis的分布式Java锁

如何避免两个不同的线程从DB中读取相同的行(Hibernate和Oracle 10g)

线程锁的原理是啥

JVM的重排序

线程

是否可以从多个线程写入同一文件的不同部分?