在多核CPU中,内存中的数据会在多个核心中存在数据副本,某一个核心发生修改操作,就产生了数据不一致的问题,而一致性协议正是用于保证多个CPU cache之间缓存共享数据的一致性。
write through 写通
每次CPU修改cache中的内容会立即更新到内存,也就意味着每次CPU写共享数据,会导致总线事务,因此这种方式常常会引起总线事务的竞争,虽然后高的一致性但是效率非常低。
write back 写回
每次CPU修改了cache中的数据,不会立即更新到内存,而是等到cache line在某一个必须或合适的实际才会更新到内存。
写失效
当一个CPU修改了数据,如果其他CPU有该数据,则通知其为无效;
写更新
当一个CPU修改了数据,如果其他CPU有该数据,则通知其更新;
cache line是cache与内存数据交换的最小单位,根据操作系统一般是32或64byte,在MESI协议中状态 可以是M、E、S、I,地址则是cache line中映射的内存地址,数据则是从内存中读取的数据。
工作方式
当CPU从cache中读取数据时候,会比较地址是否相同,如果相同则检查cache line的状态,再决定该数据是否有效,无效则从主存中获取数据,或根据一致性协议发生一次cache-to-cache的数据推送。
工作效率
当CPU能够从cache中拿到有效数据的时候,消耗几个CPU cycle,如果发生cache miss也就是缓存中没有数据需要从主存中读取,则会消耗几十上百个CPU cycle。
MESI协议将cache line的状态分为modify、exclusive、shared、invalid分别是修改、独占、共享、失效
状态 | 描述 |
---|---|
M(modify) | 当前CPU刚修改完数据的状态,当前CPU拥有最新数据,其他CPU拥有失效数据,而且和主存数据不一致 |
E(exclusive) | 只有当前CPU中有数据,其他CPU中没有改数据,当前CPU的数据和主存的数据是一致的 |
S(shared) | 当前CPU和其他CPU中都有共同的数据,并且和主存中的数据一致; |
I(invalid) | 当前CPU中的数据失效,数据应该从主存中获取,其他CPU中可能有数据也可能无数据;当前CPU中的数据和主存中的数据被认为不一致。 |
M和E状态下的Cache Line数据是独有的,不同点在于M状态的数据时dirty和内存的不一致,E状态下数据和内存是一致的;
在MESI协议中,每个Cache控制器不仅知道自己的读写操作,而且也监听其他Cache的读写操作,每个Cache line所处的状态根据本核和其他核的读写操作在4个状态之间进行迁移。
分为以下四个操作:
编译器和CPU可以保证输出结果一样的情况下对指令重排序,使性能得到优化,插入一个内存屏障,相当于告诉CPU和编译器限于这个命令的必须先执行,后于这个命令的必须后执行。
内存屏障的另一个作用是强制更新一次不同CPU的缓存,这意味着如果你对一个volatile字段进行写操作,你必须知道:
加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障,它有三个功能:
缓存一致性协议有多种,但是日常处理的大多数计算机设备都属于”嗅探(snooping)”协议,它的基本思想是:
所有内存的传输都发生在一条共享的总线上,而所有的处理器都能看到这条总线:缓存本身是独立的,但是内存是共享资源,所有的内存访问都要经过仲裁(同一个指令周期中,只有一个CPU缓存可以读写内存)。
CPU缓存不仅仅在做内存传输的时候才与总线打交道,而是不停在嗅探总线上发生的数据交换,跟踪其他缓存在做什么。所以当一个缓存代表它所属的处理器去读写内存时,其它处理器都会得到通知,它们以此来使自己的缓存保持同步。只要某个处理器一写内存,其它处理器马上知道这块内存在它们的缓存段中已失效。
** 反复思考IA-32手册对lock指令作用的这几段描述,可以得出lock指令的几个作用:
1、锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放,不过实际后来的处理器都采用锁缓存替代锁总线,因为锁总线的开销比较大,锁总线期间其他CPU没法访问内存
2、lock后的写操作会回写已修改的数据,同时让其它CPU相关缓存行失效,从而重新从主存中加载最新的数据
3、不是内存屏障却能完成类似内存屏障的功能,阻止屏障两遍的指令重排序
由于效率问题,实际后来的处理器都采用锁缓存来替代锁总线,这种场景下多缓存的数据一致是通过缓存一致性协议来保证的 **
既然CPU有了MESI协议可以保证cache的一致性,那么为什么还需要volatile这个关键词来保证可见性(内存屏障)?或者是只有加了volatile的变量在多核cpu执行的时候才会触发缓存一致性协议?
两个解释结论:
使用上的区别
volatile只能修饰变量,synchronized只能修饰方法和语句块;
对原子性的保证
synchronized可以保证原子性,volatile不能保证原子性;
对可见性的保证
都可以保证可见性,但实现原理不同,volatile对变量加了lock,synchronized使用monitorEnter和monitorExit;
对有序性的保证
都可以保证有序性,但是synchronized并发退化到串行;
其他
synchronized引起阻塞;
volatile不会引起阻塞;