这篇文章是看了其他人的博客文章和查阅一些资料自己总结推断的,如有不同看法,欢迎留言分享。
程序乱序执行的原因有3个:
1、编译器优化
2、处理器优化
3、存储系统
编译器优化其实就是编译器由于某些标准,在编译过程中做出了优化,从而导致程序重排,这个问题就不详细讨论了,重点放在处理器优化和存储系统上。
处理器优化
现代CPU大多数采用流水线技术实现的。简单来看一个 CPU 指令的运行周期可以分成 4 个阶段:取址、译码、执行、写回。这4个阶段都是分别由CPU中的4个相互独立的物理单元来处理。这样子就可以实现一个CPU指令取址完开始译码,下一个CPU指令就可以开始取址而不用等待第一个CPU指令运行完。
当多个指令有依赖关系的时候,例如a指令的运行依赖于b指令的结果,那么a指令必须等待b指令完成写回后才能开始取址,而且流水线会停止,直到b指令写回完成。如果存在一个c指令不依赖于a指令也不依赖于b指令,那么处理器优化就可以把c指令重排到a指令之前,这样子有利于减少流水线停止来等待b指令完成的时间。
存储系统
这里涉及到缓存一致性协议MESI、store buffer与invalidate queue的概念。这里有一篇文章可以作为参考:关于MESI、store buffer与invalidate queue
理解了上述概念后,就能理解程序为什么会乱序执行了,那么怎么解决呢?答案是内存屏障。
内存屏障可以使得屏障前后的指令不被编译器优化和处理器优化重排。
对于读屏障,运行时,读屏障之前的已经加入到store buffer的修改操作会被标记,如果store buffer的所有修改操作还未收到回复而清空掉,那么读屏障之后的修改操作就会加入到store buffer中,cache中对应的缓存行还是原来的值而不是store buffer的值。
对于写屏障,运行时,写屏障之后的操作要在invalidate queue的内容全部处理完(把对应的cache中的缓存行失效)之后才能运行。
atomic原子操作的内存顺序选项std::memory_order_acquire就是相当于在操作之前加一个读屏障,std::memory_order_release就相当于在操作之后加一个写屏障。这样子,就能解决程序乱序问题了。
关于CAS操作的讨论
由于CAS操作需要读出真正的原子变量的值,而且这个过程中是原子的,所以需要通过mesi来保证对应缓存行的一致。无论CAS操作的内存顺序选项什么,CAS操作的原子变量在各个cpu中的缓存行都是一致的。



