memory_order_relaxed 在 C++ 并发操作中的代码中未按预期工作

memory_order_relaxed not work as expected in code from C++ concurrency in action

在问这个问题之前,根据this question的建议,我尝试将我的 VM 内核设置为大于 1(我将其设置为 2),此解决方案对作者有效,但对我无效。

C++ Concurrency in Action 一书在清单 5.5 中给出了一个例子

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x_then_y()
{
  x.store(true,std::memory_order_relaxed);  // 1
  y.store(true,std::memory_order_relaxed);  // 2
}
void read_y_then_x()
{
  while(!y.load(std::memory_order_relaxed));  // 3
  if(x.load(std::memory_order_relaxed))  // 4
    ++z;
}
int main()
{
    for(int i = 0; i < 1000; i++){
      // this loop is addeed by me in order to try 1000 times to see whether the assertion will fail
      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);  // 5
    }
}

书上说断言assert(z.load()!=0)可能会失败。但是,我不能在测试时失败这个断言。

我在 64 位 Ubuntu(在 VMWare 下),g++ -g -o test --std=c++17 test.cpp -lpthread,我在 gcc 5.4.0 和 gcc 7.2.0 下测试了程序。

我还通过 64 位 Windows 10(不在 VM 中)测试了该程序,Visual studio 2015.

同时在网上搜了一下,看到有人说这是x86架构的问题,不过我记得在x86下,Loads可能会随着旧店重新排序到不同的位置,所以我觉得可以有一个命令 2-3-4-1 可能会使此断言失败。

这本书是正确的,断言可能会失败,只是不是像你怀疑的那样在 x86 上。

明确地说,atomics make the specific guarantee that no data races will occur. It doesn't by itself guarantee absence of race conditions

std::memory_order_relaxed 是所有内存顺序中最松散的。它是一切的底线。实际上,它 prevents compiler optimizations 围绕变量。

atomic<bool> x, y;
x.store(true,std::memory_order_relaxed);
y.store(true,std::memory_order_relaxed);

// is similar to...
volatile bool x, y;
x = true;
asm volatile("" ::: "memory");  // Tells compiler to stop optimizing here,
                                // but issues no instructions
y = true;

但是,CPU 本身可能决定重新排序写入,或者缓存系统可能决定稍后将写入发布到 x。这最终导致断言失败。

就语言而言,这是因为写入与读取没有 happens before 关系,因此无法保证其结果。

最后,您在 x86 机器上看不到任何失败断言的原因是因为它本身就具有 acquire and release semantics 的读写能力。

// On x86, effectively
void write_x_then_y()
{
    x.store(true,std::memory_order_release);  // 1
    y.store(true,std::memory_order_release);  // 2
}
void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));  // 3
    if(x.load(std::memory_order_acquire))  // 4
        ++z;
}

也就是说,如果y// 3被确定为true//2之前发生的事情保证在[=16=之后可见].