CPU体系(1):内存模型 & CPU Cache一致性 (待整理)
C++中的 volatile, atomic, memory barrier 应用场景对比
| -- | volatile | memory barrier | atomic |
|---|---|---|---|
| 抑制编译器重排 | Yes | Yes | Yes |
| 抑制编译器优化 | Yes | No | Yes |
| 抑制 CPU 乱序 | No | Yes | Yes |
| 保证访问原子性 | No | No | Yes |
- 如果需要原子性的访问支持,只能选择 atomic;
- 如果仅仅只是需要保证内存访问不会被编译器优化掉,优先考虑 volatile;
- 如果需要保证 Memory Order,也优先考虑 atomic,只有当不需要保证原子性,而且很明确要在哪插入内存屏障时才考虑手动插入 Memory Barrier。
存储一致性 vs 缓存一致性
存储一致性(memory consistency),不要跟缓存一致性(cache coherence)混淆了。
缓存一致性协议解决的是对单个存储器地址的访问之间如何排序的问题,而对于不同地址的访问并不是缓存一致性协议所要考虑的问题。存储一致性问题在任何具有或不具有高速缓存的系统中都存在,虽然高速缓存的存在有可能进一步加剧存储一致性问题。
存储器模型(memory model)又称为存储一致性模型。用于定义系统中对存储器访问需要遵守的原则,只要软件和硬件都遵循该原则,就能保证多核程序能运行得到确切的结果。Memory model一致性问题来源于:编译期乱序、执行期乱序,以及Cache不同步。
个人理解:内存模型与CPU Cahce一致性:由于CPU core对store buffer/invalidate queue与对Cache的操作是异步的。对于写操作(release语义),需要等待该cpu core的 store buffer 全部写到Cache,并等待MESI操作同步完成;对于读操作(aquire语义),需要等待该清空该CPU Core的invalidate queue,以保证该CPU Core从内存中读取数据。ref 。

多CPU/Core MESI协议局限性
当一个CPU进行写入时,首先会给其它CPU发送Invalid消息,然后把当前写入的数据写入到Store Buffer中。然后异步在某个时刻真正的写入到Cache中。当前CPU核如果要读Cache中的数据,需要先扫描Store Buffer之后再读取Cache。但是此时其它CPU核是看不到当前核的Store Buffer中的数据的,要等到Store Buffer中的数据被刷到了Cache之后才会触发失效操作。而当一个CPU核收到Invalid消息时,会把消息写入自身的Invalidate Queue中,随后异步将其设为Invalid状态。和Store Buffer不同的是,当前CPU核心使用Cache时并不扫描Invalidate Queue部分,所以可能会有极短时间的脏读问题。MESI协议,可以保证缓存的一致性,但是无法保证实时性。所以我们需要通过内存屏障在执行到某些指令的时候强制刷新缓存来达到一致性。
STL定义的 memory models
| value | memory model | description |
|---|---|---|
| memory_order_relaxed | Relaxed |
没有同步或顺序制约,仅对此操作要求原子性 |
| memory_order_consume | Consume |
1. 对当前要读取的内存施加 release 语义(store),在代码中这条语句后面所有与这块内存有关的读写操作都无法被重排到这个操作之前; 2. 在这个原子变量上施加 release 语义的操作发生之后,consume 可以保证读到所有在 release 前发生的并且与这块内存有关的写入 |
| memory_order_acquire | Acquire |
1. 向前保证,本线程中所有读写操作都不能重排到memory_order_acquire的load之前; 2. 其他线程中所有memory_order_release的写操作都对当前线程可见 |
| memory_order_release | Release |
1. 向后保证,本线程中所有读写操作都不能重排到memory_order_acquire的store之后; 2. 本线程中的所有写都对其他对同一atomic变量带有 memory_order_acquire的线程可见; 3. 本线程中的所有写都对其他所有有依赖且consume该变量的线程可见 |
| memory_order_acq_rel | Acquire/Release |
1. 是release+acquire的结合,前后都保证,本线程中所有读写操作既不能重排到memory_order_acquire的load之前也不能到之后; 2. 其他线程的memory_order_release写在本线程写之前都是可见的; 3. 本线程的的写对其他线程的memory_order_acquire读都是可见的 |
| memory_order_seq_cst | Sequentially consistent |
1. 顺序一致性; 2. 如果是读取就是 acquire 语义,如果是写入就是 release 语义,如果是读取+写入就是 acquire-release 语义; 3. 同时会对所有使用此 memory order 的原子操作进行同步,所有线程看到的内存操作的顺序都是一样的,就像单个线程在执行所有线程的指令一样 |

