目录
基础
如何保证各个CPU缓存中的数据是一致的。就是CPU的缓存一致性问题。
缓存一致性协议
MESI协议是当前最主流的缓存一致性协议,在MESI协议中,每个缓存行有4个状态,可用2个bit表示,它们分别是:
只有当缓存行处于E或者M状态时,处理器是独占这个缓存行的。
当处理器想写某个缓存行时,如果它没有独占权,它必须先发送一条"我要独占权"的请求给总线,这会通知其它处理器把它们拥有的同一缓存段的拷贝失效(如果有)。
只有在获得独占权后,处理器才能开始修改数据,这个缓存行只有一份拷贝,在我自己的缓存里,所以不会有任何冲突。
反之,如果有其它处理器想读取这个缓存行(马上能知道,因为一直在嗅探总线),独占或已修改的缓存行必须先回到"共享"状态。如果是已修改的缓存行,那么还要先把内容回写到内存中。
CPU规则
当一个CPU修改缓存中的字节时,服务器中其他CPU会被通知,它们的缓存将视为无效。
两条线程Thread-A与Threab-B同时操作主存中的一个volatile变量i时,Thread-A写了变量i,那么:
- Thread-A发出LOCK#指令
- 发出的LOCK#指令锁总线(或锁缓存行),同时让Thread-B高速缓存中的缓存行内容失效
- Thread-A向主存回写最新修改的i
Thread-B读取变量i,那么:
- Thread-B发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值
由此可以看出,volatile关键字的读和普通变量的读取相比基本没差别,差别主要还是在变量的写操作上。
MESI优化带来的可见性问题
MESI协议虽然可以实现缓存的一致性,但是也会存在一些问题。就是各个CPU缓存行的状态是通过消息传递来进行的。如果CPU0要对一个在缓存中共享的变量进行写入,首先需要发送一个失效的消息给到其他缓存了该数据的CPU。并且要等到他们的确认回执。CPU0在这段时间内都会处于阻塞状态。
解决方法
为了避免阻塞带来的资源浪费。在cpu中引入了StoreBufferes。
CPU0 只需要在写入共享数据时,直接把数据写入到 storebufferes中,同时发送 invalidate消息,然后继续去处理其他指令。当收到其他所有CPU发送了invalidate acknowledge消息时,再将 storebufferes中的数据数据存储至 cache line 中。最后再从缓存行同步到主内存。