深入理解C11/C++11内存模型

现代计算机体系结构上 , CPU执行指令的速度远远大于CPU访问内存的速度 , 于是引入Cache机制来加速内存访问速度 。 除了Cache以外 , 分支预测和指令预取也在很大程度上提升了CPU的执行速度 。 随着SMP的出现 , 多线程编程模型被广泛应用 , 在多线程模型下对共享变量的访问变成了一个复杂的问题 。 于是我们有必要了解一下内存模型 , 这是多处理器架构下并发编程里必须掌握的一个基础概念 。
1. 什么是内存模型?
到底什么是内存模型呢?看到有两种不同的观点:

  • A:内存模型是从来描述编程语言在支持多线程编程中对共享内存访问的顺序 。
  • B:内存模型的本质是指在单线程情况下CPU指令在多大程度上发生指令重排(reorder)[1] 。
实际上A , B两种说法都是正确的 , 只不过是在尝试从不同的角度去说明memory model的概念 。 个人认为 , 内存模型表达为“内存顺序模型”可能更加贴切一点 。
一个良好的memory model定义包含3个方面:
  • Atomic Operations
  • Partial order of operations
  • Visable effects of operations
这里要强调的是:我们这里所说的内存模型和CPU的体系结构、编译器实现和编程语言规范3个层面都有关系 。
首先 , 不同的CPU体系结构内存顺序模型是不一样的 , 但大致分为两种:
深入理解C11/C++11内存模型文章插图
x86_64和Sparc是强顺序模型(Total Store Order) , 这是一种接近程序顺序的顺序模型 。 所谓Total , 就是说 , 内存(在写操作上)是有一个全局的顺序的(所有人看到的一样的顺序) ,就好像在内存上的每个Store动作必须有一个排队 , 一个弄完才轮到另一个 , 这个顺序和你的程序顺序直接相关 。 所有的行为组合只会是所有CPU内存程序顺序的交织 , 不会发生和程序顺序不一致的地方[4] 。 TSO模型有利于多线程程序的编写 , 对程序员更加友好 , 但对芯片实现者不友好 。 CPU为了TSO的承诺 , 会牺牲一些并发上的执行效率 。
弱内存模型(简称WMO , Weak Memory Ordering) , 是把是否要求强制顺序这个要求直接交给程序员的方法 。 换句话说 , CPU不去保证这个顺序模型(除非他们在一个CPU上就有依赖) ,程序员要主动插入内存屏障指令来强化这个“可见性”[4] 。 ARMv8 , PowerPC和MIPS等体系结构都是弱内存模型 。 每种弱内存模型的体系架构都有自己的内存屏障指令 , 语义也不完全相同 。 弱内存模型下 , 硬件实现起来相对简单 , 处理器执行的效率也高 ,只要没有遇到显式的屏障指令 , CPU可以对局部指令进行reorder以提高执行效率 。
对于多线程程序开发来说 , 对并发的数据访问我们一般到做同步操作 ,可以使用mutex , semaphore , conditional等重量级方案对共享数据进行保护 。 但为了实现更高的并发 , 需要使用内存共享变量做通信(Message Passing) ,这就对程序员的要求很高了 , 程序员必须时时刻刻必须很清楚自己在做什么 ,否则写出来的程序的执行行为会让人很是迷惑!值得一提的是 , 并发虽好 , 如果能够简单粗暴实现 , 就不要搞太多投机取巧!要实现lock-free无锁编程真的有点难 。
其次 , 不同的编程语言对内存模型都有自己的规范 , 例如:C/C++和Java等不同的编程语言都有定义内存模型相关规范 。
2011年发布的C11/C++11 ISO Standard为我们带来了memory order的支持 ,引用C++11里的一段描述:
The memory model means that C++ code now has a standardizedlibrary to call regardless of who made the compiler and onwhat platform it's running. There's a standard way to controlhow different threads talk to the processor's memory.[7]memory order的问题就是因为指令重排引起的, 指令重排导致 原来的内存可见顺序发生了变化, 在单线程执行起来的时候是没有问题的, 但是放到 多核/多线程执行的时候就出现问题了, 为了效率引入的额外复杂逻辑的的弊端就出现了[8] 。
C++11引入memory order的意义在于我们现在有了一个与运行平台无关和编译器无关的标准库 ,让我们可以在high level languange层面实现对多处理器对共享内存的交互式控制 。 我们的多线程终于可以跨平台啦!我们可以借助内存模型写出更好更安全的并发代码 。 真棒 , 简直不要太优秀~
深入理解C11/C++11内存模型文章插图
C11/C++11使用memory order来描述memory model ,而用来联系memory order的是atomic变量 ,atomic操作可以用load()和release()语义来描述 。 一个简单的atomic变量赋值可描述为: