-总结自 “Java并发编程的艺术” 和以下两篇博客:
https://blog.csdn.net/qq_35642036/article/details/82801708;https://blog.csdn.net/martin_ke/article/details/88851393
- 原子操作是指不可被中断的一个或者一组操作。
- 处理器会自动保证基本的内存操作的原子性,也就是一个处理器从内存中读取或者写入一个字节时,其他内存是不能访问这个字节的内存地址。但处理器不能自动保证复杂的内存操作的原子性,比如跨总线宽度、跨多个缓存行或者跨页表的操作。
- 总线锁定和缓存锁定是处理器保证复杂内存操作原子性的两个机制。
总线锁定
- 一个处理器在总线上输出LOCK#信号,使得其他处理器对内存的操作请求都会被阻塞,该处理器独占共享内存。
缓存锁定
- 由于总线缓存阻止了被阻塞处理器和所有内存之间的通信,而输出LOCK#信号的CPU可能只需要锁住特定的一块内存区域,因此总线锁定开销较大。
- 缓存锁定是某个CPU对缓存数据进行更改时,会通知缓存了该数据的该数据的CPU抛弃缓存的数据或者从内存重新读取。
MESI协议
- MESI协议是经典的缓存一致性协议,它通过在每个缓存行要求设置两个状态位,使得每个缓存行处于M(modify), E(exclusive), S(share), I(invalid)四个状态之一。
- M: 处于此状态的缓存数据,只有在本CPU中有,并且该数据已经被修改,与内存中的值不同;
- E:独占的,只有本CPU有该缓存数据,并且与内存中数据一致;
- S:共享的,多个CPU中都存有这个缓存数据,并且多个缓存数据和内存数据都是一致的;
- I:该CPU存储的该缓存数据是无效的。
- 一个处于M状态的缓存行,必须时刻监听试图读取该缓存行对应的主内存地址的操作,如果监听到,则必须在读取操作之前把缓存行的数据写回主内存。
- 一个处于E状态的缓存行,必须时刻监听试图读取该缓存行对应的主内存地址的操作,如果监听到,则将缓存行状态置于I。
- 一个处于S状态的缓存行,必须时刻监听使该缓存行无效或者独享该缓存行的请求,如果监听到,则必须把其缓存行状态设置为I。
- 当一个CPU读取缓存行数据时,如果缓存行状态为I,则需要从内存重新读取,并把自己状态改为S。如果不为I可直接读取,但要必须要等待其他CPU的监听结果,如其他CPU也有该数据的缓存且状态是M,则需要等待其把缓存更新到内存之后,再读取。
- 当CPU需要写数据时,只有缓存行状态为M或者E时才可以执行。否则需要发出特殊的RFO指令(Read Or Ownership,这是一种总线事务),通知其他CPU置缓存无效(I),这种情况下性能开销是相对较大的。在写入完成后,修改其缓存状态为M。
两种不能使用缓存锁的情况
- 第一种情况是操作的数据不能被缓存在处理器内部,或者操作的数据跨多个缓存行(cache line)时,则处理器会调用总线锁定。
- 第二种情况是处理器不支持缓存锁定,对于Intel 486和Pentium处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。
网友评论