存储器-cpu高速缓存2

先来看一段java 代码

public class test {



        private static volatile int COUNTER = 0;

        public static void main(String[] args) {
            new ChangeListener().start();
            new ChangeMaker().start();
        }

        static class ChangeListener extends Thread {
            @Override
            public void run() {
                int threadValue = COUNTER;
                while ( threadValue < 5){
                    if( threadValue!= COUNTER){
                        System.out.println("Got Change for COUNTER : " + COUNTER + "");
                        threadValue= COUNTER;
                    }
                }
            }
        }

        static class ChangeMaker extends Thread{
            @Override
            public void run() {
                int threadValue = COUNTER;
                while (COUNTER <5){
                    System.out.println("Incrementing COUNTER to : " + (threadValue+1) + "");
                    COUNTER = ++threadValue;
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) { e.printStackTrace(); }
                }
            }
        }
}

最后输出的结果

Incrementing COUNTER to : 1
Got Change for COUNTER : 1
Incrementing COUNTER to : 2
Got Change for COUNTER : 2
Incrementing COUNTER to : 3
Got Change for COUNTER : 3
Incrementing COUNTER to : 4
Got Change for COUNTER : 4
Incrementing COUNTER to : 5
Got Change for COUNTER : 5

如果此时我们把 

  private static volatile int COUNTER = 0;

volatile去掉,那么此时结果如下

Incrementing COUNTER to : 1
Got Change for COUNTER : 1
Incrementing COUNTER to : 2
Incrementing COUNTER to : 3
Incrementing COUNTER to : 4
Incrementing COUNTER to : 5

为什么会出现这样的结果呢,两个线程同时对一个数字运行,因为volatile关键字让我们必须不能从cache line中读取数据,必须从内存中读取,保证缓存一致性。

我们现代cpu都是多核cpu,也会存在像volatile这样的缓存不一致问题,假设cpu1更新了数据到cache line中,此时会把此页标记成脏页。

两种修改cache line的方法

写直达(Write-Through)

直接查缓存有没有对应的数据块,有则写入缓存数据块,然后写入到内存。无则直接写入到内存,这种情况都是直接写入到内存,性能差

写回(Write-Back)

  1. 查询缓存是否命中
  2. 命中缓存,直接写入到cache line 
  3. 无命中缓存  如果是脏页,先刷到内存,然后在从内存中读取数据到cache line ,再更新cache line 
  4. 无命中缓存  如果不是脏页,从内存中读取数据到cache line,再更新cache line 
  5. 最后的步骤都是将cache line 标记成脏页

 

这种方法的好处就是不一定要读取内存和写入内存。

但是上面两种方法都不能做到缓存一致性。

我们来看一下缓存不一致的情况

首先两个高速缓存都从内存中拿到i=20的数据,然后cpu2更新了i=30的数据,那么cpu1此时拿到的还是i=20的数据,明显就不对了。为此我们要保证缓存的一致性。

要保证缓存一致性的两个前提

  1. 写传播:写入cpu1的数据必须同步到cpu2中
  2. 事务的串行化:更新的顺序必须按顺序来执行

如果按上面的执行顺序,那么这个数据就有错误,应该先更新i=50,再更新i=30,这就是事务非串行化。

现在我们引入实现了上面两个前提的协议MESI 协议

写失效:只要往当前cpu的高速缓存里面写数据,其他cpu缓存中的数据都会失效,在写失效协议里,只有一个 CPU 核心负责写入数据,其他的核心,只是同步读取到这个写入。在这个 CPU 核心写入 Cache 之后,它会去广播一个“失效”请求告诉所有其他的 CPU 核心。其他的 CPU 核心,只是去判断自己是否也有一个“失效”版本的 Cache Block,然后把这个也标记成失效的就好了。

写广播:首先高速其他cpu的高速缓存的块失效了,然后再更改其他cpu高速缓存的对应块的数据

  • M:代表已修改(Modified)
  • E:代表独占(Exclusive)
  • S:代表共享(Shared)
  • I:代表已失效(Invalidated)

我们先来看看“已修改”和“已失效”,这两个状态比较容易理解。所谓的“已修改”,就是我们上一讲所说的“脏”的 Cache Block。Cache Block 里面的内容我们已经更新过了,但是还没有写回到主内存里面。而所谓的“已失效“,自然是这个 Cache Block 里面的数据已经失效了,我们不可以相信这个 Cache Block 里面的数据。

然后,我们再来看“独占”和“共享”这两个状态。这就是 MESI 协议的精华所在了。无论是独占状态还是共享状态,缓存里面的数据都是“干净”的。这个“干净”,自然对应的是前面所说的“脏”的,也就是说,这个时候,Cache Block 里面的数据和主内存里面的数据是一致的。

那么“独占”和“共享”这两个状态的差别在哪里呢?这个差别就在于,在独占状态下,对应的 Cache Line 只加载到了当前 CPU 核所拥有的 Cache 里。其他的 CPU 核,并没有加载对应的数据到自己的 Cache 里。这个时候,如果要向独占的 Cache Block 写入数据,我们可以自由地写入数据,而不需要告知其他 CPU 核。

在独占状态下的数据,如果收到了一个来自于总线的读取对应缓存的请求,它就会变成共享状态。这个共享状态是因为,这个时候,另外一个 CPU 核心,也把对应的 Cache Block,从内存里面加载到了自己的 Cache 里来。

而在共享状态下,因为同样的数据在多个 CPU 核心的 Cache 里都有。所以,当我们想要更新 Cache 里面的数据的时候,不能直接修改,而是要先向所有的其他 CPU 核心广播一个请求,要求先把其他 CPU 核心里面的 Cache,都变成无效的状态,然后再更新当前 Cache 里面的数据。这个广播操作,一般叫作 RFO(Request For Ownership),也就是获取当前对应 Cache Block 数据的所有权。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值