关于C ++ 11内存模型的奇怪结果(轻松订购)

时间:2012-08-31 21:36:51

标签: c++ c++11 memory-model

我在Anthony Williams的书“C ++ Concurrency”

的内存模型中测试了这个例子
#include<atomic>
#include<thread>
#include<cassert>

std::atomic_bool x,y;
std::atomic_int z;

void write_x_then_y() {
  x.store(true, std::memory_order_relaxed);
  y.store(true, std::memory_order_relaxed);
}

void read_y_then_x() {
  while(!y.load(std::memory_order_relaxed));
  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);
}

根据解释,对差异变量(此处为x和y)的放松操作可以自由重新排序。但是,我重复运行这个问题超过几天。我从未遇到断言(assert(z.load()!= 0);)触发的情况。我只使用默认优化并使用g ++编译代码-std = c ++ 11 -lpthread dataRaceAtomic.cpp 有没有人真的尝试过它并打出了断言?谁能给我一个关于我测试结果的解释?顺便说一句,我也尝试了不使用原子类型的版本,我得到了相同的结果。目前,这两个项目都在健康运行。感谢。

4 个答案:

答案 0 :(得分:8)

这可能取决于您运行的处理器类型。

x86没有像其他处理器那样轻松的内存模型。特别是,商店永远不会对其他商店进行重新订购。

http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/有关于x86内存模型的更多信息。

答案 1 :(得分:3)

关于原子,记忆排序和测试的一些事情。

首先,这个例子是一个例子;你应该阅读并思考它。在现实世界中,启动新线程的开销意味着write_x_then_y将在read_y_then_x开始之前很久就已经完成运行,因此重复启动这两个线程的测试程序实际上不会永远看到重新排序。欢迎来到测试多线程代码的精彩世界!

其次,需要考虑两个重新排序问题。

首先,编译器可以生成以与源代码使用的顺序不同的顺序存储或读取内容的代码;在没有多线程的情况下,这是一个有效的优化,而且它是一个重要的优化。另一方面,一旦引入多个线程,存储顺序和读取顺序就很重要。因此,新的C ++内存模型指定何时无法移动存储和加载;特别是,它们不能跨原子访问移动。这给了你一个可以解释的固定点:在我做这个原子存储之前我做了这个非原子存储,所以我知道编译器会在第二个之前做第一个。

其次,硬件可以重新排序商店和负载;这通常是处理器缓存策略的结果,被称为“可见性”;对一个线程中的变量所做的更改不一定对另一个在第一个线程写入之后读取该变量的线程可见。那是因为两个线程可以在两个独立的处理器上运行,每个处理器都有自己的缓存;如果新值未写入主内存,或者其他处理器的缓存中有旧值,则第二个线程将看不到更改。 Atomics提供了有关值何时变为可见的规则(这转换为何时必须将写入从高速缓存刷新到主存储器中以及何时读取必须转到主存储器而不是高速缓存[过度简化,但您明白了]);这就是这个例子的意义所在。并且,正如@Michael所说,仅仅因为价值不必显示,并不意味着它不可能。有些处理器具有弱内存模型,允许这种情况,在分析它们的功能时可能会提高速度和明确的复杂性,而某些处理器则不然。 x86属于后一类:即使您允许较弱的可见性约束,您所做的每件事都将按顺序进行。

答案 2 :(得分:0)

仅仅因为可以重新排序x和y并不会迫使编译器生成不确定的行为。

x.store(false, memory_order_relaxed); // redundant store
y.store(true, memory_order_relaxed);
x.store(true, memory_order_relaxed);

看来如果我们看到x为真,那么y必须为真。但是,编译器可能会选择重新排序

x.store(false, memory_order_relaxed); // redundant store
x.store(true, memory_order_relaxed);
y.store(true, memory_order_relaxed);

会选择吗?在这种情况下,可能不是。编译器很容易在xyx模式中生成最佳代码。但是,在更复杂的情况下,随着更多事情的发生,重新排序可能允许编译器在寄存器中容纳更多值。

答案 3 :(得分:0)

内存模型只是描述了可能的内容,并不能保证实际发生排序。它取决于编译器和硬件。如果您想详尽地探索允许的行为,请使用像CDSChecker这样的工具。