28.Netty源码之缓存一致性协议

Mpsc Queue 基础知识

Mpsc 的全称是 Multi Producer Single Consumer,多生产者单消费者。Mpsc Queue 可以保证多个生产者同时访问队列是线程安全的,而且同一时刻只允许一个消费者从队列中读取数据。 Netty Reactor 线程中任务队列 taskQueue 必须满足多个生产者可以同时提交任务,所以 JCTools 提供的 Mpsc Queue 非常适合 Netty Reactor 线程模型。

Mpsc Queue 有多种的实现类,例如 MpscArrayQueue、MpscUnboundedArrayQueue、MpscChunkedArrayQueue 等。我们先抛开一些提供特性功能的队列,聚焦在最基础的 MpscArrayQueue,回过头再学习其他类型的队列会事半功倍。

首先我们看下 MpscArrayQueue 的继承关系,会发现相当复杂,如下图所示。

image.png

伪共享

除了顶层 JDK 原生的 AbstractCollection、AbstractQueue,MpscArrayQueue 还继承了很多类似于 MpscXxxPad 以及 MpscXxxField 的类。我们可以发现一个很有意思的规律,每个有包含属性的类后面都会被 MpscXxxPad 类隔开。MpscXxxPad 到底起到什么作用呢?我们自顶向下,将所有类的字段合并在一起,看下 MpscArrayQueue 的整体结构。

// ConcurrentCircularArrayQueueL0Pad long p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16, p17; // ConcurrentCircularArrayQueue protected final long mask; protected final E[] buffer; // MpmcArrayQueueL1Pad long p00, p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16; // MpmcArrayQueueProducerIndexField private volatile long producerIndex; // MpscArrayQueueMidPad long p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16, p17; // MpscArrayQueueProducerLimitField private volatile long producerLimit; // MpscArrayQueueL2Pad long p00, p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16; // MpscArrayQueueConsumerIndexField protected long consumerIndex; // MpscArrayQueueL3Pad long p01, p02, p03, p04, p05, p06, p07; long p10, p11, p12, p13, p14, p15, p16, p17;

可以看出,MpscXxxPad 类中使用了大量 long 类型的变量,其命名没有什么特殊的含义,只是起到填充的作用。如果你也读过 Disruptor 的源码,会发现 Disruptor 也使用了类似的填充方法。Mpsc Queue 和 Disruptor 之所以填充这些无意义的变量,是为了解决伪共享(false sharing)问题。

什么是伪共享呢?我们有必要补充这方面的基础知识。在计算机组成中,CPU 的运算速度比内存高出几个数量级,为了 CPU 能够更高效地与内存进行交互,在 CPU 和内存之间设计了多层缓存机制,如下图所示。

image.png

一般来说,CPU 会分为三级缓存,分别为L1 一级缓存、L2 二级缓存和L3 三级缓存。

越靠近 CPU 的缓存,速度越快,但是缓存的容量也越小。

所以从性能上来说,L1 > L2 > L3,容量方面 L1 < L2 < L3。CPU 读取数据时,首先会从 L1 查找,如果未命中则继续查找 L2,如果还未能命中则继续查找 L3,最后还没命中的话只能从内存中查找,读取完成后再将数据逐级放入缓存中。

此外,多线程之间共享一份数据的时候,需要其中一个线程将数据写回主存,其他线程访问主存数据。

由此可见,引入多级缓存是为了能够让 CPU 利用率最大化。如果你在做频繁的 CPU 运算时,需要尽可能将数据保持在缓存中。那么 CPU 从内存中加载数据的时候,是如何提高缓存的利用率的呢?

这就涉及缓存行(Cache Line)的概念,Cache Line 是 CPU 缓存可操作的最小单位,CPU 缓存由若干个 Cache Line 组成。

Cache Line 的大小与 CPU 架构有关,在目前主流的 64 位架构下 ,Cache Line 的大小通常为 64 Byte。Java 中一个 long 类型是 8 Byte,所以一个 Cache Line 可以存储 8 个 long 类型变量。

