意外的线程间发生在宽松的内存排序关系之前

问题描述 投票:0回答:1

我正在实际操作 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++
进行编译,但我认为这在这里并不重要。

c++ concurrency lock-free
1个回答
0
投票

你什么都没有错过。断言可能会失败。

特别是,编译器或 CPU 被允许在

y
中的
x
之前存储到
write_x_then_y
,因为这两个语句的顺序并不意味着任何额外的happens-before约束。

然后另一个线程可以在两个存储之间执行两种加载,以便

if
条件将评估为
false

您实际上没有看到这种情况发生,但这并不能改变它是允许的输出,并且您不能保证它在另一次编译后不会有不同的行为,或者即使您足够频繁地运行相同的二进制文件。它可能只发生在百万分之一或十亿分之一或其他任何情况下。

© www.soinside.com 2019 - 2024. All rights reserved.