c - c 11一个标准化的内存模型 它是什么意思呢 以及会是如何影响C 编程了?

  显示原文与译文双语对照的内容

C++11引入了标准化的内存模型,但这到底意味着什么? 它将如何影响 C++ 编程?

Herb Sutter说在这里

内存模型意味着 C++ 代码现在有一个标准化的库,而不管谁做了编译器以及它运行的平台。 有一种标准的方法来控制不同线程与处理器内存的交谈方式。

"当你在讨论在标准中的不同内核之间拆分 [code] 时,我们正在讨论内存模型。 我们将优化它,而不破坏以下假设人们将在代码中做出的假设"Sutter说。

嗯,我可以记住这个和类似的段落可以在线上( 因为我有自己的记忆模型诞生:) 和甚至可以由他人文章作为回答的问题询问,但老实说,我不完全理解这一点。

所以,我基本上想知道是什么,过,所以 C++ 程序员用来开发multi-threaded应用程序,甚至有关系如果它poxis线程,或者,或者 C++11 Windows 线程线程如何? 什么好处我想理解底层的细节。

我也觉得C++11内存模型与 C++11 multi-threading支持有某种关系,因为我经常看到这两个。 如果是的话? 他们为什么要相关?

因为我不知道multi-threading内部的工作原理,以及一般的内存模型是什么意思,请帮助我理解这些概念。 :- )

时间:

首先,你必须学会像语言律师一样思考。

C++ 规范不引用任何特定的编译器,操作系统或者 CPU 。 它引用了一个抽象机器,它是实际系统的一般化。 在语言律师世界中,程序员的工作是为抽象机器编写代码;编译器的工作是在具体机器上实现该代码。 在now,通过编码刚性固定在其他技能,你可以确定,你的代码可以编译并运行无需修改即可在任何系统上使用兼容的编译器,不管是今天还是 50 C++ 几年里

c++98/c++03规范中的抽象机器基本上是 single-threaded 。 因此,无法编写与规范相对应的multi-threaded C++ 代码。 规范甚至不说任何有关该 原子性的内存加载和存储或者 的顺序加载和存储在其中,并且不重视诸如互斥锁也不会例外。

于某种具体的系统相关--如pthreads或者当然,你可以写multi-threaded代码在实际工作中应用 于 c++98/但没有相关标准方式导致multi-threaded写的代码

C++11中的抽象机器通过设计实现。 它还有一个定义良好的内存模型 ;也就是说,它说编译器在访问内存时可能会做什么。

考虑以下示例,其中两个线程同时访问一对全局变量:


 Global
 int x, y;

Thread 1 Thread 2
x = 17; cout <<y <<"";
y = 37; cout <<x <<endl;

线程 2的输出可能是什么?

在 c++98/c++03下,这甚至不是未定义的行为;问题本身是无意义的,因为标准没有考虑任何所谓的"线程"。

在C++11下,结果是未定义的行为,因为负载和存储在一般情况下不需要原子。 这似乎并不是什么进步。 它本身并不是。

但是使用 C++11,你可以编写:


 Global
 atomic<int> x, y;

Thread 1 Thread 2
x.store(17); cout <<y.load() <<"";
y.store(37); cout <<x.load() <<endl;

现在事情变得更有趣了。 首先,这里的行为是定义的 。 线程 2现在可以打印 0 0 ( 如果在线程 1之前运行),37 17 ( 如果在线程 1之后运行) 或者 0 17 ( 如果线程 1分配给,但在它分配给之前) 。

无法打印的是 37 0,因为C++11中的原子加载/存储的默认模式是强制执行顺序一致性 。 这只意味着所有的装入和存储必须每个线程内"如同一样"他们命令你把那些句子内部发生的,而操作系统线程间可以但在测量过程中交错喜欢。 于加载和原子能公司同时提供原子数 stores,所以默认行为,以及相关订购.

现在,在一个现代的CPU上,确保顺序一致性很昂贵。 特别是,编译器可能会在每个访问之间产生成熟的内存屏障。 但是如果你的算法可以容忍out-of-order加载和存储;换句话说,如果它需要原子,而不是排序,那么 换句话说,如果它能容忍来自这个程序的输出,那么你可以编写:


 Global
 atomic<int> x, y;

Thread 1 Thread 2
x.store(17,memory_order_relaxed); cout <<y.load(memory_order_relaxed) <<"";
y.store(37,memory_order_relaxed); cout <<x.load(memory_order_relaxed) <<endl;

CPU越现代,它的速度就越快。

最后,如果只需要保持特定的加载和存储顺序,你可以编写:


 Global
 atomic<int> x, y;

Thread 1 Thread 2
x.store(17,memory_order_release); cout <<y.load(memory_order_acquire) <<"";
y.store(37,memory_order_release); cout <<x.load(memory_order_acquire) <<endl;

这将我们返回到有序加载并存储--所以 37 0 不再是一个可能的输出 --,但它的开销很小。 ( 在这个简单的例子中,结果与完全的顺序一致性相同;在较大的程序中,它不会是这样的。)

当然,如果你想看到的输出是 0 0 或者 37 17,你可以在原始代码周围封装一个互斥。 但是,如果你已经阅读了这么多,我打赌你已经知道了如何工作,这个答案已经超过了我预期的时间:- ) 。

所以,底线是互斥体很棒,C++11标准化了它们。 但出于性能原因,你需要lower-level原语( e.g,经典 double-checked锁定模式 ) 。 新的标准提供了诸如互斥和条件变量之类的高级工具,它还提供了低级的小工具,比如原子类型和各种类型的内存屏障。 现在,你可以完全在标准指定的语言中编写复杂的高性能并发例程,并且你可以确定代码将在今天和将来的两个系统上编译和运行。

尽管如此,除非你是一个专家,并且在处理一些严重的低级代码时,你应该坚持互斥和条件变量。 这就是我想要做的。

有关这些内容的更多信息,请参见这篇博文。

这意味着标准现在定义了 multi-threading,它定义了在多个线程上下文中发生了什么。 使用的是 home-rolled string class,当然,很多人使用不同的实现,但这就像问为什么我们应该有一个 std::string 当我们都所有 be. 当你谈到POSIX线程或者 Windows 线程时,这一点有点迷惑,因为实际上你正在谈论x86线程,因为它是一个硬件函数,可以同时运行。 C++0x内存模型保证了保证,不管你是在 x86,ARM还是 Mips,或者其他任何你可以想到的。

这现在是一个 2 -year老问题,但非常流行,它值得一个神奇的资源来学习C++11内存模型。 我认为在总结他的演讲中没有一点意义,为了让这又是一个完整的答案,但我认为这是他真正写的标准,我认为值得看的是谈话。

Herb有一个 3小时的C++11内存模型,标题为"原子 <> 武器",在Channel9站点上可用- 部件 1第 2部分。 讨论非常技术性,涵盖以下主题:

  1. 优化,争用和内存模型
  2. 排序- 什么:获取和释放
  3. 排序- 如何:互斥,原子和/或者围墙
  4. 编译器和硬件的其他限制
  5. 代码生成&性能:x86/x64,IA64,电源,ARM
  6. 放松原子

谈话并没有详述 API,而是在推理,背景,幕后和幕后的( 你知道宽松语义被添加到标准仅仅是因为电源和Arm不支持同步负载) 。

...