带有围栏和获取/释放的C++ memory_order

alf*_*ngj 9 c++ multithreading synchronization c++11

我有以下C++ 2011代码:

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

void f() {
   x.store(true, std::memory_order_relaxed);
   std::atomic_thread_fence(std::memory_order_release);
   y.store(true, std::memory_order_relaxed);
}

void g() {
   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 t1(f);
   std::thread t2(g);
   t1.join();
   t2.join();
   assert(z.load() !=0);
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

在我的计算机体系结构类中,我们被告知此代码中的断言始终成立.但经过现在的审查,我无法理解为什么会这样.

据我所知:

  • 带有' memory_order_release '的栅栏不允许在它之后执行先前的存储
  • 带有' memory_order_acquire '的栅栏不允许在它之后执行任何加载.

如果我的理解是正确的,为什么不能发生以下一系列行动?

  1. 在t1里面,y.store(true, std::memory_order_relaxed);被称为
  2. t2完全运行,并且在加载'x'时会看到'false',因此不会在单位中增加z
  3. t1完成执行
  4. 在主线程中,断言失败,因为z.load()返回0

我认为这符合'获取' - '发布'规则,但是,例如在这个问题的最佳答案:理解c ++ 11内存栅栏,这与我的情况非常相似,它暗示我的步骤1动作序列不能在'memory_order_release'之前发生,但是由于其背后的原因没有详细说明.

我对此感到非常困惑,如果有人能说清楚它会很高兴:)

Mat*_*son 4

每种情况下具体发生的情况取决于您实际使用的处理器。例如,x86 可能不会对此断言,因为它是一个缓存一致性架构(您可以有竞争条件,但是一旦一个值从处理器写入缓存/内存,所有其他处理器都会读取该值 -当然,不会阻止另一个处理器立即写入不同的值,等等)。

因此,假设它在 ARM 或类似的处理器上运行,但不能保证其本身具有缓存一致性:

因为写入x是在 之前完成的memory_order_release,所以 t2 循环不会退出,while(y...)untilx也为 true。这意味着x稍后读取时,它保证为1,因此z被更新。我唯一的疑问是,您是否也不需要releasefor z...如果在与和main不同的处理器上运行,那么中可能仍然有一个过时的值。t1t2zmain

当然,如果您有一个多任务操作系统(或者只是做了足够多的事情的中断等),则不能保证会发生这种情况 - 因为如果运行 t1 的处理器刷新其缓存,那么 t2 很可能会读取 x 的新值。

正如我所说,这不会对 x86 处理器(AMD 或 Intel 处理器)产生这种影响。

因此,一般性地解释屏障指令(也适用于 Intel 和 AMD 处理器):

首先,我们需要了解,虽然指令可以无序地开始和结束,但处理器确实对顺序有一般的“理解”。假设我们有这个“伪机器代码”:

 ...
 mov $5, x
 cmp a, b
 jnz L1
 mov $4, x
Run Code Online (Sandbox Code Playgroud)

L1:……

处理器可以mov $4, x在完成“jnz L1”之前推测性地执行 - 因此,为了解决这个事实,处理器必须回滚已执行mov $4, x的情况jnz L1

同样,如果我们有:

 mov $1, x
 wmb         // "write memory barrier"
 mov $1, y
Run Code Online (Sandbox Code Playgroud)

处理器有规则规定“不要执行在 wmb 之后发出的任何存储指令,直到完成之前的所有存储”。这是一条“特殊”指令——它的精确目的是保证内存排序。如果不这样做,你的处理器就坏了,设计部门的某个人就“命悬一线”。

同样,“读内存屏障”是处理器设计者保证处理器不会完成另一次读取的指令,直到我们完成屏障指令之前的待处理读取。

只要我们不致力于“实验性”处理器或某些无法正常工作的劣质芯片,它就会以这种方式工作。这是该指令定义的一部分。如果没有这样的保证,就不可能(或者至少极其复杂且“昂贵”)实现(安全)自旋锁、信号量、互斥体等。

通常还存在“隐式内存障碍”——也就是说,即使没有内存障碍,也会导致内存障碍的指令。软件中断(“INT X”指令或类似指令)往往会这样做。