C++11并发内存模型学习
C++11标准已发布多年,编译器支持也逐渐完善,例如ms平台上从vc2008 tr1到vc2013。新标准对C++改进体现在三方面:1.语言特性(auto,右值,lambda,foreach);2.标准库(智能指针,容器,函数式编程);3.还有最重要,又最容易被忽视的改进,并发内存模型标准的制定。
什么是内存模型?
输入一定的内存操作序列,得到一定的内存结果,这种对应关系的集合,就是内存模型,任何一种CPU架构,都有该架构对应的内存模型。C++语言规范定义了一个虚拟机,这个虚拟机的指令集就是标准C++语句,虚拟机执行C++语句,对内存操作并得到相应的结果,这就是C++内存模型。直到C++03,C++都没有定义并发环境下的内存模型,之前的内存模型只适用于单线程。C++03并发环境下的内存模型,都是具体的编译器,运行库、操作系统及具体硬件定义。在并发环境下,同一序列的C++代码在不同编译器、不同运行环境、不同硬件平台上得到的内存结果可能会不一样。也就是说,到C++03为止,C++并发环境下的代码,不可移植,容易产生混乱。
C++11的今天,程序员再没有免费的午餐,提升程序执行效率的手段必须与并发紧密结合,标准委员会已经意识到并发内存模型制定的重要性。并发内存模型制定后,任何一份符合C++标准的可并发代码(不包含特定平台API调用,特定平台汇编嵌入),在任何符合C++标准的编译器上编译,得到的二进制内容的运行时行为一致。
并发不等于多线程!
并发是指,多线程同时调度到多个cpu核心,并行执行。在以前单cpu核心系统上,多个线程的代码被限制在但个cpu核心上串行执行,所以C++03内存模型依然适用。多核心时代情况有所区别,不同线程的代码会并发执行,在这种情况下,内存(中的共享变量)在每个核心看来,是怎么样变化的?能看到变化吗?能看到变化的顺序一致吗?
如果渴望能看到变化,或者更严格,看到变化的顺序也一致,就需要:缓存一致性和顺序一致性。
缓存一致性:多核心系统,每个核心有私有的缓存,如果2个或以上核心的私有缓存引用了相同的内存位置,在并发执行情况下,就会出现有趣的现象。例如核心1刚把数据写到缓存(还没有回写内存),接着核心2读取内存,证核心2能读的到最新的数据吗?也就是说,核心2能看见核心1对内存的修改吗?缓存一致性机制保证了核心2能看到核心1对内存修改这一行为。http://en.wikipedia.org/wiki/Cache_coherence
有了缓存一致性,是不是就保证C++程序并发跨平台行为一致呢,不一定,因为从高级语言到机器码,隔了很多层。cpu实际执行的指令序列与源代码的语句序列有区别,于是引出另一个概念——顺序一致性。
为什么强调顺序一致,因为:1.源代码顺序与编译出来的机器码顺序不一致;2.编译出来的机器码顺序与执行顺序不一致;3.并发执行时,每个核心观察到的总体指令序列不一致,或者说,内存改变的顺序不一致,这与cpu核心与核心之间共享数据同步策略有关。http://en.wikipedia.org/wiki/Sequential_consistency
什么情况下会破坏顺序一致性:a.编译器优化破坏顺序一致,例如为了优化cpu缓存效率而把load指令都集中一起先执行,再执行store指令的指令顺序优化,又例如提升执行效率的变量寄存器化;b.即使编译器不做任何优化,cpu也会执行优化,例如乱序执行,操作数预取等,这些行为破坏顺序一致性。顺序一致性对并发究竟产生什么影响?最简单设想:1.如果临界区锁仍然有效,但lock之前以及之后的代码被cpu乱序执行了,什么后果;2.又或者lock成功后,得到的结果并不能反映最近一次unlock的修改。
C++11并发内存模型提供了什么给程序员?通过C++标准库,为程序员提供:“是否对代码启用顺序一致性及启用何种程度的顺序一致性”选项,这些选项就是std::memory_order。先了解几个概念再展开memory_order:
1.synchronizes with:多核心情况下,核心1对内存变量a修改后,紧接着核心2读取内存a,如果核心2能读到核心1修改的内容,那么,变量a就具有synchronizes with属性。通过观察具有synchronizes with属性变量的变化顺序,能够得到核心之间执行指令的顺序,用以确定happens before关系。注意,并不是所有内存变量都具有synchronizes with属性,因为这些变量可能被编译器或者cpu优化掉。
2.happens before:多核心情况下,如果指令A保证在指令B之前执行,就叫做A happens before B。并发环境下,happens before关系相当重要,例如lock及lock之后的代码就是happens before的关系。happens before关系与观测者有关,不同的观察者看到的顺序可以不同,也可以相同,将在展开memory_order_acquire和memory_order_release的时候举例子。
3.acquire和release:与锁拥有类似的特性(这里的特性不是指阻塞,而是指指令序列),acquire与acquire之后的代码必然是happens before关系,acquire之后必然能够看见最近一次release操作所引起的内存变化;release操作完成后,在其他核心看来(其他核心必须使用acquire),该内存已产生变化,release之前的所有操作(源代码角度)在release之后都是有效——happens before。
OK,逐一展开memory_order枚举
memory_order_relaxed:最松散,或者说,不执行顺序一致性。该内存操作保证是原子操作,但可以在编译时优化或在运行时被cpu执行任意优化。而且在多核心平台上,relax操作产生的结果可能不会被立即同步到其他核心(没有synchronizes with属性),其他核心不会立即看见这个操作。relax的原子操作与上下文代码没有happens before的关系,也就是没有release语意。
先看看memory_order_acquire和memory_order_release:这对操作具有acquire和release语义,被操作的内存变量在实施操作的核心之间有synchronizes with属性,可以保证源代码及编译后机器码顺序一致,但这种保证仍然不够严格,看这个例子:
-Thread 1- y.store (20, memory_order_release);
-Thread 2- x.store (10, memory_order_release);
-Thread 3- assert (y.load (memory_order_acquire) == 20 && x.load (memory_order_acquire) == 0)
-Thread 4- assert (y.load (memory_order_acquire) == 0 && x.load (memory_order_acquire) == 10)
运行时,两个assert都可能同时不触发,在t3角度看来xy的改变顺序与t4角度允许不一致,xy顺序无关,无论在源代码的角度,还是机器码的角度。如果没有t3和t4的观察,xy的执行顺序既是x-y,也是y-x,是不是跟那个什么猫的实验很像。如果要所有核心看来顺序一致,就要用memory_order_seq_cst。
再看看memory_order_consume:与acquire类似,但不会涉及到代码上下文内与原子操作不相关的内存变量,看这个例子:
-Thread 1-
n = 1
m = 1
p.store(&n, memory_order_release)
-Thread 2-
t = p.load(memory_order_acquire);
assert( *t == 1 && m == 1 );
-Thread 3-
t = p.load(memory_order_consume);
assert( *t == 1 && m == 1 );
t3会fail,因为p与m无关,(p只与n有关),所以t3角度只得到n,p的release结果,m没有被同步,相反,t2会同步n、m、及p。
memory_order_acq_rel:这个不用介绍了。
memory_order_seq_cst:最严格的顺序一致性,所有标记seq_cst的操作,顺序都不能被打乱(不能被编译器优化,不能被cpu乱序),无论从代码角度(源代码)还是cpu指令流(并发执行)的角度。在任意一个核心看来,所有标记为seq_cst的操作的顺序都一致。
说了这么多,C++11并发内存模型究竟起到什么作用,没有C++11并发内存模型,以前并发代码仍然可用!是的,因为以前的代码用了系统相关的锁,带有acquire和release语义,不过,这些代码不可移植。试想我们要编写一个用户模式下的spinlock,怎么做?vc下可以用interlockxxx这类编译器内置函数完成,你没看错,interlockxxx既不是C++标准,也不是windows API,它是ms c++编译器的内置函数(msdn remark有这样一段This function is implemented using a compiler intrinsic where possible),用于指导编译器生成顺序一致性代码。有了C++11并发内存模型,你就可以用标准库atomic和memory_order的memory_order_acquire、memory_order_release完成spinlock的编写,既符合C++标准,也具备一致性,而且代码的行为也得到保证。
参考
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1525.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2338.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2480.html
http://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync
http://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicTypes
8.2.2及8.2.3章节
http://msdn.microsoft.com/en-us/library/windows/desktop/ms686355(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/hardware/ff540496(v=vs.85).aspx
C++11并发内存模型学习的更多相关文章
- Java内存模型学习笔记
Java内存模型(JMM):描述了java程序中各种变量(线程共享变量)的范根规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节.共享变量就是指一个线程中的变量在其他线程中也是可见 ...
- java内存模型学习
根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 虚拟机的内存模型分为两部分:一部分是线程共享的,包括 Java 堆和方法区:另一部分是线程私有的,包括虚拟 ...
- Java内存模型学习笔记(一)—— 基础
1.并发编程模型的分类 在并发编程中,我们需要处理两个关键的问题:1.线程间如何通信,2.线程间如何同步.通信是指线程之间以何种机制来交换信息,同步是指程序用于不同线程之间操作发生相对顺序的机制. 在 ...
- Java 内存模型学习笔记
1.Java类 public class Math { public static final Integer CONSTANT = 666; public int math(){ int a = 1 ...
- java并发内存模型
java中线程之间的共享变量存储在主内存(java堆)中,每个线程都有一个私有的本地内存,本地内存存储了该线程以读.写共享变量的副本.本地内存是一个抽象概念,并不真实存储.它涵盖了cache,寄存器记 ...
- java 内存模型 ——学习笔记
一.Java 内存模型 java内存模型把 Java 虚拟机内部划分为线程栈和堆 下面这张图演示了调用栈和本地变量存放在线程栈上,对象存放在堆上. ==>> 一个局部变量可能是 ...
- java 多线程——并发编程模型 学习笔记
并发编程模型 ...
- 内存模型学习-- Container Executor task之间的关系
(分割线前的都是废话) java8内存模型: http://www.cnblogs.com/paddix/p/5309550.html http://www.cnblogs.com/dingyings ...
- jvm内存模型学习心得
昨天面试了两家,备受打击,问的多的就是jvm内存,然额真的是一头雾水.工作中用到的真是少之又少,面试还得问道, 今天恶补了下,在此作以下总结: jvm分为5部分 1.程序计数器 jvm支持多线程运行, ...
随机推荐
- enable cors in spring mvc with swagger
a. In controller add @CrossOrigin(origins = "http://localhost:8080") b. In mvc-servlet.xml ...
- hibernate和mybatis思想,区别,优缺点
Hibernate 简介 Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/R Mapping实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行.程序员往往只 ...
- Ganglia3.6.0,nginx+php搭建gweb,监控Hadoop2.2 和 Hbase0.98.1
环境:CentOS6.5 Hadoop2.2.0 Hbase0.98.1 服务端(master): 安装 ganglia ganglia-devel ganglia-gmetad ganglia-gm ...
- [IO] C# INI文件读写类与源码下载 (转载)
/// <summary> /// 类说明:INI文件读写类. /// 编 码 人:苏飞 /// 联系方式:361983679 /// 更新网站:[url]http://www.sufei ...
- javascript-图片横向无缝隙滚动(可在服务器运行)
前两次弄'图片横向滚动'javascript,在本地上运行得很美,可是一上到我们学校后台的服务器,就有很多问题,这个算是行的了. css代码: <style type="text/cs ...
- count(*),count(1)和count(主键) 区别
看如下数据: SQL> select count(*) from ysgl_compile_reqsub; COUNT(*) ---------- 已用时间: : : 07.51 SQL) fr ...
- Win32中GDI+应用(五)--GDI与GDI+编程模型的区别
在GDI里面,你要想开始自己的绘图工作,必须先获取一个device context handle,然后把这个handle作为绘图复方法的一个参数,才能完成任务.同时,device context ha ...
- iframe中的各种跳转方法(转)
一.背景A,B,C,D都是jsp,D是C的iframe,C是B的iframe,B是A的iframe,在D中跳转页面的写法区别如下. 二.JS跳转window.location.href.locatio ...
- MVVM模式应用 之在ViewModel中使用NavigationService
在ViewModel.cs页面中是不能使用NavigationService,那该怎么实现跳转呢? 其实在ViewModel中实现页面的跳转也很简单,下面的代码: using Microsoft.Ph ...
- 对N个数组进行操作。先把这N个一维数组合并成一个2为数组;然后进行操作
using System;using System.Collections.Generic;using System.Linq;using System.Collections;using Syste ...