深入理解volatile与MESI缓存一致性协议

12 篇文章 0 订阅
7 篇文章 0 订阅

volatile

volatile是Java虚拟机提供的轻量级的同步机制。

  • 不能保证原子性
  • 保证volatile修饰的共享变量对所有线程是可见的。也就是当一个线程修改 了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。
  • 禁止指令重排序

关于volatile的相关知识点,已经在以下两篇博文中详细解释了。
volatile详解1
volatile详解2

实例

在这里插入图片描述
volatile为什么会保证有序性?而不能保证原子性?
在对volatile属性进行操作的代码转换为汇编语言语言时,汇编指令代码有lock指令;
查看关于Lock资料:

  • LOCK(前缀) 代表的是总线锁,这个指令对应用程序有用且允许被应用程序使用。
  • 其在修改内存操作时,使用 LOCK 前缀去调用加锁的读-修改-写操作(原子的)。这种
    机制用于多处理器系统中处理器之间进行可靠的通讯。
  • 具体描述如下:
    在 Pentium 和早期的 IA-32 处理器中,LOCK 前缀会使处理器执行当前指令时产生
    一个 LOCK#信号,这总是引起显式总线锁定出现

多级缓存结构

在这里插入图片描述
CPU厂商在CPU中内置了少量的高速缓存以解决I\O速度和CPU运算速度之间的不匹配问题

总线锁

在早期的读写操作中,多个CPU中执行的多个线程会经过BUS总线去内存内存中读数据,第一个线程是修改操作,第二个是读操作,就会发生数据的读写问题;
早期的设计中,写操作的线程去内存中读取变量时,会在BUS总线上加上一个总线锁,其他CPU写操作且需要该变量的线程就无法从内存中获取该变量,全部都阻塞住了。因此,CPU的执行效率不高
类比Java synchronized关键字只允许一个线程执行一段代码;
在这里插入图片描述
在这里插入图片描述

多核CPU多级缓存一致性协议MESI

多核CPU的情况下有多个一级缓存,如何保证缓存内部数据的一致,不让系统数据混乱。这里就引出了一个 一致性的协议MESI。

MESI协议缓存状态

即数据在缓存中的状态,一共有四种;

  • M 修改 (Modified):该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。
  • E 独享、互斥 (Exclusive): 该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。
  • S 共享 (Shared): 该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中
  • I 无效 (Invalid):该Cache line无效。

注意:

  • 对于M和E状态而言总是精确的,他们在和该缓存行的真正状态是一致的,而S状态可能是非一致的。
  • 如果一个缓存将处于S状态的缓存行作废了,而另一个缓存实际上可能已经独享了该缓存行,但是该缓存却不会将该缓存行升迁为E状态,这是因为其它缓存不会广播他们作废掉该缓存行的通知,同样由于缓存并没有保存该缓存行的copy的数量,因此(即使有这种通知)也没有办法确定自己是否已经独享了该缓存行。 从上面的意义看来E状态是一种投机性的优化:如果一个CPU想修改一个处于S状态的缓存行,总线事务需 要将所有该缓存行的copy变成invalid状态,而修改E状态的缓存不需要使用总线事务

MESI状态转换

在这里插入图片描述
触发事件描述

本地读取(Local read)本地cache读取本地cache数据本地写入(Local write)
本地写入(Local write)本地cache写入本地cache数据
远端读取(Remote read)其他cache读取本地cache数据
远端写入(Remote write)其他cache写入本地cache数据

MESI状态切换举例

假设有三个CPU A、B、C,对应三个缓存分别是cache a、b、 c。在主内存中定义了x的引用值为0。
在这里插入图片描述

  1. 单核读取
    CPUA发出了一条指令,从主内存中读取X;
    从主内存通过BUS读取到缓存中(远端读取Remote READ),这是修改Cache Line的状态为E状态(独享);

在这里插入图片描述
2. 双核读取的执行流程:

  • CPUA发出了一条指令,从主内存中读取X;
  • 从主内存通过BUS读取到缓存中(远端读取Remote READ),这是修改Cache Line的状态为E状态(独享);
  • CPU B发出了一条指令,从主内存中读取x。
  • CPU B试图从主内存中读取x时,CPU A检测到了地址冲突。这时CPU A对相关数据做出响应。此时x 存储于cache a和cache b中,x在chche a和cache b中都被设置为S状态(共享)。
    在这里插入图片描述
  1. 修改数据的执行流程:
  • CPU A 计算完成后发指令需要修改x.
  • CPU A 将x设置为M状态(修改)
  • 并通知缓存了x的CPU B, CPU B将本地cache b中的x设置为I状态(无效),CPUB可能会把数据丢弃,也可能把数据放着,等到读取X的时候,将他给覆盖掉;
  • CPU A 对x进行赋值。
    在这里插入图片描述
  1. 同步数据的执行流程:
  • CPUB发出了读取X的指令
  • CPUB通知CPUA,CPUA将修改后的数据同步到主内存时,cache a 改为E;
  • CPU A同步CPUB的X ,将cache A ,和同步后cache b的x设置为S状态;
    在这里插入图片描述

MESI优化和引入的问题

缓存的一致性消息传递是需要的时间,这就使其切换时产生延迟。当一个缓存被切换状态时,其他缓存收到消息,完成各自的切换并且发出回应消息,这么一长串的时间中,CPU都会等待所有缓存响应完成。可能出现的阻塞都会导致各种各样的性能问题和稳定性问题;

CPU切换状态阻塞解决­存储缓存(Store Bufferes)

比如你需要修改本地缓存中的一条信息,那么你必须将I状态通知到其他拥有该缓存数据的CPU缓存中,并且等待确认。等待确认的过程会阻塞处理器,降低处理器性能;因为这个等待远远比一个指令的执行时间长的多;

Store Bufferes

为了避免CPU运算能力的浪费,Store Bufferes被引入使用,处理器把它想要写入到主内存的值写到缓存,然后继续处理其他事情。当所有失效确认收到时,数据才会被最终提交。
这么做有两个风险:

  • 处理器会尝试从存储缓存(Store Bufferes)中读取值,但他还没有进行提交。这个的解决方案为为Store Forwarding,他使得加载的时候,如果存储缓存中存在,则进行返回。
  • 保存什么时候会完成,无法保证,可能会断电丢失。

store buffer过程图

在这里插入图片描述

MESI失效

当一个对象数据太大,超过了缓存行的容量64KB,无法锁住所有缓存行,会直接升级为总线锁,即该对象的修改操作,直接将总线锁住,其他线程需要修改该对象的都会被阻塞;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值