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_ordermemory order再探。C++17开始consume被弃用,自动升级为acquire。

进一步阅读:

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一致性 (待整理)的更多相关文章

  1. Java内存模型_顺序一致性

    数据竞争: 当程序未正确同步时,就会存在数据竞争.java内存模型规范对数据竞争的定义如下: 在一个线程中写一个变量 在另一个线程读同一个变量 而且写和读没有通过同步来排序 如果程序是正确同步的,程序 ...

  2. 从 CPU 讲起,深入理解 Java 内存模型!

    Java 内存模型,许多人会错误地理解成 JVM 的内存模型.但实际上,这两者是完全不同的东西.Java 内存模型定义了 Java 语言如何与内存进行交互,具体地说是 Java 语言运行时的变量,如何 ...

  3. 再有人问你Java内存模型是什么,就把这篇文章发给他

    前几天,发了一篇文章,介绍了一下JVM内存结构.Java内存模型以及Java对象模型之间的区别.有很多小伙伴反馈希望可以深入的讲解下每个知识点.Java内存模型,是这三个知识点当中最晦涩难懂的一个,而 ...

  4. 深入理解java内存模型

    深入理解Java内存模型(一)——基础 深入理解Java内存模型(二)——重排序 深入理解Java内存模型(三)——顺序一致性 深入理解Java内存模型(四)——volatile 深入理解Java内存 ...

  5. Java 并发系列之三:java 内存模型(JMM)

    1. 并发编程的挑战 2. 并发编程需要解决的两大问题 3. 线程通信机制 4. 内存模型 5. volatile 6. synchronized 7. CAS 8. 锁的内存语义 9. DCL 双重 ...

  6. 02 java内存模型

    java内存模型 1.JVM内存区域 方法区:类信息.常量.static.JIT (信息共享) java堆:实例对象 GC (信息共享) OOM VM stack:JAVA方法在运行的内存模型 (OO ...

  7. 内存模型与c++中的memory order

    概 c++的atomic使用总会配合各种各样的memory order进行使用,memory order控制了执行结果在多核中的可见顺序,,这个可见顺序与代码序不一定一致(第一句代码执行完成的结果不一 ...

  8. JVM内存模型、指令重排、内存屏障概念解析

    在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...

  9. JVM内存模型、指令重排、内存屏障概念解析(转载)

    在高并发模型中,无是面对物理机SMP系统模型,还是面对像JVM的虚拟机多线程并发内存模型,指令重排(编译器.运行时)和内存屏障都是非常重要的概念,因此,搞清楚这些概念和原理很重要.否则,你很难搞清楚哪 ...

  10. Java并发(二):Java内存模型

    一.硬件内存架构 一个现代计算机通常由两个或者多个CPU.其中一些CPU还有多核.每个CPU在某一时刻运行一个线程是没有问题的.如果你的Java程序是多线程的,在你的Java程序中每个CPU上一个线程 ...

随机推荐

  1. PostgreSQL 时间函数分类与特性

    KingbaseES 时间函数有两大类:返回事务开始时间和返回语句执行时的时间.具体函数看以下例子: 1.返回事务开始时的时间 以下函数返回事务开始的时间(通过 begin .. end 两次调用结果 ...

  2. 使用 MAUI 在 Windows 和 Linux 上绘制 PPT 的图表

    我在做一个图表工具软件,这个软件使用 MAUI 开发.我的需求是图表的内容需要和 PPT 的图表对接,需要用到 OpenXML 解析 PPT 内容,读取到 PPT 图表元素的内容,接着使用 MAUI ...

  3. 跨语言调用C#代码的新方式-DllExport

    简介 上一篇文章使用C#编写一个.NET分析器文章发布以后,很多小伙伴都对最新的NativeAOT函数导出比较感兴趣,今天故写一篇短文来介绍一下如何使用它. 在以前,如果有其他语言需要调用C#编写的库 ...

  4. STL堆排序&时间复杂度分析

    1. 逻辑&时间复杂度分析 pop 和 initialize 的时间复杂度请参考: [DSAAinC++] 大根堆的pop&remove&initialize 将数组初始化为一 ...

  5. PHP实践项目【1】:注册登录页面

    在我们这个项目里面,一共用到了5个php文件,他们分别是: login.php 登录页面 logincheck.php 登录检测php文件 register.php 新用户注册页面 regcheck. ...

  6. PHP全栈开发(八):CSS Ⅳ 文本格式及字体

    文本系列属性主要是设置文本格式的,例如.... 颜色 body {color:red;} h1 {color:#00ff00;} p.ex {color:rgb(0,0,255); 可以设置文本的居中 ...

  7. leetcode刷题记录之25(集合实现)

    题目描述: 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表. k 是一个正整数,它的值小于或等于链表的长度.如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原 ...

  8. 斗鱼 H5 直播原理解析,它是如何省了 80% 的 CDN 流量?

    斗鱼直播相信大家都听说过,打开斗鱼官网就可以直接在浏览器中观看直播.那么斗鱼是如何实现浏览器视频直播的呢?本篇文章就来解析斗鱼是如何实现直播的,以及它是如何节省 80% 的 CDN 流量,要知道视频直 ...

  9. Springboot 之 Filter 实现超大响应 JSON 数据压缩

    简介 项目中,请求时发送超大 json 数据外:响应时也有可能返回超大 json数据.上一篇实现了请求数据的 gzip 压缩.本篇通过 filter 实现对响应 json 数据的压缩. 先了解一下以下 ...

  10. Linux-->vi和vim编辑器的基本操作

    vim编辑器介绍 vi或者vim就是对linux下的文本进行编辑的一种编辑器比如说a.cpp文件这种 Linux会内置vi文本编辑器 Vim可以简单的认为vi的增强版 Linux是区分大小写的! 用法 ...