笔者知识和理解水平有限,欢迎指出不足之处html
推荐阅读
在上篇文章 深刻讲解并发编程模型之概念 比较详细分析了并发编程模型的相关概念。这篇文章就深刻讲解下关于重排序的问题。编程
重排序分为:segmentfault
其中,只要对单线程的语义(实际上能够理解为单线程执行结果)不产生影响,<span style="color:blue">编译器</span>在编译源代码的时候就能够从新编排程序语句的执行顺序。这就是编译器优化的重排序。缓存
若是对程序执行结果不产生影响,处理器能够优化程序指令的运行顺序,通常是采用并行的方式来执行指令。那么,如何判断改变指令的执行方式不会影响程序运行结果呢?其实能够根据指令之间是否有数据依赖。,<span style="color:blue">具备数据依赖的指令不能更改执行顺序,没有的能够修改,不会影响程序执行结果。</span>这就是指令级并行的重排序。什么是数据依赖性,下面会讲。安全
接下来,什么是内存系统重排序呢?咱们先来了解如下有关处理器缓存的知识。多线程
咱们知道,处理器为了加速对数据的读取,采用了三级缓存的策略。下面,先来分析CPU三级缓存是什么回事。并发
CPU与内存之间的临时数据交换器,它的出现是为了解决CPU运行处理速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。优化
首先,咱们来看看CPU三级缓存在计算机存储系统中处于哪一个位置:spa
计算机存储系统:线程
CPU三级缓存:
名词解释
执行速度:L1 > L2 > L3
咱们能够看到,CPU缓存是直接和主存进行数据交换的。由于CPU缓存执行速度远远大于主存,咱们能够把经常使用的数据缓存在CPU缓存中,大大提升指令的的执行速度。CPU缓存具体的工做原理不在这里讲,有兴趣的同窗能够翻看相关书籍。
回到咱们讲的重排序。这里咱们就能够分析如下内存系统重排序了。
在上面CPU三级缓存系统中,实现缓存一致性机制有两种。
若是CPU读取一个数据,就会经过总线来对这个数据加锁。那么其它线程就不能对这个数据进行读写操做。其实这样的实现是有很大问题的,在多线程环境下,若是出现多个线程同时读写相同临界量,那么这种加锁效率是很是低下的。
MESI它的工做原理能够阅读这篇文章 MESI--CPU缓存一致性协议
。
在内存系统中,处理器执行的程序指令有一部分是缓存在CPU缓存的,有一部分是须要在主存中获取的。那么,因为处理器使用缓存和读/写缓冲区,这使得加载和存储操做看上去多是在乱序执行。(能够理解为是有些指令读写操做不必定按照程序对处理器的顺序要求来作,由于从缓存获取指令进行操做,远远快于从主存获取指令进行操做)。这就是做者理解的内存系统重排序。
若是两个操做访问同一个变量,且这两个操做中有一个为写操做,此时这两个操做之间就存在数据依赖性。数据依赖分下列三种类型:
写一个变量以后,在读这个变量。好比:a=1, b=a
写一个变量后,再写这个变量。好比a=1, a=2
读一个变量后,再写这个变量。好比:a=b, b=1
上面三种状况,只要重排序两个操做的执行顺序,程序的执行结果将会被改变。前面提到过,编译器和处理器可能会对操做作重排序。编译器和处理器在重排序时,会遵照数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操做的执行顺序。注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操做,不一样处理器之间和不一样线程之间的数据依赖性不被编译器和处理器考
虑。(言外之意就是数据依赖性不保证线程安全问题)
as-if-serial 语义的意思指
无论怎么重排序(编译器和处理器为了提升并行度), (单线程)程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵照 as-if-serial 语义。
为了遵照 as-if-serial 语义,编译器和处理器不会对存在数据依赖关系的操做作重排序,由于这种重排序会改变执行结果。可是,若是操做之间不存在数据依赖关系,这些操做就可能被编译器和处理器重排序。
这篇文章和你们一块儿分析了重排序的三种方式和基本原理,又讲了数据依赖性的必要性以及as-if-serial语义的用途。你们掌握了这些就能够了,不必再深究重排序的知识了。
总之,在不影响程序执行结果的前提下,重排序是为了加速程序指令执行速度。重排序只对单个线程内的程序指令执行结果不产生影响,可是多线程模式下,就算不存在数据依赖的指令,在重排序后有可能会影响程序的执行结果。