CPU 在加载内存数据时,会将相邻的数据一同读取到 Cache Line 中,因为相邻的数据未来被访问的可能性最大,这样就可以避免 CPU 频繁与内存进行交互了。

伪共享问题是如何发生的呢?它又会造成什么影响呢?我们使用下面这幅图进行讲解。

image.png

假设变量 A、B、C、D 被C1和C2加载到同一个 Cache Line,它们会被高频地修改。

当线程 1 在 CPU Core1 中中对变量 A 进行修改,修改完成后 CPU Core1 会通知其他 CPU Core 该缓存行已经失效。

然后线程 2 在 CPU Core2 中对变量 C 进行修改时,发现 Cache line 已经失效,此时 CPU Core1 会将数据重新写回内存,CPU Core2 再从内存中读取数据加载到当前 Cache line 中。

由此可见,如果同一个 Cache line 被越多的线程修改,那么造成的写竞争就会越激烈,数据会频繁写入内存,导致性能浪费。

所以如何让一个缓存行尽量被更少的线程修改呢?

原来一个缓存行被多个线程修改,是因为一个缓存行存储了多个数据,每个数据可能由不同的线程修改。

所以我们可以让一个缓存行只存储一个数据。这样可以降低多个线程同时访问一个数据的概率。

题外话,多核处理器中,每个核的缓存行内容是如何保证一致的呢?

有兴趣的同学可以深入学习下缓存一致性协议 MESI。

对于伪共享问题,我们应该如何解决呢?Disruptor 和 Mpsc Queue 都采取了空间换时间的策略,让不同线程共享的对象加载到不同的缓存行即可。下面我们通过一个简单的例子进行说明。

public class FalseSharingPadding {    protected long p1, p2, p3, p4, p5, p6, p7;    protected volatile long value = 0L;    protected long p9, p10, p11, p12, p13, p14, p15; }

从上述代码中可以看出,变量 value 前后都填充了 7 个 long 类型的变量。这样不论在什么情况下,都可以保证在多线程访问 value 变量时,value 与其他不相关的变量处于不同的 Cache Line,如下图所示。

image.png

伪共享问题一般是非常隐蔽的,在实际开发的过程中,并不是项目中所有地方都需要花费大量的精力去优化伪共享问题。CPU Cache 的填充本身也是比较珍贵的,我们应该把精力聚焦在一些高性能的数据结构设计上,把资源用在刀刃上,使系统性能收益最大化。

使用缓存行的对齐能够提高效率,也就是让数据位于同一缓存行,会浪费内存(会定义很多变量),但是能提升效率。

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

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

至此,我们知道 Mpsc Queue 为了解决伪共享问题填充了大量的 long 类型变量,造成源码不易阅读。

因为变量填充只是为了提升 Mpsc Queue 的性能,与 Mpsc Queue 的主体功能无关。

接下来我们先忽略填充变量,开始分析 Mpsc Queue 的基本实现原理。

缓存一致性协议(MESI)

在目前主流的计算机中,cpu执行计算的主要流程如图所示:

image.png

数据加载的流程如下:

1.将程序和数据从硬盘加载到内存中

2.将程序和数据从内存加载到缓存中(目前三级缓存,数据加载顺序:L3->L2->L1)

3.CPU将缓存中的数据加载到寄存器中,并进行运算

4.CPU会将数据刷新回缓存,并在一定的时间周期之后刷新回内存

缓存一致性协议发展背景

现在的CPU基本都是多核CPU,服务器更是提供了多CPU的支持,而每个核心也都有自己独立的缓存,当多个核心同时操作多个线程对同一个数据进行更新时,如果核心2在核心1还未将更新的数据刷回内存之前读取了数据,并进行操作,就会造成程序的执行结果造成随机性的影响,这对于我们来说是无法容忍的。

而总线加锁是对整个内存进行加锁,在一个核心对一个数据进行修改的过程中。

