你也可以通过我的独立博客 —— www.huliujia.com 获取本篇文章
内存乱序
内存乱序指的是内存操作出现乱序,CPU缓存、编译器优化、处理器指令优化等都会改变内存顺序,造成内存乱序。
学习内存顺序容易陷入了一个误区,因为内存顺序是和CPU架构、编译器息息相关的,想要去深入理解CPU缓存怎么导致内存乱序的,编译器优化和处理器指令又是怎么导致内存乱序的,很容易陷入一个又一个填不了的坑。要去了解各种编译器优化技术、了解各种CPU的指令集,甚至连ARM的v7和v8区别都去看了一下。
鲁迅说过,学海无涯,回头是岸。内存顺序或者说内存模型其实是语言层面的东西,所以可以直接从语言层面去理解,所谓内存乱序,在语言层面的表现就是虽然你写了A、B、C三行代码,但是最后在线程中执行顺序可能是CBA,而另外一个线程,观察该线程得到的执行顺序可能是ACB。这些是由于CPU缓存、编译器优化、处理器指令优化等共同造成的。
上述各种优化导致的内存乱序并不是随意地乱序,是有底线的,这个底线就是单线程场景下,优化后程序的执行结果要和优化前保持一致,即让写代码的人感觉是没有优化存在的,代码就是按照他写的顺序执行的。
但是在多线程场景下,内存乱序就会给开发者创造惊喜了,程序的执行结果可能和开发者的预期不一致,看起来像是执行出现问题了,比如下面两个线程:
//线程1
//a,b,c的初始值为0
Thread1()
{
a = 1; //A
b = 2; //B
c = 3; //C
}
//线程2
Thread2()
{
printf("a:%d, b:%d, c:%d", a, b,c);
}
两个线程同时执行,一般来说线程2打印出来的应当是下面四种可能的结果,不应该有其他结果的出现。
- a:0, b:0, c:0
- a:1, b:0, c:0
- a:1, b:2, c:0
- a:1, b:2, c:3
但是,因为内存乱序的存在,线程1的执行顺序可能是C、B、A。假设线程1观察到的顺序也是C、B、A,那么线程2的输出就可能是(a:0, b:2, c:3),而由于内存乱序也会导致其他线程观察到的执行顺序和线程实际的执行顺序不一致,线程2观察到的顺序可能是A、C、B,那么线程2的输出就是(a:1, b:0, c:2)。
解决上述问题的一个最简单的方式是使用互斥锁,每次访问共享数据之前都加上互斥锁,但是这个很麻烦,意味着每个需要共享的变量都需要在访问前后加上互斥锁。另外一方面,互斥锁很“强”,在一些不需要强一致性的场景下使用互斥锁,就有点杀鸡用牛刀了,会不必要地降低程序性能。