一、为什么多线程并行的场景会有可见性的问题?
- 这个问题主要是CPU为了提高运算效率,设置
三级缓存模型
带来了缓存不一致的问题 - 现在的计算机基本都是多核的,线程可能运行在不同的处理器下,每个线程运行的时候都需要将主内存的数据load到各自的处理器中
- 当一个线程对共享变量的修改,其他线程
无法立刻感知
到其他处理器对共享变量的修改,所以缓存一致性问题就导致多线程间共享变量的可见性问题
二、volatile的作用是什么?是如何保证可见性的?
-
volatile修饰的变量会自动加上一个lock前缀指令(汇编层面),执行的时候识别到这个指令以后,在一些比老的处理器会直接触发
总线锁
,现在的话一般都是会优先触发缓存一致性协议
(缓存锁定),来保证多线程多共享变量的可见性 -
缓存一致性协议里缓存行会有四种状态,分别是
修改
、独占
、共享
、失效
-
首先,在缓存行的共享状态的时候,如果处理器对共享变量执行写操作的时候会对其所在缓存行加锁,同时会发一个
总线写的信号
-
然后,其他也拥有这个缓存行数据的处理器通过
总线窥探机制
窥探到这个信号后首先会判断自己的CPU Cache中是否也存在这个缓存行数据,如果存在的话,会把这个缓存数行据置为失效状态,然后下次读取时,直接从主内存读取 -
如果两个处理器
同时
对同一个缓存行的共享变量执行了写操作,然后两个处理器同一时刻都发了总线写的信号,这个时候,总线就会通过总线裁决
的方式判断只有一个请求成功,另一个信号直接丢弃,缓存置为失效,然后下次读取时,直接从主内存读取 -
一个缓存行默认是
64byte
,缓存的数据比这个大的话就会换总线锁
了 -
总线锁是锁定
CPU的总线
,从而导致同一时刻
只能有一个线程与内存通信,这就避免了多线程并发造成的可见性问题 -
总线锁会导致锁定期间,其他处理器也不能操作其他内存地址的数据,CPU的执行效率大幅度下降,相当于单核执行 。
注意:总线裁决的执行是非常快的,只需要判断一下即可
三、缓存一致性协议的实现原理怎样的?
缓存一致性协议是通过总线窥探
的方式实现,而总线窥探又分为 窥探失效
和 窥探更新
,MESI则属于窥探失效类型
3.1 MESI缓存一致性协议中,缓存行有4种不同的状态
已修改Modified (M)
:缓存行是脏的(dirty),与主存的值不同。如果别的CPU内核要读主存这块数据,该缓存行必须回写到主存,状态变为共享(S).
独占Exclusive (E)
:缓存行只在当前处理器的缓存中,当别的缓存读取它时,状态变为共享;当前写数据时,变为已修改状态。
共享Shared (S)
:缓存行也存在于其它处理器的缓存中且是未修改的。缓存行可以在任意时刻抛弃。
无效Invalid (I)
:缓存行是无效的
3.2 窥探失效
- 当一个处理器将共享变量从缓存写回主内存的时候,同时会发一个
总线写的信号
- 其他处理器窥探到这个信号以后,会判断缓存里是否存在这个共享变量,如果存在的话,则将缓存置为
失效状态
,重新从主内存读取数据
3.3 窥探更新
- 当一个处理器将共享变量从缓存写回主内存的时候,会将写数据
广播
到总线上的所有缓存中,其他处理器缓存的所有共享副本都会窥探到这个写信号,然后更新本地缓存数据
。 - 这个方法将写数据,通过
总线广播
给其他处理器。 - 它比窥探失效协议引起更大的总线流量。这就是为什么这种方法不常见。Dragon和firefly协议属于此类别。