C++11——内存模型

[TOC]
# 零、 前言  
  C++11早就来了,各种特性,其中影响最大的一个(我个人觉得)是多线程。  
  早早的去接触了线程相关的一些特性。而其中的一个子特性就是原子操作。而想要深刻理解原子操作,则必须要深刻认识`C++内存模型`。而前前后后18遍,都是以“战败”告终——都是囫囵吞枣,不得其法。  
  最近看`线性一致性`,突然有想起了这个“硬茬”,而又想再战一轮。  
  而又恰好,看到了一篇文章[漫谈C++11多线程内存模型](https://blog.csdn.net/cszhouwei/article/details/11730559),对我颇具启发,对其有了更深的理解,大约也看到了点`C++内存模型`真面目。  
  下面,我也来聊聊我的认识、看法。(下面的全是个人看法,所以,肯定有错漏、谬误,如能指正,不胜感激)
# 一、 战前准备(预备知识)  
  要理解内存模型,我觉得首先要理解下计算机处理结果——`CPU`、`Register(寄存器)` 是怎么工作的。因为`C++内存模型`解决的根本问题就是CPU访问Register的控制。  
  关于这块,我也只能简单说下我的认识,具体的细节,请自行找资料。  
  这里,简化为“CPU访问数据,从Register中访问”(内存什么的就先不考虑了,毕竟,`原子`操作主要针对的是Register的数据。另外,内存,其实也是类似的) 
  
  另外,这里是多核处理器,每个核心简称为PC,每个PC都有自己的一套寄存器。
## 例子
  假设对某变量A进行操作P,在P之前有N个操作pbs;在P之后有M个操作pas。  
  正常情况下(不做任何处理),执行顺序是:pbs->P->pas。  
  但是,在`多核心`、`多线程`的并行时代下面会有两个`但是`:
- 一号但是:寄存器中的值,并不会实时同步的(写数据是同步到缓存,使得其它PC可以获取;读数据是从Cache中获取,但是并不会每次都从Cache中读取)。  
- 二号但是:编译器、处理器会进行优化。可能会把pbs中的部分操作放到P之后;或者把pas中的部分操作放到P之前。  
  其实,(我个人觉得),内存模型就是解决这两个问题。
  
# 二、 开打
## 0、 内存模型
```C++
enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
};
```

## 1、原子操作
  原子性:一个事情,要么完整的被执行,要么完全不被执行。  
  原子操作:一般情况下,一条语句(C++ 语句),到CPU这里会有多个指令。而这些指令,要么完整的执行,要么完全不被执行——那么这条语句就有原子性。  
  所以,C++中的所有了原子操作,那么这些操作本身都是原子的,不管你用哪个`memory_order`。  

> 我之前一直把`memory_order`与原子操作混在一起了——这是我一直没能理解`C++ 内存模型`的一个很重要的原因。总结如下:    
> - memory_order的目的是用在语句之间的。
> - 原子操作就是原子性的。

## 2、 `一号但是`——不实时同步
  前情提要:在多`核心`的场景下,为了高性能,寄存器中的值并不会实时同步的(写数据是同步到缓存,使得其它PC可以获取;读数据是从Cache中获取,但是并不会每次都从Cache中读取)。  
  这一切的一切都是为了性能——编译器言。
  但是,编译器并没有那么聪明,不能完全知道`程序员`的需求、意图的。那怎么办,性能优化就不做了?——这是因噎废食。完全可以告诉编译器——哪里必须要同步下数据了。于是,`memory_order_acquire`和`memory_order_release`就出现了。  
- memory_order_acquire: 这就是告诉系统,我要获取数据,你把所有的数据都同步下吧(寄存器数据)
  - memory_order_acquire之后,所有都同步最新的了。
  - 隐含保证:memory_order_acquire 之后的语句,不会在memory_order_acquire之前执行(否则就没法保证之后用到的寄存器中的值都是最新的)。
- memory_order_release:就是要系统把之前的所有的数据都写到缓存中去了。
  - memory_order_acquire之前的数据,都写了,可以被其它核心看到了。
  - 隐含保证: memory_order_acquire之前的语句不会在memory_order_acquire之后执行(要保证写到公共缓存中去,其它核心可见)。

## 3、 `二号但是`——不保证顺序性
  上一个情况中,`acquire`,`release`是把读写分开,`memory_order_seq_cst`则相当于把两个功能合并了:  
- memory_order_seq_cst之前的都写缓存(隐含保证:pbs不会在memory_order_seq_cst之后执行)
- memory_order_seq_cst读取到的数据都是最新的(隐含保证:pas不会优化到memory_order_seq_cst之后执行)
> 之前的顺序的例子,其实我们的目标是:pbs->P->pas。这里,就是为了这个目标而奋斗。`memory_order_seq_cst`就很好的达标了。
## 4、其他情况
  还有两个内存模型memory_order_consume/memory_order_acq_rel没找到很好的例子。我也不是特别理解,所以这里就不做探讨了。

# 打完收工
  这次虽然没有将`C++ 内存模型`完全吃透,但是好歹没有完败,多少有点收获。也许,这个以后不会用到,但是,装下B也是不错的。

猜你喜欢

转载自blog.csdn.net/cainiaohhf/article/details/82971975
今日推荐