了解c ++内存顺序吗?我错了吗?

hon*_* bi 5 c++ concurrency multithreading

#include <atomic>
#include <cassert>
#include <thread>

std::atomic<bool> x = false, y = false, go = false;
int v = 0;

// t1
void write_xy() {
  while (!go) {
    std::this_thread::yield();
  }

  v = 1;                                     // 1
  x.store(true, std::memory_order_relaxed);  // 2
  y.store(true, std::memory_order_relaxed);  // 3
}

// t2
void read_yx() {
  while (!go) {
    std::this_thread::yield();
  }

  while (!y.load(std::memory_order_relaxed))
    ;

  assert(1 == x.load(std::memory_order_relaxed));  // 4
  assert(1 == v);                                  // 5
}

int main() {
  for (;;) {
    x = false;
    y = false;
    v = 0;

    go = false;
    std::thread t1(write_xy);
    std::thread t2(read_yx);
    go = true;  // start
    t1.join();
    t2.join();
  }
}
Run Code Online (Sandbox Code Playgroud)

作为C ++并发编程的初学者,根据我的理解memory_order_relaxed,t1上述代码中线程中三个语句的执行顺序是看不到的t2。从的角度来看t2,中的三个语句t1的顺序可能为3、2、1,因此可能触发4和5的断言。

经过多次尝试后,assert从未触发,因此我写了一个无限循环来重复上述过程,assert但仍然没有触发。后来,有人怀疑它t1在t2开始执行之前就已结束,因此go引入了变量以等待两个线程的开始,以确保两个线程都尽快开始执行,并且assert仍然没有被触发。

我在装有Centos8和4个CPU的虚拟机上进行了测试。我的CPU是i5-7500。

Max*_*hof 7

语言允许发生某些事情并不意味着您可以在特定情况下重现它。

让我们暂时忽略数据竞争v(即使这意味着您的程序具有未定义的行为)。

您正在编译x86的代码,该代码对内置的内存排序具有非常强的保证。例如,使用std::memory_order_release以下命令执行存储时,您将获得完全相同的汇编代码:

https://godbolt.org/z/pZaFDC

    mov     DWORD PTR v[rip], 1
    mov     BYTE PTR x[rip], 1
    mov     BYTE PTR y[rip], 1
Run Code Online (Sandbox Code Playgroud)

所以这个代码(编译为你的CPU)都保证有两个v == 1和x == 1可见的,当所有其他线程y == 1。您的C ++程序没有此保证,但是此机器代码有此保证。

同样,std::memory_order_acquire用于加载无效(只有断言消息的文本更改):

https://godbolt.org/z/e2-uNA

    movzx   eax, BYTE PTR y[rip]
[...]
    movzx   eax, BYTE PTR x[rip]
[...]
    cmp     DWORD PTR v[rip], 1
Run Code Online (Sandbox Code Playgroud)

同样,该平台已经提供了必要的保证。其他平台(例如ARM)提供的保证较少,您会在编译后的二进制文件中看到差异:

https://godbolt.org/z/Ru4YdD

在这里,同步被添加到所有存储并读取:

    bl      __sync_synchronize
Run Code Online (Sandbox Code Playgroud)

上面的x86代码也是为什么此时数据争用v不起作用的原因。但是,依赖于此是一个可怕的想法,因为编译器将完全有权(例如)assert(v == 1);在之前移动while (!y.load(std::memory_order_relaxed))。只是目前还没有这样做。

获得断言的另一种方法是,如果编译器重新排序了您的加载和存储。可以这样做(但是上面的发布获取顺序则不允许这样做),但事实并非如此,因为这样做没有意义。您可能可以通过更改周围的代码来诱使这样做,但是我无法提出一种实现方法。