其他的核心也无法修改内存中的其他数据,这样对导致CPU处理性能严重下降。

缓存一致性协议提供了一种高效的内存数据管理方案。

它只会对单个缓存行(缓存行是缓存中数据存储的基本单元)的数据进行加锁,不会影响到内存中其他数据的读写。

因此,我们引入了缓存一致性协议来对内存数据的读写进行管理。

MESI协议

缓存一致性协议有MSI,MESI,MOSI,Synapse,Firefly及DragonProtocol等等,接下来我们主要介绍MESI协议。

MESI分别代表缓存行数据所处的四种状态,通过对这四种状态的切换,来达到对缓存数据进行管理的目的。

| 状态 | 描述 | 监听任务 | | ------------------ | ------------------------------------ | ------------------------------------------------------------------ | | M 修改(Modify) | 该缓存行有效,数据被修改了,和内存中的数据不一致,数据只存在于本缓存行中 | 缓存行必须时刻监听所有试图读该缓存行相对应的内存的操作,其他缓存须在本缓存行写回内存并将状态置为E之后才能操作该缓存行对应的内存数据 | | E 独享、互斥(Exclusive) | 该缓存行有效,数据和内存中的数据一致,数据只存在于本缓存行中 | 缓存行必须监听其他缓存读主内存中该缓存行相对应的内存的操作,一旦有这种操作,该缓存行需要变成S状态 | | S 共享(Shared) | 该缓存行有效,数据和内存中的数据一致,数据同时存在于其他缓存中 | 缓存行必须监听其他缓存是该缓存行无效或者独享该缓存行的请求,并将该缓存行置为I状态 | | I 无效(Invalid) | 该缓存行数据无效 | 无 |

备注

1.MESI协议只对汇编指令中执行加锁操作的变量有效,表现到java中为使用voliate关键字定义变量或使用加锁操作。volatile是Java这种高级语言中的一个关键字,要实现这个volatile的功能,需要借助MESI! CPU有缓存一致性协议:MESI,这不错。但MESI并非是无条件生效的! 不是说CPU支持MESI,那么你的变量就默认能做到缓存一致了。 https://www.zhihu.com/question/296949412 ​ 2.对于汇编指令中执行加锁操作的变量,MESI协议在以下两种情况中也会失效: ​ 一、CPU不支持缓存一致性协议。 ​ 二、该变量超过一个缓存行的大小,缓存一致性协议是针对单个缓存行进行加锁,此时,缓存一致性协议无法再对该变量进行加锁,只能改用总线加锁的方式。 ​ 其实这里也是分段加锁 提高并发度。

MESI工作原理:(此处统一默认CPU为单核CPU,在多核CPU内部执行过程和下面流程一致)

1、CPU1从内存中将变量a加载到缓存中,并将变量a的状态改为E(独享),并通过总线嗅探机制对内存中变量a的操作进行嗅探

image.png

2、此时,CPU2读取变量a,总线嗅探机制会将CPU1中的变量a的状态置为S(共享),并将变量a加载到CPU2的缓存中,状态为S

image.png

3、CPU1对变量a进行修改操作,此时CPU1中的变量a会被置为M(修改)状态,而CPU2中的变量a会被通知,改为I(无效)状态,此时CPU2中的变量a做的任何修改都不会被写回内存中(高并发情况下可能出现两个CPU同时修改变量a,并同时向总线发出将各自的缓存行更改为M状态的情况,此时总线会采用相应的裁决机制进行裁决,将其中一个置为M状态,另一个置为I状态,且I状态的缓存行修改无效)

image.png

4、CPU1将修改后的数据写回内存,并将变量a置为E(独占)状态

image.png

5、此时,CPU2通过总线嗅探机制得知变量a已被修改,会重新去内存中加载变量a,同时CPU1和CPU2中的变量a都改为S状态

image.png

