zoukankan      html  css  js  c++  java
  • 内存模型二:不同内存模型的差异

    这次总结一下c++的两个内存模型。本来内存模型的概念应该是编程语言无关的。但是在众多高级编程语言中貌似只有c++支持使用不同的内存模型(Java只能使用顺序一致型内存模型)。

    内存模型主要是和原子操作相关。在c++11中,一个原子对象,其load和store方法都有一个参数,用于指定操作所使用的内存序列(memory order),不同的内存序列对应不同的内存模型。

    一般使用较多的主要有两个内存模型,分别是:

    1. SC模型(sequentially consistent),顺序一致性内存模型。
    2. AR模型(acquire/release),获取释放内存模型。

    顺序一致性内存模型

    SC模型,也是原子操作默认使用的内存模型。顺序一致性,说的是所有的处理器以相同的顺序看到所有的修改。可以把SC内存模型想象成:CPU和内存之间存在一个多路开关,连接到这个开关的CPU能够执行内存读写操作,因此能每一时刻有且只有一个CPU能操作内存。这意味着所有的内存操作都是串行执行的!SC模型保证了一个很强的内存顺序,如果一个原子操作A是顺序一致性的,那么它能保证,如果一个CPU观察到了A的发生,那么对于A之前的所有内存操作,都能被这个CPU观测到!上面一大段说白了其实就是:SC模型保证了一个内存操作对其它所有线程都同步(可见)

    获取释放内存模型

    程序执行的序列性越强,意味着效率越低。有些时候,或许不必使一个内存操作对于所有线程均同步(比如只想在某些特定的线程同步即可),因此可以允许更高程度的乱序来提高指令执行的效率。这便是其它内存模型存在的意义。具体来说,AR模型做的是,保证了执行store和load的两个线程之间同步,对于其它不相关的线程不作执行次序的要求,因此允许了这些线程为了执行效率而乱序执行指令。

    作为对上述理论的补充,下面将给出几个实例。

    例子一:一写多读

    这个例子较为普遍,其中一个线程负责写某个数据,其它的线程只负责读数据。首先是最简单的,使用SC内存模型(atomic的load和store默认就是std::memory_order_seq_cst),代码如下:

    atmoic<int> ready(0);
    
    //thread 0(产生任务)
    //======================
    task_add();
    ready.store(1);
    
    
    //other threads(消费任务)
    //======================
    while (ready.load() != 1);
    task_do();
    

    使用AR模型优化一写多读

    上面的代码是可以工作的,但是若想更精准的控制线程之间的同步,就要使用AR模型了,具体代码如下:

    atmoic<int> ready(0);
    
    //thread 0(产生任务)
    //======================
    task_add();
    ready.store(1, std::memory_order_release);
    
    
    //other threads(消费任务)
    //======================
    while (ready.load(std::memory_order_acquire) != 1);
    task_do();
    

    如此一来,同步仅发生在必要的线程之内了!

    例子二:多写多读

    多写多读是例子一的扩展,一些线程负责写数据,一些线程负责读数据。同样的,先是使用SC模型:

    atmoic<int> ready(0);
    
    //some threads(产生任务)
    //======================
    task_add();
    ready.store(1);
    
    
    //other threads(消费任务)
    //======================
    while (ready.load() != 1);
    task_do();
    

    值得一提的是,代码和一写多读的一样!如果是使用SC模型的话,并不需要区分究竟是一个线程写数据,还是多个线程写数据。因为SC模型的效果就是,所有读写看上去都是串行的!

    使用AR模型对多写多读进行错误的优化

    多写多读对AR模型稍有不同,稍不注意就会跌入大坑。先看一个错误的例子:

    atmoic<int> ready(0);
    
    //some threads(产生任务)
    //======================
    task_add();
    ready.store(1, std::memory_order_release);
    
    
    //other threads(消费任务)
    //======================
    while (ready.load(std::memory_order_acquire) != 1);
    task_do();
    

    直接把一写多读的代码搬过来可行吗?读操作与写操作之间是有同步的,看上去似乎是没有问题。但是由于是多个线程都有机会写数据,那么多个线程的写操作之间的同步呢?

    使用AR模型对多写多读进行正确的优化

    atmoic<int> ready(0);
    
    //some threads (产生任务)
    //======================
    task_add();
    ready.exchange(1, std::memory_order_acq_rel);
    
    
    //other threads(消费任务)
    //======================
    while (ready.load(std::memory_order_acquire) != 1);
    task_do();
    

    这样一来就保证了不同线程的写操作之间也会进行必要的同步!

    总结 

    当然还有其它的内存模型,比如对次序不作任何保证的Relax。不同的内存模型对多线程乱序做了不同程度的保证。在保证程序执行正确性的前提下,根据不同的应用场景选择最合适的内存模型,允许尽可能多的乱序,是提高执行效率的一种方法!

  • 相关阅读:
    安装SQL Server驱动到Maven仓库[转]
    Nuget 摘录
    删除除了Src属性以后的全部属性
    执行NET 命令无法使用超过20个字符的组名或用户名
    在EntityFramework中使用 nock的方法。
    两代码的区别
    SQLServer 执行计划
    win7电脑的账户被禁用了怎么办
    win10防火墙损坏如何修复
    win10摄像头在哪打开?
  • 原文地址:https://www.cnblogs.com/adinosaur/p/6338075.html
Copyright © 2011-2022 走看看