我正在实际操作 C++ 并发,并在尝试理解清单 5.12 时遇到了问题,复制如下(GitHub 代码示例)。我理解为什么当释放和获取内存栅栏进入时以下应该起作用。
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
x.store(true,std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
y.store(true,std::memory_order_relaxed);
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
if(x.load(std::memory_order_relaxed))
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0);
}
但是,如果我移除栅栏,这个示例出乎意料地仍然有效并且
z == 1
。请参阅下面我修改的示例:
#include <atomic>
#include <cassert>
#include <thread>
std::atomic_int x(0);
std::atomic_int y(0);
std::atomic_int z(0);
void read_y_then_x() {
while (!(y.load(std::memory_order_relaxed) == 1))
;
// std::atomic_thread_fence(std::memory_order_acquire);
if (x.load(std::memory_order_relaxed) == 1) {
z.fetch_add(1, std::memory_order_relaxed);
}
}
void write_x_then_y() {
x.store(1, std::memory_order_relaxed);
// std::atomic_thread_fence(std::memory_order_release);
y.store(1, std::memory_order_relaxed);
}
int main() {
for (int i = 0; i < 100'000; i++) {
z.store(0);
x.store(0);
y.store(0);
std::thread t2(read_y_then_x);
std::thread t1(write_x_then_y);
t2.join();
t1.join();
assert(z.load(std::memory_order_relaxed) == 1);
}
}
我在这里缺少内存排序约束吗?是否存在我不知道的发布顺序?
我在 M1 mac 上运行它并使用
g++
进行编译,但我认为这在这里并不重要。
你什么都没有错过。断言可能会失败。
特别是,编译器或 CPU 被允许在
y
中的 x
之前存储到 write_x_then_y
,因为这两个语句的顺序并不意味着任何额外的happens-before约束。
然后另一个线程可以在两个存储之间执行两种加载,以便
if
条件将评估为 false
。
您实际上没有看到这种情况发生,但这并不能改变它是允许的输出,并且您不能保证它在另一次编译后不会有不同的行为,或者即使您足够频繁地运行相同的二进制文件。它可能只发生在百万分之一或十亿分之一或其他任何情况下。