Acquire/Release VS C++11 中的顺序一致性?
Acquire/Release VS Sequential Consistency in C++11?
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_release);
}
void write_y()
{
y.store(true, std::memory_order_release);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_acquire))
;
if (y.load(std::memory_order_acquire)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_acquire))
;
if (x.load(std::memory_order_acquire)) {
++z;
}
}
int main()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0);
}
如果我在 cppreference's last example 中将 seq_cst 替换为 acquire/release,
assert(z.load() != 0)
可以失败吗?
- Seq_CST可以防止StoreLoad重新排序,但是代码没有。
- Acquire 可以防止 LoadLoad 重新排序。
- 发布可以防止 StoreStore 重新订购。
是的,断言可以触发。
本金属性即不获取/释放保证的是单个总修改顺序。它仅保证 a
和 b
的(不存在的)先前操作被 c
和 d
观察到,如果他们从负载中看到 true
。
一个(稍微人为设计的)示例是在不完全高速缓存一致的多cpu(物理套接字)系统上。 Die 1 有核心 A 运行ning 线程 a
和核心 C 运行ning 线程 c
。 Die 2 有核心 B 运行ning 线程 b
和核心 D 运行ning 线程 d
。与命中片上缓存的内存操作相比,两个插槽之间的互连具有较长的延迟。
a
和 b
运行 在同一挂钟时间。 C 与 A 在芯片上,因此可以立即看到 x
的存储,但互连延迟了它对 y
的存储的观察,因此它看到了旧值。类似地,D 与 B 在片上,因此它看到 y
的存储,但错过了 x
.
的存储
而如果您具有顺序一致性,则需要一些协调来强制执行总顺序,例如“C 和 D 在互连同步缓存时被阻塞”。
是的,如果您使用 acquire
/release
顺序,您的代码中可能会出现 z.load() == 0
。 x
和 y
的独立写入之间没有 happens-before 关系。 cppreference 专门使用该示例来说明 acquire/release 不足的情况并非巧合。
这有时称为 IRIW(独立写入的独立读取),并且在某些硬件订购模型中往往被掩盖。特别是,仅根据可能的加载-加载、加载-存储、存储-存储等定义的内存模型,重新排序并没有说明任何关于 IRIW 的任何方式。在 x86 内存模型中,IRIW 重新排序是不允许的,因为一个子句解释了存储具有总顺序并且所有处理器都以相同的顺序查看存储。
我不知道是否有任何常用的 CPU 在使用获取和释放所需的障碍 and/or 指令时允许 IRIW 重新排序,但如果有人这样做我不会感到惊讶。
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
void write_x()
{
x.store(true, std::memory_order_release);
}
void write_y()
{
y.store(true, std::memory_order_release);
}
void read_x_then_y()
{
while (!x.load(std::memory_order_acquire))
;
if (y.load(std::memory_order_acquire)) {
++z;
}
}
void read_y_then_x()
{
while (!y.load(std::memory_order_acquire))
;
if (x.load(std::memory_order_acquire)) {
++z;
}
}
int main()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join(); b.join(); c.join(); d.join();
assert(z.load() != 0);
}
如果我在 cppreference's last example 中将 seq_cst 替换为 acquire/release,
assert(z.load() != 0)
可以失败吗?
- Seq_CST可以防止StoreLoad重新排序,但是代码没有。
- Acquire 可以防止 LoadLoad 重新排序。
- 发布可以防止 StoreStore 重新订购。
是的,断言可以触发。
本金属性即不获取/释放保证的是单个总修改顺序。它仅保证 a
和 b
的(不存在的)先前操作被 c
和 d
观察到,如果他们从负载中看到 true
。
一个(稍微人为设计的)示例是在不完全高速缓存一致的多cpu(物理套接字)系统上。 Die 1 有核心 A 运行ning 线程 a
和核心 C 运行ning 线程 c
。 Die 2 有核心 B 运行ning 线程 b
和核心 D 运行ning 线程 d
。与命中片上缓存的内存操作相比,两个插槽之间的互连具有较长的延迟。
a
和 b
运行 在同一挂钟时间。 C 与 A 在芯片上,因此可以立即看到 x
的存储,但互连延迟了它对 y
的存储的观察,因此它看到了旧值。类似地,D 与 B 在片上,因此它看到 y
的存储,但错过了 x
.
而如果您具有顺序一致性,则需要一些协调来强制执行总顺序,例如“C 和 D 在互连同步缓存时被阻塞”。
是的,如果您使用 acquire
/release
顺序,您的代码中可能会出现 z.load() == 0
。 x
和 y
的独立写入之间没有 happens-before 关系。 cppreference 专门使用该示例来说明 acquire/release 不足的情况并非巧合。
这有时称为 IRIW(独立写入的独立读取),并且在某些硬件订购模型中往往被掩盖。特别是,仅根据可能的加载-加载、加载-存储、存储-存储等定义的内存模型,重新排序并没有说明任何关于 IRIW 的任何方式。在 x86 内存模型中,IRIW 重新排序是不允许的,因为一个子句解释了存储具有总顺序并且所有处理器都以相同的顺序查看存储。
我不知道是否有任何常用的 CPU 在使用获取和释放所需的障碍 and/or 指令时允许 IRIW 重新排序,但如果有人这样做我不会感到惊讶。