基本解释

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

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

 enum memory_order{
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
}

原子类型的操作可以指定上述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.每个线程所能看到其他线程的操作的执行顺序都是一样的。

示例代码:

 std::string work;
std::atomic<bool> ready(false); void consumer(){
while(!ready.load()){}
std::cout<< work << std::endl;
} void producer(){
work= "done";
ready=true;
}

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操作具有传递性。

示例代码:

 std::vector<int> mySharedWork;
std::atomic<bool> dataProduced(false);
std::atomic<bool> dataConsumed(false); void dataProducer(){
mySharedWork={1,0,3};
dataProduced.store(true, std::memory_order_release);
} void deliveryBoy(){
while( !dataProduced.load(std::memory_order_acquire) );
dataConsumed.store(true,std::memory_order_release);
} void dataConsumer(){
while( !dataConsumed.load(std::memory_order_acquire) );
mySharedWork[1]= 2;
}

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)。

示例代码:

 std::atomic<std::string*> ptr;
int data;
std::atomic<int> atoData; void producer(){
std::string* p = new std::string("C++11");
data = 2011;
atoData.store(2014,std::memory_order_relaxed);
ptr.store(p, std::memory_order_release);
} void consumer(){
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_consume)));
std::cout << "*p2: " << *p2 << std::endl;
std::cout << "data: " << data << std::endl;
std::cout << "atoData: " << atoData.load(std::memory_order_relaxed) << std::endl;
}

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/

C++11内存模型的粗略解释的更多相关文章

  1. C++11 并发指南七(C++11 内存模型一:介绍)

    第六章主要介绍了 C++11 中的原子类型及其相关的API,原子类型的大多数 API 都需要程序员提供一个 std::memory_order(可译为内存序,访存顺序) 的枚举类型值作为参数,比如:a ...

  2. c++11 内存模型解读

    c++11 内存模型解读 关于乱序 说到内存模型,首先需要明确一个普遍存在,但却未必人人都注意到的事实:程序通常并不是总按着照源码中的顺序一一执行,此谓之乱序,乱序产生的原因可能有好几种: 编译器出于 ...

  3. C++11内存模型的一些补充阅读材料

    <Intel Threading Building Block> O'REILLY Chapter 7 Mutual Exclusion - Atomic Operation - Memo ...

  4. 再说 c++11 内存模型

    可见性与乱序 在说到内存模型相关的东西时,我们常常会说到两个名词:乱序与可见性,且两者经常交错着使用,容易给人错觉仿佛是两个不同的东西,其实不是这样,他们只是从不同的角度来描述一个事情,本质是相同的. ...

  5. [转载]《C++0x漫谈》系列之:多线程内存模型

    <C++0x漫谈>系列之:多线程内存模型 By 刘未鹏(pongba) 刘言|C++的罗浮宫(http://blog.csdn.net/pongba) <C++0x漫谈>系列导 ...

  6. 二.GC相关之Java内存模型

    根据上节描述的问题,我们知道其最终原因是GC导致的.本节我们就先详细探讨下与GC息息相关的Java内存模型. 名词解释:变量,理解为java的基本类型.对象,理解为java new出来的实例. Jav ...

  7. C++11并发内存模型学习

    C++11标准已发布多年,编译器支持也逐渐完善,例如ms平台上从vc2008 tr1到vc2013.新标准对C++改进体现在三方面:1.语言特性(auto,右值,lambda,foreach):2.标 ...

  8. 11、Java并发性和多线程-Java内存模型

    以下内容转自http://ifeve.com/java-memory-model-6/: Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的.Java虚拟机是一个完整的计算机的一个模型, ...

  9. Cocos2d-x v3.11 中的新内存模型

    Cocso2d-x v3.11 一项重点改进就是 JSB 新内存模型.这篇文章将专门介绍这项改进所带来的新研发体验和一些技术细节. 1. 成果 在 Cocos2d-x v3.11 之前的版本中,使用 ...

随机推荐

  1. RestFul && HATEOAS && Spring-Data-Rest介绍

    1.什么是RestFul 经常上网的同学会发现,现代软件的一个重要趋势就是互联网化,几乎没有一款软件是纯粹的单机版了.通常的情况下,软件管理着服务器的资源以及这些资源的状态变化,用户通过在浏览器输入h ...

  2. [DevExpress]ChartControl之基准线示例

    关键代码: /// <summary> /// 创建基准线ConstantLine /// </summary> /// <param name="chart& ...

  3. input获取永久焦点

    $(function () { $('#test').blur(function () { var that = this; //或者用闭包 setTimeout(function () { $(th ...

  4. linux centos 安装

    本着学习的目的,在自己的电脑上进行 centos 7 安装,记录下这步骤以备忘. 一.Centos 下载 centos 官方(https://www.centos.org/)下载ISO镜像(这是我的下 ...

  5. 如何利用VS2010安装和部署应用程序

    转自:http://jingyan.baidu.com/article/4b52d70255d7f0fc5d774b4d.html 1.假设你当前的winform已经okay了 2.解决方案中新建&q ...

  6. a标签点击后的虚线框问题

    以前一直用的方法都是: a {outline: none;star:expression(this.onFocus=this.blur());} 后来发现有瑕疵,不完美.体现在页面调用JS动作比较频繁 ...

  7. 阶段性放弃 wxPython 前的总结

    为了实现一个管理本地电子书的程序,搞了一段时间 GUI,使用 wxPython. 实在难以适应和习惯,也搞不出什么太好看的效果. 最不能忍受的是,多线程处理能力太弱.遂决定放弃 GUI. 放弃之前,整 ...

  8. Android UI学习前言:Android UI系统的知识结构

    Android UI系统的知识结构如下图所示: 对于 一个GUI系统地使用,首先是由应用程序来控制屏幕上元素的外观和行为,这在各个GUI系统中是不相同的,但是也具有相通性.Android系统在这方面, ...

  9. 【转】perl ping检测功能脚本代码

    我的第一个用于生产环境的perl脚本,虽然不是很优秀,但也迈出了扎实的一步 :)领导有任务,给一批IP列表,ping每一台机器,如果没有响应就发邮件通知,通知的邮件需要分开,不能通知一个列表,得一封一 ...

  10. GHOST中DISK TO DISK 和DISK FROM to image的区别

    Ghost的Disk菜单下的子菜单项可以实现硬盘到硬盘的直接对拷(Disk-To Disk)、硬盘到镜像文件(Disk-To Image)、从镜像文件还原硬盘内容(Disk-From Image)。  ...