zoukankan      html  css  js  c++  java
  • C++11内存模型的粗略解释

    基本解释

        C++11引入了多线程,同时也引入了一套内存模型。从而提供了比较完善的一套多线程体系。在单线程时代,一切都很简单。没有共享数据,没有乱序执行,所有的指令的执行都是按照预定的时间线。但是也正是因为这个强的同步关系,给CPU提供的优化程度也就相对低了很多。无法体现当今多核CPU的性能。因此需要弱化这个强的同步关系,来增加CPU的性能优化。

    Overview

        C++11提供了6种内存模型:

      1 enum memory_order{
      2   memory_order_relaxed,
      3   memory_order_consume,
      4   memory_order_acquire,
      5   memory_order_release,
      6   memory_order_acq_rel,
      7   memory_order_seq_cst
      8 }

          原子类型的操作可以指定上述6种模型的中的一种,用来控制同步以及对执行序列的约束。从而也引起两个重要的问题:

    1.哪些原子类型操作需要使用内存模型?
    2.内存模型定义了那些同步语义(synchronization )和执行序列约束(ordering constraints)?

    原子操作可分为3大类:

    读操作:memory_order_acquire, memory_order_consume
    写操作:memory_order_release
    读-修改-写操作:memory_order_acq_rel, memory_order_seq_cst

    未被列入分类的memory_order_relaxed没有定义任何同步语义和顺序一致性约束

    执行序列约束

        C++11中有3种不同类型的同步语义和执行序列约束:

    1. 顺序一致性(Sequential consistency):对应的内存模型是memory_order_seq_cst

    2.请求-释放(Acquire-release):对应的内存模型是memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel

    3.松散型(非严格约束。Relaxed):对应的内存模型是memory_order_relaxed

    下面对上述3种约束做一个大概解释:

    Sequential consistency:指明的是在线程间,建立一个全局的执行序列

    Acquire-release:在线程间的同一个原子变量的读和写操作上建立一个执行序列

    Relaxed:只保证在同一个线程内,同一个原子变量的操作的执行序列不会被重排序(reorder),这种保证也称之为modification order consistency,但是其他线程看到的这些操作的执行序列式不同的。

    还有一种consume模式,也就是std::memory_order_consume。这个模式主要是引入了原子变量的数据依赖。

    代码解释

    Sequential consistency

    Sequential consistency有两个特性:
    1.所有线程执行指令的顺序都是按照源代码的顺序;
    2.每个线程所能看到其他线程的操作的执行顺序都是一样的。

    示例代码:

      1 std::string work;
      2 std::atomic<bool> ready(false);
      3 
      4 void consumer(){
      5   while(!ready.load()){}
      6   std::cout<< work << std::endl;
      7 }
      8 
      9 void producer(){
     10   work= "done";
     11   ready=true;
     12 }

    1. work = "done"  sequenced-before ready=true 推导出 work = "done" happens-before ready=true
    2. while(!ready.load()){} sequenced-before std::cout<< work << std::endl 推导出 while(!ready.load()){} happens-before std::cout<< work << std::endl
    3. ready = true synchronizes-with while(!ready.load()){} 推导出 ready = true inter-thread happens-before while (!ready.load()){},也就推导出ready = true happens-before while (!ready.load()){}

    同时因为happens-before关系具有传递性,所以上述代码的执行序列式:       

    work = "done" happens-before ready = true happens-before while(!ready.load()){} happens-before std::cout<< work << std::endl

    Acquire-release

    关键思想是:在同一个原子变量的release操作和acquire操作间同步,同时也就建立起了执行序列约束。

    所有的读和写动作不能移动到acquire操作之前。
    所有的读和写动作不能移动到release操作之后。

    release-acquire操作在线程间建立了一种happens-before。所以acquire之后的操作和release之前的操作就能进行同步。同时,release-acquire操作具有传递性。

    示例代码:

      1 std::vector<int> mySharedWork;
      2 std::atomic<bool> dataProduced(false);
      3 std::atomic<bool> dataConsumed(false);
      4 
      5 void dataProducer(){
      6     mySharedWork={1,0,3};
      7     dataProduced.store(true, std::memory_order_release);
      8 }
      9 
     10 void deliveryBoy(){
     11     while( !dataProduced.load(std::memory_order_acquire) );
     12     dataConsumed.store(true,std::memory_order_release);
     13 }
     14 
     15 void dataConsumer(){
     16     while( !dataConsumed.load(std::memory_order_acquire) );
     17     mySharedWork[1]= 2;
     18 }

    1. mySharedWork={1,0,3};  is sequenced-before dataProduced.store(true, std::memory_order_release);
    2. while( !dataProduced.load(std::memory_order_acquire) ); is sequenced-before dataConsumed.store(true,std::memory_order_release);
    3. while( !dataConsumed.load(std::memory_order_acquire) ); is sequenced-before mySharedWork[1]= 2;

    4. dataProduced.store(true, std::memory_order_release); is synchronizes-with while( !dataProduced.load(std::memory_order_acquire) );
    5. dataConsumed.store(true,std::memory_order_release); is synchronizes-with while( !dataConsumed.load(std::memory_order_acquire) );

    因此dataProducer和dataConsumer能够正确同步。

    原子变量的数据依赖

    std::memory_order_consume说的是关于原子变量的数据依赖。
    数据依赖有两种方式:
    1. carries-a-dependency-to:如果操作A的结果用于操作B的操作当中,那么A carries-a-dependency-to(将依赖带入) B
    2. dependency-ordered-before:如果操作B的结果进一步在相同的线程内被操作C使用,那么A的stor操作(with std::memory_order_release, std::memory_order_acq_rel or std::memory_order_seq_cst)是dependency-ordered-before(在依赖执行序列X之前)B的load操作(with std::memory_order_consume)。

    示例代码:

      1 std::atomic<std::string*> ptr;
      2 int data;
      3 std::atomic<int> atoData;
      4 
      5 void producer(){
      6     std::string* p  = new std::string("C++11");
      7     data = 2011;
      8     atoData.store(2014,std::memory_order_relaxed);
      9     ptr.store(p, std::memory_order_release);
     10 }
     11 
     12 void consumer(){
     13     std::string* p2;
     14     while (!(p2 = ptr.load(std::memory_order_consume)));
     15     std::cout << "*p2: " << *p2 << std::endl;
     16     std::cout << "data: " << data << std::endl;
     17     std::cout << "atoData: " << atoData.load(std::memory_order_relaxed) << std::endl;
     18 }

    1. ptr.store(p, std::memory_order_release) is dependency-ordered-before while (!(p2 = ptr.load(std::memory_order_consume)))。因为后面的std::cout << "*p2: " << *p2 << std::endl;将读取load操作的结果。
    2. while (!(p2 = ptr.load(std::memory_order_consume)) carries-a-dependency-to std::cout << "*p2: " << *p2 << std::endl。因为*p2的输出使用了ptr.load操作的结果

    综上所述,对于data和atoData的输出是没有保证的。因为它们和ptr.load操作没有carries-a-dependency-to关系。同时它们又不是原子变量,这将会导致race condition。因为在同一时间,多个线程可以访问data,线程t1(producer)同时会修改它。程序的行为因此是未定义的(undefined)。

    参考:

    http://en.cppreference.com/w/cpp/atomic/memory_order
    http://www.modernescpp.com/

  • 相关阅读:
    常用的居中方法
    CSS实现盒子高度撑开且以最高的为高
    利用vertical-align实现行内元素对齐
    CSS实现模糊效果
    【LeetCode 37】解数独
    【LeetCode 36】有效的数独
    【LeetCode 35】搜索插入位置
    【LeetCode 34】在排序数组中查找元素的第一个和最后一个位置
    【LeetCode 33】搜索旋转排序数组
    【LeetCode 32】最长有效括号
  • 原文地址:https://www.cnblogs.com/navono007/p/5746048.html
Copyright © 2011-2022 走看看