一、CPU高速缓存
1、为什么需要高速缓存
计算机在执行程序时,程序的每条指令都是在CPU中执行的。而执行指令的同时势必会伴随着数据的读取和写入。由于程序运行过程的临时数据都是存储在内存中的(主内存-物理内存),而内存的读写相对于CPU的执行速度来说是非常慢的。
如果CPU执行指令所需要的任何数据都需要通过与内存进行交互,那么肯定会降低CPU的执行效率,因此就有了CPU的高速缓存。
2、什么是CPU的高速缓存
CPU中有一定的空间用于存储执行指令所需的临时数据,在程序执行时CPU会先从主内存中将运行所需的数据复制一份到自身的高速缓存中。那么在执行时就只需要从自身的缓存中读取和向其中写入数据,避免了"漫长"的与内存交互的过程。当运算结束之后再将缓存中的数据刷新到主内存中。
以下方代码为例:
int i = 0; // 语句1
i = i + 1; // 语句2
- 执行语句1时,会先从主内存中读取i的初始值0,存入CPU的高速缓存中。
- CPU执行指令对i进行加1操作。
- 将加1后的值写入CPU的高速缓存中。
- 最后将数据刷新到主内存中,主内存中的i赋值为1。
3、CPU高速缓存导致的缓存不一致问题
上述代码在单线程中执行是没有问题的,但是到了多线程中就可能会问题。在多核CPU中每个CPU中会同时运行不同的线程,每个线程都会拥有不同的CPU高速缓存(单核CPU也会出现多线程问题,以现在线程调度执行上)。
仍然以上述代码为例,i的初始值为0,当线程1和线程2同时执行语句1时我们期望的最终结果为2。但结果不一定为2,因为可能会出现下面一种情况:
1、线程1和线程2同时读取i的初始值0到自身CPU的高速缓存中。
2、线程1执行加一操作,线程1高速缓存中的i变成1,并将计算的数据刷新到主内存中i为1。
3、线程2执行加一操作,线程2高速缓存中的i变成1,并将计算的数据刷新到主内存中i为1。
最终的结果并不是我们希望的2而是变成了1,这就是缓存一致性问题。上述中被多个线程访问的变量i,通常称为共享变量。通俗的讲就是如果一个变量在多个CPU中都存在缓存,那么就可能会出现缓存一致性问题。
4、如何解决缓存一致性问题
1、总线锁机制。
2、缓存一致性协议。
(1)、总线锁机制
由于CPU与其它组件进行交互都是通过总线来进行的,如果当一个CPU需要访问主内存中的一个共享变量时,采取对对总线加锁的机制,就会阻塞其它CPU与其他组件的交互,从而使得只有当前的CPU能够操作主内存中的共享变量,进而避免了缓存一致性问题。
还是以上述代码为例,当线程1执行语句2时向总线发出加锁的操作信号后,此时就只能有线程1操作共享变量i。线程2就会处于阻塞状态,只有当线程1执行完毕后释放总线锁,线程2才能操作共享变量i。
总线锁机制虽然能够解决缓存一致性问题,但是由于会阻塞其它线程的执行,所以执行效率低下。
(2)、缓存一致性协议
由于总线锁机制的 效率低下,所以出现了缓存一致性协议。其中最著名的就是Intel的MESI协议。
MESI协议保证了每一个CPU缓存中使用的同一共享变量的副本都是一致的。核心理念是:当CPU向内存中写数据时,如果发现操作中使用到了共享变量(在其他CPU中存在该变量的副本),那么就会发出信号通知其它CPU将该变量的缓存行置为无效状态。因此当其他的CPU读取缓存中的这个变量副本时,发现本身缓存中的该变量缓存行为无效状态,那么就会重新去主内存中读取该变量。