MESI

  • java代码执行流程
  • 硬件缓存锁定机制
  • MESI
    • MESI协议缓存状态
    • MESI状态转换
      • 多核缓存协同操作
      • 单核读取
      • 双核读取
      • 修改数据
      • 同步数据
    • 缓存行伪共享
  • MESI优化和他们引入的问题
    • CPU切换状态阻塞解决­存储缓存(Store Bufferes)
      • Store Bufferes
      • Store Bufferes的风险
    • 硬件内存模型
      • 失效队列
      • 内存屏障

java代码执行流程

  • 一个java类通过javac编译成字节码文件后会通过类加载子系统装载进元空间
  • 在堆中生成Class实例
  • 创建线程
  • 要执行的方法的字节码会加到虚拟机栈栈帧中,执行
  • 执行引擎(解释器/JIT)会将字节码翻译成汇编指令(硬件原语)

  • 硬件再将汇编指令翻译成二进制,速度非常快
  • 这个二进制代码对应的线程被CPU调度后就会被执行
    因为jvm是klt的,操作系统中会会维护一个线程常量池,其中的每个线程与jvm上的线程一一对应

硬件缓存锁定机制

加了volatile
汇编指令LOCK会触发硬件缓存锁定机制(总线锁,缓存一致性协议),使得在锁定操作期间不会响应总线控制请求。
硬件缓存锁定机制有两种:

  • 总线锁
    早期用总线锁保证缓存一致,因为cpu是通过总线访问内存的,一个核的线程对总线加锁后只有他能访问内存 ,其他线程就不能了。也就是说只要使用了总线锁,我们的cpu就是单核的。
  • 缓存一致性协议
    缓存一致性是一个协议,目前实现的使用最多的就是mesi去保证缓存一致性。
    当一个缓存行装不下一个数据的时候,这时mesi就会升级成总线锁。

MESI

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

MESI协议缓存状态

MESI 是指4中状态的首字母。每个缓存行(Cache line)有4个状态,可用2个bit表示,它们分别是:

