跳到主要内容
  1. 文章/

【内存模型】什么是内存模型

·3 分钟

讨论编程问题的时候,尤其是在并发编程的领域,我们经常看到会「内存模型」这个词,英语通常叫「Memory Model」或「Memory Consistency Model」。理解「内存模型」究竟意味着什么,对于写出高效稳定的计算机程序至关重要。但是,理解「内存模型」并不是一件容易的事情。本系列文章的目的,希望能对「内存模型」这个概念,给出浅显易懂的解释。尤其是帮助在嵌入式开发领域的软件工程师理解、掌握工作中必须要用到的知识。

本篇我们先从一个简单的问题开始,介绍内存模型的概念。

// Core C1
S1: x = NEW;
L1: r1 = y;
// Core C2
S2: y = NEW;
L2: r2 = x;

上面的代码采用了类似 C 语言的伪代码,呈现了在两个处理器核上运行的一些指令。对这里的一些符号和初始条件做一些说明:

  • C1C2 代表处理器核(Core)
  • L1L2 代表读指令(Load)
  • S1S2 代表写指令(Store)
  • xy 代表存储在共享内存中的变量
  • r1r2 代表存储在处理器寄存器中的变量
  • 所有变量的初始值都是 0
  • NEW 代表一个非 0 常量

(在系列的后面文章中,都会用类似的表示方法,出现时将不再对此赘述。)


问题来了:

程序运行后,(r1, r2) 的值可能是 (0, 0) 吗?

这个问题看似很简单,一般的推理是这样的:

  1. r1 == 0 意味着 y == 0
  2. y == 0 意味着 y = NEW 尚未执行
  3. 所以 r2 = x 更不可能执行
  4. 所以 (r1, r2) == (0, 0) 是不可能的

这种推理是最符合直觉的,不过,它是正确的吗?答案是——取决于处理器的「内存模型」。上面的推理,其实是需要某种内存模型来保证的。我们最基本的直觉是——先执行的指令,其结果先「被看到」,在硬件层面保证这种关系是最简单的,写代码的人就可以少操心。但是,硬件实现者的手被束缚了。

另一些内存模型下,(0, 0) 这个结果是可能出现的,因为部分内存模型允许重排符合规则的指令,比如这个执行序列:L1 → L2 → S1 → S2 。为什么部分内存模型会允许这样的执行序列呢?主要是为了让硬件的实现者有更大自由度,做更多的优化(优化的原理不是本文重点)。有人自由了,就有人会不自由。程序员感觉世界崩塌了,什么都不可信了。其实,没有完全崩塌,因为指令的重排并不是随机的,而是有一定规则的,并且,当我们识别到有程序需要保证先后顺序的时候,处理器也给了我们工具来实现。

不过无论如何,我们需要学习规则,并使用工具,才能达到目的,编程确实变难了。


到这里,我们对「内存模型」这个概念,已经有了一定的直观认识。对于这样一类程序:

  • 运行在某种 CPU 指令集上
  • 在多个 CPU 上执行多个线程
  • 多个线程对共享内存进行读写

我们会问:

  • 硬件的实现者可以对这些读写指令做哪些优化?规则是什么?
  • 软件的实现者可以信任哪些假设?不能信任哪些假设?如何来保证执行顺序?

澄清以上问题的规则,构成了「内存模型」。「内存模型」让 CPU 设计者、编译器实现者、操作系统开发者、应用软件开发者等角色在统一的规则下工作。

从下一篇开始,我们会逐个介绍几种主流的内存模型。