深入探索并发编程系列(九)-弱/强内存模型

存在许多种内存乱序注1,但它们发生的频率并不都是一样的。这取决于你的目标处理器以及开发工具链。

对于特定的处理器和工具链,内存模型能告诉你的源代码在运行时可能会发生哪种类型的内存乱序。在脑海中一定要时刻记住,只有在使用无锁编程技术时,内存乱序的效果才能显现出来。

学习了一段时间的内存模型后(大部分是通过阅读网上的资源并进行实验来验证),我将它们概括为以下四种类型。下图中,每种内存模型都囊括涵盖它左边的内存模型所能提供的所有保证,并提供了更多的保证注2。 根据大部分使用弱内存模型与强内存模型这些术语的方式,我在它们之间划了一条明显的界限。继续往下读就能知道我为什么这么做了。

上图中每个物理设备代表一种硬件内存模型。硬件内存模型能告诉你对于汇编(或者机器)代码在运行期间会出现哪种内存执行顺序

对于内存乱序情况,每种处理器家族都有不同的处理习惯,而那些习惯只有在多核或多处理器的配置环境下才能显现出来。考虑到现在的主流都是多核的,它们之间会有很多相似性。

同样也存在许多软件内存模型。从技术上来说,只要你用C11,C++11或Java编写(调试)代码,与之关联的是软件内存模型。尽管如此,对硬件内存模型的一般理解也迟早会派上用场。它能帮助你解释调试过程中不可预料的行为(可能正因为它很重要),能识别错误代码在不走运的情况下是如何在特定处理器与工具链上运行正确的。

弱内存模型

在弱内存模型中,有可能会出现我在上一篇文章中用资源控制类比来描述的所有四种内存乱序. 只要不改变单个独立线程的行为,任何的读/写操作与其它的读/写操作都有可能发生乱序。实际上,编译器或者处理器都可能产生乱序。

当处理器是弱硬件内存模型时,我们更倾向于说它是weakly orderedweak ordering。 我们也可以说它有个宽松的内存模型。提到弱内存模型处理器,令人敬仰的DEC Alpha处理器是人人爱用的例子。现在主流的处理器中,没有比它更弱的内存模型了。

受Alpha多方面的影响,C11与C++11编程语言是一种弱的软件内存模型。如果你正在用x86/64等强类型处理器家族,当在这些语言中使用底层的原子操作时并不会受到影响注3。我之前说过,只要是为了阻止编译器乱序,你必须要指定正确的内存执行顺序限制.

带有数据依赖执行顺序的弱内存模型

随着时间变化,尽管Alpha已经变得无关紧要了,现代的CPU家族仍有保留了一些会执行弱硬件执行顺序的传统:

  • ARM 成千上万的智能手机和平板电脑都在使用,在多核环境下变得越来越受欢迎
  • PowerPC xbox 360已经在多核环境下贡献了七千万的生存空间
  • Itanium 微软在Windows下不再支持,但在HP服务器上仍能被Linux支持

除了程序员特别感兴趣的常识细节也就是它们能保持数据依赖执行顺序这点之外,这些处理器家族在不同的方式上都和Alpha有着一样弱的内存模型。这是什么意思呢?意味着如果你用C/C++写A->B,你能一直确保B的值至少和A的值一样新。Alpha却不能确保注4。在这里我不过多叙述这种数据依赖执行顺序,只提一提Linux RCU机制非常依赖这个性质。

强内存模型

我们首先来看看硬件内存模型。强内存模型与弱内存模型最大的差别在哪里呢?在这个问题上大家可能会有一些分歧,但我的感觉是80%的场合下,人们说的都是同一件事。因此,我给出下面这个定义:

强内存模型意味着每个机器指令都能隐式的包含Acquire与Release语义。结果是,当一个CPU核执行一系列的写操作时,其它的每一个CPU核看见的都是那些值都以它们被写入时的顺序在改变。

这些都不难理解。只要想象资源控制类比中的核心观点,所有的修改有序地提交给共享内存(没有写写乱序),有序地从共享内存取出(没有读读乱序),并且指令总有序的执行(没有读写乱序)。然而写读乱序还是有可能发生。

从上述的定义可以看出,x86/64处理器家族一向是强内存模型(strong ordered)的。有时候有例外,但在大部分情况下,对于程序员来说,这些例子都可以忽略。x86/x64处理器能乱序执行指令,但那属于硬件实现的范畴。而真正有意义的是它仍能保证内存的交互是有序的,因此在多核环境下,我们仍然可以把它认为是强内存模型的。从历史角度来看,标准在不断地发展,可能会给大家带来一些困扰。