在上述过程第3步中,CPU2的变量a被置为I(无效)状态后,只是保证变量a的修改不会被写回内存,但CPU2有可能会在CPU1将变量a置为E(独占)状态之前重新读取内存中的变量a,这个取决于汇编指令是否要求CPU2重新加载内存。

总结

以上就是MESI的执行原理,MESI协议只能保证并发编程中的可见性,并未解决原子性和有序性的问题,所以只靠MESI协议是无法完全解决多线程中的所有问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/86165.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pycharm离线安装依赖包

一、对于单个下载离线包&#xff0c;然后安装 1、先去https://pypi.org/网站下载离线包&#xff0c;下载到本地&#xff1b; 2、从磁盘中找到刚刚下载包&#xff0c;点击确定就可以安装了 二、将本地项目所有依赖包全部下载下来&#xff0c;然后批量在另一个项目&#xff…

造个破谣而已,咋还用上AI了?

最近&#xff0c;央视等各大媒体纷纷曝光了全国多起用AI炮制网络谣言的案例&#xff0c;其中涉及灾害、安全事故、刑事案件等多类谣言内容&#xff0c;造谣的方式更是从文案到图片、视频不一而足。 看到这样的消息&#xff0c;总是会加重我们对AI的担忧&#xff0c;联想到此前的…

windows使用/服务(13)戴尔电脑怎么设置通电自动开机

戴尔pc机器通电自启动 1、将主机显示器键盘鼠标连接好后&#xff0c;按主机电源键开机 2、在开机过程中按键盘"F12",进入如下界面&#xff0c;选择“BIOS SETUP” 3、选择“Power Management” 4、选择“AC Recovery”&#xff0c;点选“Power On”&#xff0c;点击“…

C语言每日一题:16:数对。

思路一&#xff1a;基本思路 1.x,y均不大于n&#xff0c;就是小于等于n。 2.x%y大于等于k。 3.一般的思路使用双for循环去遍历每一对数。 代码实现&#xff1a; #include <stdio.h> int main() {int n 0;int k 0;//输入scanf("%d%d", &n, &k);int x…

springboot第35集:微服务与flutter安卓App开发

Google Playplay.google.com/apps/publis…[1]应用宝open.qq.com/[2]百度手机助手app.baidu.com/[3]360 手机助手dev.360.cn/[4]vivo 应用商店dev.vivo.com.cn/[5]OPPO 软件商店&#xff08;一加&#xff09;open.oppomobile.com/[6]小米应用商店dev.mi.com/[7]华为应用市场dev…

Pycharm解决启动时候索引慢的问题

设置里去掉update里面的两个勾 shared indexes中&#xff0c;把自动下载索引改成不下载使用本地索引

综合能源系统(6)——综合能源综合评估技术

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 综合能源系统是多种能源系统非线性耦合的、多时间与空间尺度耦合的“源-网-荷一储”一体化系统&#xff0c;通过能源耦合、多能互补&#xff0c;能够实现能源的高效利用&#xff0c;并提高新能源的利用水平。对…

沁恒ch32V208处理器开发(三)GPIO控制

目录 GPIO功能概述 CH32V2x 微控制器的GPIO 口可以配置成多种输入或输出模式&#xff0c;内置可关闭的上拉或下拉电阻&#xff0c;可以配置成推挽或开漏功能。GPIO 口还可以复用成其他功能。端口的每个引脚都可以配置成以下的多种模式之一&#xff1a; 1 浮空输入 2 上拉输入…

安装使用IDEA,修改样式,配置服务,构建Maven项目(超级详细版)

目录 前言&#xff1a; 一&#xff0c;安装 1.1打开官网JetBrains: Essential tools for software developers and teams点击 Developer Tools&#xff0c;再点击 Intellij IDEA 2.点击下载​编辑 3.选择对应的版本&#xff0c;左边的 Ultimate 版本为旗舰版&#xff0c;需要…

C++ ModBUS TCP客户端工具 qModMaster 介绍及使用

