为了结果MESI的性能弱点(要等其他cpu发来的回复), 有 写缓冲器 ,无效化队列
就是除了给cpu配高速缓存外, 还给它们每个 配一个 写缓冲器 和 无效队列
这么用
写缓冲器
写的时候,发现相应 缓存行的状态是S, 就是共享,说明其他cpu也有副本, 这时候要发 无效 的通知出去广播给其他cpu, 本来是要等回复才能继续的, 有写缓冲器
就可以先在写缓冲器
放下就走了, cpu可以用本来等待的时间干点活, 省了
等回复都到了以后, cpu再来从写缓冲器
搬运到缓存行
存储转发: 读的时候先从 写缓存器
读, 因为它一般来说更新嘛, 没有 再去 高速缓存
, 这样就可以还是读到真的数据了
无效化队列
收到无效通知以后, 并不把数据无效化, 而是存入无效化队列
就回复 收到无效Ack,
这样可以让对方早点收到回复
存储转发 导致可见性问题
cpu0 更新了a的值, 写到写缓冲器
就往下走了,
过一阵,要读a, 这时先去写缓存器
读, 读到的自以为是最新的,
但是没准这一阵时间里面, cpu1已经改过a的值了,
但是, cpu1过来的无效通知, 是管不到 cpu0的写缓冲器
的
写缓冲器 导致 StoreLoad重排序, 导致可见性问题
写缓冲造成的
比如 cpu0 执行
data=1
ready=true
cpu0的高速缓存里面ready是E/M的状态(本来就是以我为准),data是无效的状态
这样对写data的时候会写入写缓冲器
,_先当写成了,再慢慢通知等其他cpu等他们回复知道了
写ready的时候 直接写高速缓存
就行了, 因为其他cpu本来就知道自己不知道,不用通知
这样 其他cpu可能先看到对ready的写(因为会主动去问来), 再看到对data的写(要等失效的通知), 相当于重排序了
无效化队列 导致LoadLoad重排序 导致可见性问题
也是上面这个例子, 但是改成 cpu0 和cpu1 的高速缓存里面 data都是有效值, 就是状态为S,
cpu1里面ready是I的状态,本来就没有有效的ready
cpu0的都执行过了,cpu1收到data无效的信息, 但是它存了无效化队列
没真的更新状态,就回复知道了,
然后,cpu1执行
if(ready){//我么有,需要去总线请求. 拿到真实值
b=data+1
}
先读ready, 本来状态就是I 需要去总线问来, 这样肯定就能拿到最新值了, true
到这是对的,
然后读data, !!! data状态还是S没改, 无效化队列里面排队的还没生效!!!还以为已经失效的值是对的!
2个重排导致的后果是一样的, 但是原因不同, 一个是发送方没通知到, 一个是接受方收到了 没来得及改变状态
存储屏障 加载屏障 来解决
总之, 写缓冲器 ,无效化队列 就是导致了可见性问题, 明明写了 其他线程看不到
这就需要编译器等底层系统 借助 内存屏障
存储屏障 : 让cpu 将 写缓冲器
排空,写入高速缓存
这叫冲刷
, 这样其他cpu 就会收到通知, 其他cpu可以来拿新数据
加载屏障:cpu 根据无效化队列
里面的信息,删除其高速缓存
的无效数据(就是状态变为I)
这2个屏障的成对使用, 才能保证更新可见