很明显,当SPARC处理器运行在TSO模式中,是另一种强硬件内存模型的例子。TSO代表Total Store Order,相比我上面给出的定义来看,这是一种微妙的方式,意味着对所有cpu核来说,关于写内存操作,都有一个单一确定的全序注5。X86/X64也有这个性质,可以参考Intel x86/x64系统结构手册中Volume 3, §8.2.3.6-8的例子。从那里我可以得出结论,TSO性质不总是lock-free程序员对底层感兴趣的最直接部分,但这在对顺序一致性的理解上又迈进了一步。

顺序一致性

在顺序一致性内存模型中,没有内存乱序。这看起来就像是整个程序的执行被缩减到每个线程中指令的顺序交错。具体来说,这篇文章中r1 = r2 = 0 的结果就变得不可能了注6

如今你很难找到一个现代的多核设备能在硬件级别保证顺序一致性。然而,似乎在1989年至少有一种能保证顺序一致性的多处理器机器:基于386的Compaq SystemPro。根据Intel文档,386还不够先进到能在运行期间执行任意的内存乱序。

在任何情况下,当使用高级编程语言,顺序一致性只有在作为软件内存模型时才变得有趣。在Java5或更高版本中,你可以将共享变量声明为volatile。在C++11中,当在原子数据类型执行操作的时候,可以使用默认的顺序约束,也就是memory_order_seq_cst注7。如果这么做了,工具链会限制编译器乱序并发出CPU特定的指令来充当合适的memory barrier。在这种方式下,尽管是在弱内存模型的多核设备上,也会模拟出顺序一致性的内存模型。如果你读了Herlihy & Shavit’s的 The Art of Multiprocessor Programming,要注意那里的大部分例子都假设顺序一致性的软件内存模型。

更多细节

要填完有关内存模型的坑,还有许多其它微妙的细节,但以我的经验来看,当在应用层上写无锁代码时,这些都变得没什么意思了。存在一些控制依赖,因果一致性和不同的内存乱序类型。然而,大部分的讨论都能归于我上面总结的四种主要类型里。

如果你真的想探究处理器内存模型的细节,并且你能在吃早餐的时候还想琢磨一些正式的逻辑,你可以看看剑桥大学的非常精细的工作
Paul McKenney写了它们的工作和相关工具的一些概览注8

译者注

注1:比如说写写乱序、写读乱序、读读乱序、读写乱序等等。

注2:也就是说从右到左模型越来越弱,限制越来越松,乱序发生的越来越多。

注3:即使在X86/X64下,还是得注意可能的写读乱序。

注4:举个关于data dependency的例子,来说明在DEC Alpha下编程有多么的困难和tricky。

1
2
3
4
5
6
7
8
9
10
11
12
13
初始化
int A = 1;
int B = 2;
int C = 3;
int *P = &A;
int *Q = &B;
//cpu1
B = 4;
CPU_BARRIER();
P = &B;
//cpu2
Q = P;
D = *Q

从直觉上说,Q最后要么等于&A,要么等于&B。也就是说:

Q == &A, D == 1

或者

Q == &B, D == 4

但是,让人吃惊的是,DEC Alpha下,可能出现

Q == &B, D == 2的情形,原因是,虽然CPU1按照顺序执行完两条语句先后对B和P进行了修改,但CPU2可能先感知到P的变化,读到了P的新值,然后才是B的新值。因此,为了防止这样的问题,需要加一个data dependency barrier。

注5:偏序、全序什么意思?读者诸君如果忘记,可以简单复习下大学的离散数学教材。

注6:对顺序一致性(Sequential Consistency)感兴趣的读者,可以参考我们写的这篇博客。

注7:C++11引入的feature。在C++11中,除了这里提到的std::memory_order_seq_cst之外,还有如下的内存序选项:

1
2
3
4
5
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,

关于它们的语义和用法,请继续关注我们的深入探索并发编程系列,或者参考《C++ Concurrency In Action》这本书第五章。

注8:谈到C++ memory model,很难绕开一个大牛:Mark John Batty。对C++11的memory model感兴趣的读者,如果想深入研究其中的理论和形式化部分,可以参考他的论文

Acknowledgement

本文由 Diting0x睡眼惺忪的小叶先森 共同完成,在原文的基础上添加了许多精华注释,帮助大家理解。

感谢好友小伙伴-小伙伴儿 阅读了初稿,并给出宝贵的意见。

原文: http://preshing.com/20120930/weak-vs-strong-memory-models/

本文遵守Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0)
仅为学习使用,未经博主同意,请勿转载
本系列文章已经获得了原作者preshing的授权。版权归原作者和本网站共同所有

攒点碎银娶媳妇