qModMaster工具介绍 QModMaster是一个基于Qt的Modbus主站&#xff08;Master&#xff09;模拟器&#xff0c;用于模拟和测试Modbus TCP和RTU通信。它提供了一个直观的图形界面&#xff0c;使用户能够轻松设置和发送Modbus请求&#xff0c;并查看和分析响应数据。 以下是QModM…

图像处理技巧形态学滤波之腐蚀操作

1. 引言 欢迎回来&#xff0c;我的图像处理爱好者们&#xff01;今天&#xff0c;让我们深入研究图像处理领域中的形态学计算。这些非线性的图像处理技术允许我们操纵图像中对象的形状和结构。在本系列中&#xff0c;我们将依次介绍四种基本的形态学操作&#xff1a;腐蚀、膨胀…

Kafka 概述

Kafka 为什么需要消息队列&#xff08;MQ&#xff09;使用消息队列的好处&#xff08;1&#xff09;解耦&#xff08;2&#xff09;可恢复性&#xff08;3&#xff09;缓冲&#xff08;4&#xff09;灵活性 & 峰值处理能力&#xff08;5&#xff09;异步通信 消息队列的两…

Neo4j笔记-数据迁移(导出/导入)

这里先说明以下几点&#xff1a; Neo4j在4.0下版本默认的库名是&#xff1a;graph.db Neo4j在4.0上版本默认的库名是&#xff1a;neo4j.db 不管是Neo4j&#xff0c;还是Neo4j Desktop&#xff0c;都会在bin目录下有neo4j、neo4j-admin软件。在conf目录下&#xff0c;有neo4j.…

MySQL SUBSTRING_INDEX() 函数的详细介绍

MySQL SUBSTRING_INDEX() 从给定字符串中返回指定数量的分隔符出现之前的子字符串。 当指定数字为正数时从最终分隔符的左侧返回子字符串&#xff0c;当指定数字为负数时从最终分隔符的右侧返回子字符串。 如果指定的次数大于分隔符的出现次数&#xff0c;则返回的子字符串将…

【每日一题】1572. 矩阵对角线元素的和

【每日一题】1572. 矩阵对角线元素的和 1572. 矩阵对角线元素的和题目描述解题思路 1572. 矩阵对角线元素的和 题目描述 给你一个正方形矩阵 mat&#xff0c;请你返回矩阵对角线元素的和。 请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。 示例 1&a…

pycharm的Terminal中如何设置打开anaconda3的虚拟环境

在pycharm的File -> Settings -> Tools -> Terminal下面&#xff0c;如下图所示 修改为红框中内容&#xff0c;然后关闭终端在重新打开终端&#xff0c;即可看到anaconda3的虚拟环境就已经会被更新

数学建模(一)前继概念

课程推荐&#xff1a;数学建模老哥_哔哩哔哩_bilibili 目录 一、什么是数学建模&#xff1f; 二、数学建模的一般步骤 三、数学建模赛题类型 1.预测型 2. 评价类 3.机理分析类 4. 优化类 一、什么是数学建模&#xff1f; 数学建模是利用数学方法解决实际问题的一种实践。…

《Linux从练气到飞升》No.12 Linux进程概念

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…

maven 删除下载失败的包

本文介绍了当Maven包报红时&#xff0c;使用删除相关文件的方法来解决该问题。文章详细说明了_remote.repositories、.lastUpdated和_maven.repositories文件的作用&#xff0c;以及如何使用命令行删除这些文件。这些方法可以帮助开发者解决Maven包报红的问题&#xff0c;确保项…

css-4:元素水平垂直居中的方法有哪些?如果元素不定宽高呢?

1、背景 在开发中&#xff0c;经常遇到这个问题&#xff0c;即让某个元素的内容在水平和垂直方向上都居中&#xff0c;内容不仅限于文字&#xff0c;可能是图片或其他元素。 居中是一个非常基础但又是非常重要的应用场景&#xff0c;实现居中的方法存在很多&#xff0c;可以将这…