3

我在 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

4 回答 4

8

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

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

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

于 2012-08-31T21:52:36.460 回答
3

关于原子、内存排序和测试的一些事情。

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

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

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

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

于 2012-09-01T12:11:34.077 回答
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 模式生成最佳代码。然而,在更复杂的情况下,随着更多的事情发生,重新排序可能允许编译器在寄存器中容纳更多的值。

于 2013-09-03T05:28:49.113 回答
0

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

于 2013-09-03T05:51:32.503 回答