状态 描述 监听任务
M 修改 (Modified) 该Cache line有效,数据被修改了,和内存中 的数据不一致,数据只存在于本Cache中。 缓存行必须时刻监听所有试图读该缓存行相对就主缓存行写回主存并将状态变成S(共享
E 独享、互斥 (Exclusive) 该Cache line有效,数据和内存中的数据一 致,数据只存在于本Cache中。 缓存行也必须监听其它缓存读主存中该缓存行的操 变成S(共享)状态。
S 共享 (Shared) 该Cache line有效,数据和内存中的数据一 致,数据存在于很多Cache中。 缓存行也必须监听其它缓存使该缓存行无效或者独 成无效(Invalid)。
I 无效 (Invalid) 该Cache line无效。

注意:

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

MESI状态转换

状态转换图

  • 触发事件

    • 本地读取(Local read)
      本地cache读取本地cache数据
    • 本地写入(Local write)
      本地cache写入本地cache数据
    • 远端读取(Remote read)
      其他cache读取本地cache数据(应该是从主存读数据)
    • 远端写入(Remote write)
      其他cache写入本地cache数据(应该是内存的数据被修改了)
  • cache分类
    前提:所有的cache共同缓存了主内存中的某一条数据
    注意:本地的事件触发 本地cache和触发cache为相同。

    • 本地cache:指当前cpu的cache。
    • 触发cache:触发读写事件的cache。
    • 其他cache:指既除了以上两种之外的cache。


cpu会监听总线中被lock前缀修饰的变量,为其分配四种状态的一种,如果是第一次读就会给他分配E状态, 后来又有别的线程读了这个变量 ,那就会更改每个的状态为S

如果两个线程都要对这个变量修改, 那就各自对自己的缓存行去加锁,如果加锁成功则就可以修改,状态由S->M。加锁的同时还要向外部发一个本地写缓存行的信号,这样其他拥有这个变量的线程就知道已经被别人写了,这时候状态由S->I 并被丢弃。
如果两个线程都在自己内部加锁成功,都往外发本地写缓存行的信号了,那总线就会裁决

多核缓存协同操作

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

单核读取

那么执行流程是:
CPU A发出了一条指令,从主内存中读取x。
从主内存通过bus读取到缓存中(远端读取Remote read),这时该Cache line修改为E状态(独享).

双核读取

那么执行流程是:

  • CPU A发出了一条指令,从主内存中读取x。
  • CPU A从主内存通过bus读取到 cache a中并将该cache line 设置为E状态。
  • CPU B发出了一条指令,从主内存中读取x。
  • CPU B试图从主内存中读取x时,CPU A检测到了地址冲突。这时CPU A对相关数据做出响应。此时x 存储于cache a和cache b中,x在chche a和cache b中都被设置为S状态(共享)。

修改数据

那么执行流程是:

  • CPU A 计算完成后发指令需要修改x.
  • CPU A 将x设置为M状态(修改)并通知缓存了x的CPU B, CPU B将本地cache b中的x设置为I状态(无效)
  • CPU A 对x进行赋值。

同步数据

那么执行流程是:

  • CPU B 发出了要读取x的指令。
  • CPU B 通知CPU A,CPU A将修改后的数据同步到主内存时cache a 修改为E(独享)
  • CPU A同步CPU B的x,将cache a和同步后cache b中的x设置为S状态(共享)。

缓存行伪共享

什么是伪共享?

CPU缓存系统中是以缓存行(cache line)为单位存储的。目前主流的CPU Cache 的Cache Line 大小都是64Bytes。在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享(False Sharing。

例如:
举个例子: 现在有2个long 型变量 a 、b,如果有t1在访问a,t2在访问b,而a与b刚好在同一个
cache line中,此时t1先修改a,将导致b被刷新!

怎么解决伪共享?

Java8中新增了一个注解:@sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended才会生效。

@sun.misc.Contended
public final static class TulingVolatileLong {public volatile long value = 0L;//public long p1, p2, p3, p4, p5, p6;
}

MESI优化和他们引入的问题

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

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

Store Bufferes

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

store buffers

为了避免这种CPU运算能力的浪费,Store Bufferes被引入使用。处理器把它想要写入到主存的值写到缓存即store buffers,然后继续去处理其他事情。当所有失效确认(Invalidate Acknowledge)都接收到时,数据才会最终被提交。

写到store buffer的原因

写到store buffer的原因因为写操作后发本地写缓存行到其他cpu需要时间,为了不影响我接下来的操作,就先写到store buffer,继续执行后续的,等到另外一个核收到消息并把该变量失效后(失效即放到queue中排队,等cpu有空就会去这个失效队列中把这些变量拿掉),并发消息给刚刚发通知的cpu核,这时这个核再从store buffer中把刚刚改的同步到缓存行并写到主内存,但是同步回主内存的时机不确定。

Store Bufferes的风险

Store Bufferes的风险:

  • 第一、就是处理器会尝试从存储缓存(Store buffer)中读取值,但它还没有进行提交。这个的解决方案称为Store Forwarding,它使得加载的时候,如果存储缓存中存在,则进行返回。
  • 第二、保存什么时候会完成,这个并没有任何保证。

硬件内存模型

失效队列

执行失效也不是一个简单的操作,它需要处理器去处理。另外,存储缓存(Store Buffers)并不是无穷大的,所以处理器有时需要等待失效确认的返回。这两个操作都会使得性能大幅降低。为了应付这种情况,引入了失效队列。它们的约定如下:

  • 对于所有的收到的Invalidate请求,Invalidate Acknowlege消息必须立刻发送
  • Invalidate并不真正执行,而是被放在一个特殊的队列中,在方便的时候才会去执行。
  • 处理器不会发送任何消息给所处理的缓存条目,直到它处理Invalidate。

内存屏障

即便是这样处理器已然不知道什么时候优化是允许的,而什么时候并不允许。
干脆处理器将这个任务丢给了写代码的人。这就是内存屏障(Memory Barriers)。

  • 写屏障 Store Memory Barrier(a.k.a. ST, SMB, smp_wmb)
    是一条告诉处理器在执行这之后的指令之前,应用所有已经在存储缓存(store buffer)中的保存的指令。
    在更新数据之前必须将所有存储缓存(store buffer)中的指令执行完毕。

  • 读屏障Load Memory Barrier (a.k.a. LD, RMB, smp_rmb)
    是一条告诉处理器在执行任何的加载前,先应用所有已经在失效队列中的失效操作的指令。
    在读取之前将所有失效队列中关于该数据的指令执行完毕。

【并发】3、MESI相关推荐

  1. 两个例子详解并发编程的可见性问题和有序性问题,通过volatile保证可见性和有序性以及volatile的底层原理——缓存一致性协议MESI和内存屏障禁止指令重排

    1. 并发编程的可见性问题 2. 并发编程的有序性问题 3. 使用volatile关键字解决可见性问题 4. 可见性问题的本质--缓存不一致 因为cpu执行速度很快,但是内存执行速度相对于CPU很慢, ...

  2. java内存分配模型优点_高并发实战(二)-并发基础 缓存 MESI 内存模型

    左图为高速缓存 右图为多级缓存 数据的读取和存储都经过高速缓存,CPU核心与高速缓存有一条特殊的快速通道.主存与高速缓存都是连接在系统总线上,当然其他组件也是在此基础上进行通信的. 在高速缓存出现后不 ...

  3. 4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?

    MESI一致性协议 小陈:老王,上一章你让我看看MESI一致性协议,我大概了解了一下. 老王:哦,来说说你对MESI一致性协议的理解 小陈:MESI协议也叫做缓存一致性协议,主要是用来进行协调多核CP ...

  4. 并发-MESI缓存一直协议详解

    并发-MESI缓存一直协议详解 CPU缓存一致性协议MESI CPU高速缓存(Cache Memory) CPU为何要有高速缓存 目前流行的多级缓存结构 多核CPU多级缓存一致性协议MESI MESI ...

  5. 【原理/Java并发】从volatile到MESI协议

    文章目录 1 前言 2 有序性 2.1 编译器层面的内存屏障 2.2 CPU层面的内存屏障 3 可见性 3.1 MESI协议 3.2 Store Buffer 和 Invalid Queue 3.3 ...

  6. 并发编程中的可见性——缓存一致性协议MESI

    一.应用场景展示--多线程计数 1.全局原子操作计数的数据流图 核心问题就是不同CPU如何在同一时刻看到同样的全局变量值. 2.每线程自增计数的数据流图 二.cache原理和实现 1. cache g ...

  7. “了解高并发底层原理”,面试官:讲一下MESI(缓存一致性协议)吧

    目录 前言: 1.什么是(Who): 2.为何来(How): 2.1缓存不一致带来的后果 2.2解决方法: 3.是什么(What) 3.1数据在缓存中的四种状态: 3.2MESI的六种消息(请求消息和 ...

  8. 并发编程实战-MESI缓存一致性协议

    大家好,最近呢我对并发编程展现出了兴趣(没办法,别人都会你不会说不过去啊),然后我就要奋发图强学好并发编程,那么接下来让我们一起进入学习吧.我们在学习并发编程实战之前,应该先要了解一下我们的cpu缓存 ...

  9. Java并发编程:JMM和volatile关键字

    Java内存模型 随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内置高速缓存区.高速缓存区的加入使得CP ...

最新文章

  1. 狂神Spring Boot 员工管理系统 超详细完整实现教程(小白轻松上手~)
  2. table 隔列换色
  3. Mina airQQ聊天 client篇(三)
  4. hdu 2473(并查集+删除操作)
  5. java为什么被开发者_为什么开发者对Java 9如此的兴奋
  6. 漫画:程序员之间的真爱,好暖啊!
  7. 响应式网站关于资源跨域问题
  8. mybatis动态sql片段与分页,排序,传参的使用与一对多映射与resultMap使用
  9. 杰奇python采集器_linux下能完美运行的杰奇采集器ckp
  10. 读书笔记(一):双脑记
  11. 科比投篮选择——数据采集
  12. 计算机应用能力考试ppt2003,全国专业技术人员计算机应用能力考试_PPT_2003_题库版.docx...
  13. Livid: 消失的未来
  14. php 计算函数 相加,比较,相除,相减,求余,相乘
  15. HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取
  16. 调用HMS SDK接口报错6003
  17. ESC/POS 打印机指令
  18. buaacoding C.真心话大冒险
  19. java计算机毕业设计居家养老系统MyBatis+系统+LW文档+源码+调试部署
  20. Mac突然没有声音了,音频和视频都不能播放了。

热门文章

  1. vim中显示和关闭行号
  2. 【蓝桥杯】——键盘是使用
  3. plt.plot()函数详解
  4. 谷歌浏览器F12断点调试按钮说明
  5. Java Swing实现仿win7计算器
  6. BZOJ4755 [Jsoi2016]扭动的回文串
  7. 视频号无人直播怎么弄?微信视频号无人直播教程【无需软件】
  8. java vpa_使用VPA快速洞悉Java应用性能瓶颈
  9. 解决安卓应用程序未安装的三种方法
  10. 51nod 1001