无锁编程教程:强内存模型与弱内存模型


内存重排有非常多种类,但是并不是所有的内存重排都会经常发生。这决定于你是用的处理器类型以及你是用的工具链(toolchain)

一个内存模型告诉你,对于一个指定的处理器或工具链,究竟哪种类型的内存重排可能出现。需要时刻警醒,内存重排仅仅会在多线程无锁编程中才会显现。

经过一段时间的研究——查阅网上的资料以及亲手实验,我成功的将他们总结成为如下4种类型。在下图中,对于所有右边的模型,不仅满足其左侧模型的所有要求,并且还更近一步。通过图示,我们可以画出一条清晰地界限指定强/弱内存模型。

上图中,所有的实际的硬件类型都对应了一个硬件内存模型,而硬件内存模型又对应了一个内存重排的严格性等级。

在内存重排方面,不同类别的处理器表现不尽相同。不过这些表现只有在多核条件下才能表现出来,鉴于多核是当今的一个主流趋势,所以我们需要对这些内存顺序问题有所理解。

有了硬件内存模型,我们也有软件内存模型。技术上来说,当你编写并调试可移植的C11,C++11或Java的无锁代码,你只需要关心软件内存模型。但是了解了硬件内存模型,你可以知道为什么一个错误的代码在某些处理器上运行毫无问题。

弱内存模型

在一个弱的内存模型中,我们可能遇到所有的四种内存重排。这些重排我在之前的文章中已经讲解过了,在不影响单线程程序的情况下,任何的读写操作都会有概率互相交换顺序,这些重排源于编译器和处理器的共同作用。

当一个处理器是弱内存模型,我们倾向于说是“弱有序(weakly-ordered)”,我们又是也会称之为relaxed 内存模型.弱内存模型的一个经典的例子是DEC Alpha. 不过主流的处理器都不是弱有序的。

C11和C++11所描述的弱内存模型在很多方面都是收Alpha影响的,当我们在这些语言中使用底层原子操作时,即便你使用了 x86/64这一类强有序的硬件,你也应该保证正确的内存顺序来保证编译器正确处理编译期间的内存重排。

保证数据依赖性的弱有序模型

虽然Alpha现在已经不再流行了,我们仍然有若干的线代CPU模型继承了弱有序硬件模型的性质

  • ARM,实现中很多手机、平板的处理器,在多核环境下也越来越普及
  • PowerPC,以Xbox 360为典型,也在生活中普及
  • Itanium,现今已经不被Window支持维护,但是仍然被Linux支持,存在于一些HP服务器端。

这些类别对应的内存模型,在很多方面都和Alpha类似,但是他们抱着了数据依赖性(data dependency ordering)。具体来说,如果你在C/C++中写了A->B,那么你永远保证对于B的读取一定至少比A要新,这是Alpha所不能霸主的。对于数据依赖性,我不在这里深究了。

强内存模型

先让我们看看硬件内存模型,强内存模型和弱内存模型的区别是什么呢?对于这个问题,确实有一些争议。但是就我的感觉而言,对于80%的情况,大多数人都有共同的认识。归纳如下——

strong hardware memory model is one in which every machine instruction comes implicitly with acquire and release semantics. As a result, when one CPU core performs a sequence of writes, every other CPU core sees those values change in the same order that they were written.

强硬件内存模型是指,硬件的所有指令都保证了 acquire和release语义。这意味着,当处理器核心进行了一系列写操作,其他处理器核心看到值的改变的一定符合相同的顺序。

这不难通过版本控制的例子想象出来,所有的修改都向共享内存以指定顺序渗透(没有#StoreStore重排),而所有的读取也按照固定顺序(没有#LoadLoad重排),指令的处理也都按照顺序执行(没有#LoadStore重排),在此基础上#StoreLoad仍然是可能的。

在上述的定义下,x86/64系列处理器通常是强有序的,但是在一些特定情况下,这个强有序的保证是不存在的,不过对于应用编写者,我们完全可以忽视这些情况,处理器确实会乱序执行指令,但是这仅仅是处理器内部的问题,重要的是即使如此,所有的内存指令仍然是有序的,因此在多核环境中,我们仍然将其视为强有序的。

运行在TSO模式下的SPARC处理器是另一个强硬件有序的例子,TSO意思是储存全有序(total store order),换句话说,对于向内存的写操作,所有核心有一个唯一的全局的顺序,x86/64也保证了这个性质。从我知道的来看,TSO性质并不被程序员们关注,但是这确实是想顺序一致性又进了一步。

顺序一致性

在顺序一致性内存模型中,内存重排不复存在,整个程序多个线程的指令可以看做不在相交,这时,之前例子中r1 = r2 = 0的情况将不复存在。

现在,想要找到一个多核设备保证了顺序一致性并不容易,不过回顾历史,在1989年,386系列的 Compaq SystemPro 从Intel手册上来看,并没有足够“先进”以至于能够引入运行时内存重排。

顺序一致性一般而言都是作为一个软件内存模型存在的,在编写高级语言时,比如Java5以上你可以定义volatile,在C++11,你可以使用缺省的memory_order指示符,memory_order_seq_cst来实现顺序一致性。倘若你读过Herlihy & Shavit的 多核编程的艺术,注意他们的大多数例子都是基于顺序一致性的。

更多

关于内存模型,还有很多重要的细节,但是在我看来都在工程中不那么重要,比如control dependencies, causal consistency, and different memory types。而对他们的大部分研究都会回到我之前说过的四种组合。

如果你真的希望深入研究,你可以看看剑桥大学的admirably detailed work,以及Paul McKenney的accessible overview 。

 

原创文章地址:【无锁编程教程:强内存模型与弱内存模型】,转载时请注明出处mhy12345.xyz

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.