写点什么

Volatile:内存屏障原理应该没有比这篇文章讲的更清楚了

  • 2021 年 11 月 11 日
  • 本文字数:1723 字

    阅读完需:约 6 分钟

最近在研究 volatile 的过程中发现内存屏障这东西如果不搞明白,Java 中的 volatile 就别想学透,所以花了较长时间来研究这块。看了很多资料,写了很多代码


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


测试,这篇文章就来总结下我目前认知中的内存屏障。


CPU 缓存


=====


如果你不了解讲内存屏障为什么要讲 CPU 缓存,接着往后看。


学过《计算机组成原理》的同学应该都听过一个词:时钟周期。什么是时钟周期呢?通俗点来讲就是 CPU 完成一个基本动作需要的时间周期。对硬件有点认识的同学都知道看 CPU 好不好一定要看的一个参数:多少多少 GHZ。这个 GHZ 跟时钟周期之间是存在一定的换算关系的,感兴趣的同学可以去自行研究。说明一下:不了解这层换算关系不影响你看后面的内容,只要你对时钟周期有一个基本认知就可以了。


在很早以前,CPU 里面是没有缓存这块区域的,就是 CPU 直接读写内存。那后面为什么在 CPU 中增加了缓存呢?因为 CPU 的运行效率与读写内存的效率存在着巨大的鸿沟,在读写内存过程中带来的等待浪费了很大的 CPU 算力。现在最新的内存是 DDR4 规格,但是向内存中写入数据,据权威资料,需要 107 个 CPU 时钟周期,即 CPU 的运行效率是写内存的 107 倍。如果 CPU 只执行写操作需要一个时钟周期,那 CPU 等待这个写完成需要等待 106 个时钟周期,是不是很浪费 CPU 算力?那如何解决呢?就跟我们工作中发现 MySQL 出现读写瓶颈如何解决是一样的思维:加缓存


拿我们今天主流的 CPU 架构来说,现在的 CPU 主要采用三层缓存:


  1. L1、L2 缓存成为本地核心内缓存,即一个核一个。如果你的机器是 4 核,

  2. 那就是有 4 个 L1+4 个 L2

  3. L3 缓存是所有核共享的。即不管你的 CPU 是几核,这个 CPU 中只有一个 L3

  4. L1 缓存的大小是 64K,即 32K 指令缓存+32K 数据缓存。L2 是 256K,L3 是 2M。这不是绝对的目前 Intel CPU 基本是这样的设计


这里还补充一个知识点:缓存行(Cache-line)。缓存行是 CPU 缓存存储数据的最小单位,大小为 64B。这块如果展开来讲要讲很久很久,本篇文章就不展开讲了,有兴趣的朋友可以自行研究。如果你没有学习过计算机硬件相关知识,可能看不懂。


根据哲学的矛盾相对论:任何问题的解决方案都是一个利与弊共存的矛盾体。加缓存的确有效提升了 CPU 的执行效率,但是 CPU 缓存间的数据一致性、CPU 缓存与内存间的数据一致性就是不得不去思考与解决的问题了。而且还得保证解决这两层数据一致性的效率要高于不加缓存前浪费的 CPU 算力,不然这个方案就是一套伪方案:听起来高大上,不解决问题。



缓存的一致性


======


MESI 协议就是为了保证 CPU 各核的缓存、内存间的数据一致性而生的,没有了解过的可以百度普及一下,这个比较简单。这里拓展两点:


一、CPU 运算单元与 L1 缓存间为什么要增加 buffer?CPU 实现各个核的缓存与内存间的数据一致性的思路有点像 socket 的三次握手:CPU0 修改了某个数据,需要广播告诉其他 CPU,这时候 CPU0 进入阻塞状态等待其他 CPU 修改其缓存中的状态,待其他 CPU 都修改完状态返回应答消息后才进入运行状态。虽然这个阻塞的时间很短,但是在 CPU 的时间里就很长了,为了保证这部分阻塞时间也能得到充分利用,于是加入了 buffer。将预读信息存储进去,这样 CPU 解除阻塞后就可以直接从 buffer 拿出请求处理。


二、MESI 协议的实现思路是:如果 CPU0 修改了某个数据,需要广播给其他 CPU,缓存中没有这个数据的 CPU 丢弃这个广播消息,缓存中有这个数据的 CPU 监听到这个广播后会将相应的缓存行改为 invalid 状态,这样 CPU 在下次读取这个数据的时候发现缓存行失效,就去内存中读取。这里面童鞋们有没有发现一个问题:只要存在数据修改,CPU 就需要去内存取数据,那为什么不实现 CPU 缓存能共享数据呢?这样 CPU 在下次读取的时候去 CPU0 的缓存行去读取就可以啦,而且性能更高。现在的 CPU 也的确实现了这个思路,对应的协议就是:AMD 的 MOESI、Intel 的 MESIF。



内存屏障的由来


=======


对于 CPU 的写,目前主流策略有两种:


1、write back:即 CPU 向内存写数据时,先把真实数据放入 store buffer 中,待到某个合适的时间点,CPU 才会将 store buffer 中的数据刷到内存中,而且这两个操作是异步的。这在多线程环境中,有些情况下是可以接受的,但是有些情况是不可接受的,为了让程序员有能力根据业务需要达到同步完成,就设计了内存屏障。

评论

发布
暂无评论
Volatile:内存屏障原理应该没有比这篇文章讲的更清楚了