常使用有三种情形:relaxed order,release/acquire,cst。std::memory_order, memory order再探。C++17开始consume被弃用,自动升级为acquire。
进一步阅读:
- std::memory_order
- Sequential Consistency,Cache-Coherence及Memory barrier
- ARM系列 - - 存储模型(一)
- 处理器设计 -- 顺序与乱序
- Memory Model: 从多处理器到高级语言
- 内存模型与c++中的memory order
- CPU多级缓存
- 從硬體觀點了解 memory barrier 的實作和效果
ARM fence 指令
- 数据存储器隔离指令 DMB。指令保证:仅当所有在它前面的存储器访问操作都执行完毕后,才提交(commit)在它后面的存储器访问操作。
- 数据同步隔离指令 DSB。比DMB严格:仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访问操作——译者注)
- 指令同步隔离 ISB。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令。
CPU指令乱序来自流水线中的发射(Issue)/执行(取指、解码、发射/执行、取数、写回)阶段,其他阶段都是顺序DMB、DSB 和 ISB指令的深度解读。
out-of-order的CPU以及有多个pipeline的CPU,都会产生指令执行乱序。更多资料:
C++11 的单例模式线程安全
class A {
public:
static A* instance() {
static A* ptr = new A();
return ptr;
}
};
int main(int argc, char *argv[]) {
auto ptr = A::instance();
return 0;
}
使用Compiler Explore查看,插入了一个guard variable标记是否初始化;
并且调用guard aquire/release 保证初始化的原子性。
参考
CPU体系(1):内存模型 & CPU Cache一致性 (待整理)的更多相关文章
- Java内存模型_顺序一致性
数据竞争: 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据竞争的定义如下: 在一个线程中写一个变量 在另一个线程读同一个变量 而且写和读没有通过同步来排序 如果程序是正确同步的,程序 ...
- 从 CPU 讲起,深入理解 Java 内存模型!
Java 内存模型,许多人会错误地理解成 JVM 的内存模型.但实际上,这两者是完全不同的东西.Java 内存模型定义了 Java 语言如何与内存进行交互,具体地说是 Java 语言运行时的变量,如何 ...
- 再有人问你Java内存模型是什么,就把这篇文章发给他
前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而 ...
- 深入理解java内存模型
深入理解Java内存模型(一)——基础 深入理解Java内存模型(二)——重排序 深入理解Java内存模型(三)——顺序一致性 深入理解Java内存模型(四)——volatile 深入理解Java内存 ...
- Java 并发系列之三:java 内存模型(JMM)
1. 并发编程的挑战 2. 并发编程需要解决的两大问题 3. 线程通信机制 4. 内存模型 5. volatile 6. synchronized 7. CAS 8. 锁的内存语义 9. DCL 双重 ...
- 02 java内存模型
java内存模型 1.JVM内存区域 方法区:类信息.常量.static.JIT (信息共享) java堆:实例对象 GC (信息共享) OOM VM stack:JAVA方法在运行的内存模型 (OO ...
- 内存模型与c++中的memory order
概 c++的atomic使用总会配合各种各样的memory order进行使用,memory order控制了执行结果在多核中的可见顺序,,这个可见顺序与代码序不一定一致(第一句代码执行完成的结果不一 ...
- JVM内存模型、指令重排、内存屏障概念解析
在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...
- JVM内存模型、指令重排、内存屏障概念解析(转载)
在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...
- Java并发(二):Java内存模型
一.硬件内存架构 一个现代计算机通常由两个或者多个CPU.其中一些CPU还有多核.每个CPU在某一时刻运行一个线程是没有问题的.如果你的Java程序是多线程的,在你的Java程序中每个CPU上一个线程 ...
随机推荐
- Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点
Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...
- Docker_删除none镜像
Docker 删除 none 镜像 docker images|grep none|awk '{print $3}'|xargs docker rmi
- python3实现:进程遇Error定时重启
import os import time # 停止HFish def stopHFish(): # while True: try: # 找到HFish进程号 HFish_id = int(os.p ...
- day40-网络编程02
Java网络编程02 4.TCP网络通信编程 基本介绍 基于客户端--服务端的网络通信 底层使用的是TCP/IP协议 应用场景举例:客户端发送数据,服务端接收并显示控制台 基于Scoket的TCP编程 ...
- vue3 的 ref、isRef、toRef、toRefs、toRaw 详细介绍
ref.isRef.toRef.toRefs.toRaw 看着一堆类似的东西,一个头两个大,今天整理一篇文章详细介绍它们的功能及区别. 1.ref ref 属性除了能够获取元素外,也可以使用 ref ...
- PAT (Basic Level) Practice 1008 数组元素循环右移问题 分数 20
一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A0A1⋯AN−1)变换为(AN−M⋯AN−1A0A1⋯AN ...
- 「国产系统」Tubian 0.1,兼容Windows和Android的GNU/Linux系统!
Tubian 0.42已发布:https://www.cnblogs.com/tubentubentu/p/16745926.html Tubian是我的自用系统整理而成的Linux发行版,基于Deb ...
- 关于aws的ec2实例导出成ova后在vmware中的网络配置不生效的问题
在aws上的ec2实例,尤其是使用了aws市场中的ami创建的linux系统,默认情况下,网络配置都是通过dhcp自动获取的, 这周笔者将一台ec2实例(redhat/linux 8.3)导出/转换成 ...
- Node.js躬行记(24)——低代码
低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台.让具有不同经验水平的开发人员可以通过图形化的用户界面,通过拖拽组件和模型驱动的逻辑来创建网页和移动应用程序 ...
- go-zero docker-compose搭建课件服务(四):生成Dockerfile
0.转载 go-zero docker-compose 搭建课件服务(四):生成Dockerfile并在docker-compose中启动 0.1源码地址 https://github.com/liu ...