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. Docker_构建_运行总结

    样例: 构建镜像 build-image-fim-backend.sh echo "开始构建 fim-backend 镜像..." cp -rp ../target/fim-bac ...

  2. Cat Theme

    将博客皮肤设置为: SimpleMemory 插入CSS代码 #EntryTag{margin-top:20px;font-size:9pt;color:gray}.topicListFooter{t ...

  3. 关于在PyCharm中使用虚拟环境

    Python虚拟环境的概念对于管理项目用到的第三方包真是好处多多,所以也想在PyCharm使用虚拟环境. 在这个过程中,遇到很多问题: 第一是使用Python创建虚拟环境,然后在PyCharm创建项目 ...

  4. 使用 Elastic 技术栈构建 K8S 全栈监控 -2: 用 Metricbeat 对 Kubernetes 集群进行监控

    文章转载自:https://www.qikqiak.com/post/k8s-monitor-use-elastic-stack-2/ 操作步骤 git clone https://github.co ...

  5. git pull提示如下信息时候的操作

    执行git pull时提示信息如下: There is no tracking information for the current branch. Please specify which bra ...

  6. 利用curl命令访问Kubernetes API server

    kubectl 通过访问 Kubernetes API 来执行命令.我们也可以通过对应的TLS key, 使用curl 或是 golang client做同样的事. API 请求必须使用 JSON 格 ...

  7. 影响 erp 系统实施成功的因素是什么?

    影响ERP系统实施成功的因素很多,主要有以下几点:企业一把手是否大力支持.实施顾问是否专业负责.ERP系统是否强大灵活且适用三个方面!没有企业一把手的大力支持,ERP的应用基本上不可能获得成功.ERP ...

  8. C#-12 转换

    一 什么是转换 转换是接受一个类型的值并使用它作为另一个类型的等价值的过程. 下列代码演示了将1个short类型的值强制转换成byte类型的值. short var1 = 5; byte var2 = ...

  9. java的分页原理详解

    首先先创建一个Student实体类. import java.io.Serializable; import java.util.Map; public class Student implement ...

  10. iptables基本使用举例

    一.链的基本操作 1.清除所有的规则. 1)清除预设表filter中所有规则链中的规则. # iptables -F 2)清除预设表filter中使用者自定链中的规则. #iptables -X #i ...