多核cpu的缓存一致性

为什么要有缓存?

再说到cpu的缓存一致性前提下,需要了解到为什么会有缓存:
众所周知,cpu是用来计算和处理数据的,本身并不存储数据,数据都是放在内存或者硬盘中,而内存与cpu之间是存在距离的,如果说cpu目前要进行一次计算,那么就需要先从内存将数据取出再进行计算,而内存和cpu是存在速度差异的,在随着现代技术的发展,嵌入到cpu上的晶体管越来越多,那么就造成了cpu的计算和处理能力越来越强,这样就更加凸显了这个弊端,cpu在一次处理中更多的时间是在等待内存中的数据到来,这样的情况是我们人类是不能容忍的(我把你创造出来就是干活的,你可不能闲着);

在这样的需求驱动下,就需要提高cpu的利用率。思路就是在cpu和内存之间加一层缓冲(缓存,此处忽略三级缓存的差异,统称为缓存),因为cpu加载缓存中的数据比较快,所以就提高了cpu的利用率,那么现在cpu在处理数据的时候就会先从缓存中查找,找不到就去内存中找,但是这次cup从内存中不但是加载本次需要处理的数据,而且会将本次处理数据周围空间的数据也进行加载(这涉及时间局部性,空间局部性)因为cpu会觉得我下次要加载的数据肯定会在本次处理数据的周围,那么下次我就不需要再来读取内存了,直接在缓存中找就可以啦;

所以目前的架构变成了这个样子:
在这里插入图片描述

缓存的利和弊

现在cpu再加入了缓存之后如虎添翼,cpu的利用率大大提高了(基本实现了007);
■ 优点:加快了cpu的处理能力,提高了cpu的利用率
■ 缺点:
● 在单核cpu的的系统中,基本是没缺点的
● 那么在多核cpu的情况下会有什么问题呢?首先需要知道一个知识点(敲黑板啦),为了避免多个cpu共用一个缓存,造成多个cpu掐架,那么给每个cpu都分配一个女朋友,不对,是分配一个缓存,各自用各自的缓存,那么就会造成一个问题:数据不一致问题;

数据不一致问题

先来看一段代码:
下面的这段代码,执行后会发现,有一定概率 线程1 会一直在运行,
代码描述:
目前是开启了两个线程,那现在这两个线程各自由一个cpu所处理,而线程退出的条件是:公有变量COUNTER大于5,目前这个COUNTER值的增加是在 线程2中进行的;

执行流程:

  1. cpu1将线程1加载到自己的缓存,包括COUNTER 的值=0
  2. cpu2将线程2加载自己的缓存,包括COUNTER 的值=0
  3. 目前因为cpu1和cpu2将数据都各自拷贝了一份到自己的缓存中,那么cpu2对COUNTER的值进行增加操作,对于cpu1是无法感知的;所以cpu1会在while 一直出不来

解决:
对于这样因为缓存不一致导致的bug,在Java层面,我们可以使用 volatile 关键字来进行解决,这种在多cpu环境下变量的可见性问题,被volatile关键字标注的变量,当此变量的值被更改后,会强制将更改后的值刷新会内存,并失效各个cpu中的缓存的变量;

package com.zy.util;

import java.util.regex.Pattern;

/**
 * @Author: hu.chen
 * @Description:
 * @DateTime: 2021/12/2 2:54 下午
 **/
public class Test {
    //    volatile
    private static 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("线程1 : " + COUNTER + "");
                    threadValue = COUNTER;
                }
            }
        }
    }

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

}

操作系统级别的缓存一致性解决方案

解决缓存不一致性问题,通常来说有以下2种解决方法:

  1. 通过在总线加LOCK#锁的方式
  2. 通过缓存一致性协议
    这2种方式都是硬件层面上提供的方式

总线加lock锁:
在早期的CPU当中,是通过在总线上加LOCK#锁的形式来解决缓存不一致的问题。因为CPU和其他部件进行通信都是通过总线来进行的,如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。
但是这种方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下;

缓存一致性协议:
缓存一致性协议中,最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取

在MESI协议中,每个cache line有4个状态,可用2个bit表示,它们分别是

状态描述
M(Modified)这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
E(Exclusive)这行数据有效,数据和内存中的数据一致,数据只存在于本Cache中。
S(Shared)这行数据有效,数据和内存中的数据一致,数据存在于很多Cache中。
I(Invalid)这行数据无效
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值