剖析Disruptor:为什么会这么快?(一)锁的缺点

并发框架Disruptor(核心概念 入门 高性能原理-伪共享 CAS 环形数据 生产和消费模式 高级使用 ) 并发框架Disruptor并发框架DisruptorDisruptor概述背景什么是Disruptor为什么使用DisruptorDisruptor 的核心概念Ring BufferSequenceSequencerSequence BarrierWait StrategyEventEventHandlerProducerDisruptor特性Disruptor入门性能对比测试Disruptor官方性能测试高性能原理伪共享概念什么是伪共享如何解伪共享填充字段@Contended注解Disruptor解决伪共 阅读详情

作者:Trisha’s  译者:张文灼,潘曦  整理和校对:方腾飞,丁一

Martin Fowler写了一篇非常好的文章,里面不仅提到了Disruptor,而且还解释了Disruptor如何应用在LMAX的架构里。里面有提及了一些目前没有涉及的概念,但最经常问到的问题是 “Disruptor究竟是什么?"。

目前我正准备在回答这个问题,但首先回答"为什么它会这么快?"

这些问题持续出现,但是我不能没有说它是干什么的就说它为什么会这么快,不能没有说它为什么这样做就说它是什么东西。

所以我陷入了一个僵局,一个如何写博客的僵局。

要打破这个僵局,我准备以最简单方式回答第一个问题,如果幸运的话,在以后博文里,如果需要解释的话我会重新提回:Disruptor提供了一种线程之间信息交换的方式。

作为一个开发者,因为"线程"一词的出现,我的警钟已经敲响,它意味着并发,而并发是困难的。

并发 01

想象有两个线程尝试修改同一个变量value:

情况一:线程1先到达

  1. 变量value的值变为”blah”。
  2. 然后当线程2到达时,变量value的值变为”blahy”。

情况二:线程2先到达

  1. 变量value的值变为”fluffy”。
  2. 然后当线程1到达时,值变为”blah”。

情况三:线程1与线程2交互

  1. 线程2得到值"fluff"然后赋给本地变量myValue。
  2. 线程1改变value的值为”blah”。
  3. 然后线程2醒来并把变量value的值改为”fluffy”

情况三显然是唯一一个是错误的,除非除非你认为wiki编辑的幼稚做法是正确的(Google Code Wiki,我一直在关注你)。其他两种情况主要是看你的意图和想要达到的效果。线程2可能不会关心变量value的值是什么,主要的意图就是在后面加上字符 ‘y'而不管它原来的值是什么,在这种前提下,情况一和情况二都是正确的。

但是如果线程2只是想把"fluff"改为”fluffy”,那么情况二和三都不正确。假定线程2想把值设为”fluffy”,有几种办法可以解决这个问题:

办法一:悲观锁

(“No Entry”的标志对于在没有在英国开车的人看得明白不?)

悲观锁和乐观锁这两个词通常在我们谈论数据库读写时经常会用到,但原理可以应用到在获得一个对象的锁的情况。

只要线程2一获得Entry的互斥锁,它就会阻击其它线程去改变它,然后它就可以随意做它要做的事情,设置值,然后做其它事情。

你可以想象这里非常耗性能的,因为其它线程在系统各处徘徊着准备要获得锁然后又阻塞。线程越多,系统的响应性就会越慢.

办法二:乐观锁

在这种情况,当线程2需要去写Entry时才会去锁定它.它需要检查Entry自从上次读过后是否已经被改过了。如果线程1在线程2读完后到达并把值改为”blah”,线程2读到了这个新值,线程2不会把"fluffy"写到Entry里并把线程1所写的数据覆盖.线程2会重试(重新读新的值,与旧值比较,如果相等则在变量的值后面附上’y’),这里在线程2不会关心新的值是什么的情况.或者线程2会抛出一个异常,或者会返回一个某些字段已更新的标志,这是在期望把”fluff”改为”fluffy”的情况.举一个第二种情况的例子,如果你和另外一个用户同时更新一个Wiki的页面,你会告诉另外一个用户的线程 Thread 2,它们需要重新加载从Thread1来新的变化,然后再提交它们的内容。

潜在的问题:死锁

锁定会带来各种各样的问题,比如死锁,想象有2个线程需要访问两个资源

如果你滥用锁技术,两个锁都在获得锁的情况下尝试去获得另外一个锁,那就是你应该重启你的电脑的时候了。(校注:作者还挺幽默)

很明确的一个问题:锁技术是慢的..

关于锁就是它们需要操作系统去做裁定。线程就像两姐妹在为一个玩具在争吵,然后操作系统就是能决定他们谁能拿到玩具的父母,就像当你跑向你父亲告诉他你的姐姐在你玩着的时候抢走了你的变形金刚-他还有比你们争吵更大的事情去担心,他或许在解决你们争吵之前要启动洗碗机并把它摆在洗衣房里。如果你把你的注意力放在锁上,不仅要花时间来让操作系统来裁定。Disruptor论文中讲述了我们所做的一个实验。这个测试程序调用了一个函数,该函数会对一个64位的计数器循环自增5亿次。当单线程无锁时,程序耗时300ms。如果增加一个锁(仍是单线程、没有竞争、仅仅增加锁),程序需要耗时10000ms,慢了两个数量级。更令人吃惊的是,如果增加一个线程(简单从逻辑上想,应该比单线程加锁快一倍),耗时224000ms。使用两个线程对计数器自增5亿次比使用无锁单线程慢1000倍。并发很难而锁的性能糟糕。我仅仅是揭示了问题的表面,而且,这个例子很简单。但重点是,如果代码在多线程环境中执行,作为开发者将会遇到更多的困难:

  • 代码没有按设想的顺序执行。上面的场景3表明,如果没有注意到多线程访问和写入相同的数据,事情可能会很糟糕。
  • 减慢系统的速度。场景3中,使用锁保护代码可能导致诸如死锁或者效率问题。

这就是为什么许多公司在面试时会多少问些并发问题(当然针对Java面试)。不幸的是,即使未能理解问题的本质或没有问题的解决方案,也很容易学会如何回答这些问题。

Disruptor如何解决这些问题。

首先,Disruptor根本就不用锁。

取而代之的是,在需要确保操作是线程安全的(特别是,在多生产者的环境下,更新下一个可用的序列号)地方,我们使用CAS(Compare And Swap/Set)操作。这是一个CPU级别的指令,在我的意识中,它的工作方式有点像乐观锁——CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。

注意,这可以是CPU的两个不同的核心,但不会是两个独立的CPU。

CAS操作比锁消耗资源少的多,因为它们不牵涉操作系统,它们直接在CPU上操作。但它们并非没有代价——在上面的试验中,单线程无锁耗时300ms,单线程有锁耗时10000ms,单线程使用CAS耗时5700ms。所以它比使用锁耗时少,但比不需要考虑竞争的单线程耗时多。

回到Disruptor,在在我讲生产者时讲过ClaimStrategy。在这些代码中,你可以看见两个策略,一个是SingleThreadedStrategy(单线程策略)另一个是MultiThreadedStrategy(多线程策略)。你可能会有疑问,为什么在只有单个生产者时不用多线程的那个策略?它是否能够处理这种场景?当然可以。但多线程的那个使用了AtomicLong(Java提供的CAS操作),而单线程的使用long,没有锁也没有CAS。这意味着单线程版本会非常快,因为它只有一个生产者,不会产生序号上的冲突。

我知道,你可能在想:把一个数字转成AtomicLong不可能是Disruptor速度快的唯一秘密。当然,它不是,否则它不可能称为“为什么这么快(第一部分)”。

但这是非常重要的一点——在整个复杂的框架中,只有这一个地方出现多线程竞争修改同一个变量值。这就是秘密。还记得所有的访问对象都拥有序号吗?如果只有一个生产者,那么系统中的每一个序列号只会由一个线程写入。这意味着没有竞争、不需要锁、甚至不需要CAS。在ClaimStrategy中,如果存在多个生产者,唯一会被多线程竞争写入的序号就是 ClaimStrategy 对象里的那个。

这也是为什么Entry中的每一个变量都只能被一个消费者写。它确保了没有写竞争,因此不需要锁或者CAS。

回到为什么队列不能胜任这个工作

因此你可能会有疑问,为什么队列底层用RingBuffer来实现,仍然在性能上无法与 Disruptor 相比。队列和最简单的ring buffer只有两个指针——一个指向队列的头,一个指向队尾:

如果有超过一个生产者想要往队列里放东西,尾指针就将成为一个冲突点,因为有多个线程要更新它。如果有多个消费者,那么头指针就会产生竞争,因为元素被消费之后,需要更新指针,所以不仅有读操作还有写操作了。

等等,我听到你喊冤了!因为我们已经知道这些了,所以队列常常是单生产者和单消费者(或者至少在我们的测试里是)。

队列的目的就是为生产者和消费者提供一个地方存放要交互的数据,帮助缓冲它们之间传递的消息。这意味着缓冲常常是满的(生产者比消费者快)或者空的(消费者比生产者快)。生产者和消费者能够步调一致的情况非常少见。

所以,这就是事情的真面目。一个空的队列:

…

一个满的队列:

(校对注:这应该是一个双向队列)

队列需要保存一个关于大小的变量,以便区分队列是空还是满。否则,它需要根据队列中的元素的内容来判断,这样的话,消费一个节点(Entry)后需要做一次写入来清除标记,或者标记节点已经被消费过了。无论采用何种方式实现,在头、尾和大小变量上总是会有很多竞争,或者如果消费操作移除元素时需要使用一个写操作,那元素本身也包含竞争。

基于以上,这三个变量常常在一个cache line里面,有可能导致false sharing。因此,不仅要担心生产者和消费者同时写size变量(或者元素),还要注意由于头指针尾指针在同一位置,当头指针更新时,更新尾指针会导致缓存不命中。这篇文章已经很长了,所以我就不再详述细节了。

这就是我们所说的“分离竞争点问题”或者队列的“合并竞争点问题”。通过将所有的东西都赋予私有的序列号,并且只允许一个消费者写Entry对象中的变量来消除竞争,Disruptor唯一需要处理访问冲突的地方,是多个生产者写入 Ring Buffer的场景。

总结

Disruptor相对于传统方式的优点:

  1. 没有竞争=没有锁=非常快。
  2. 所有访问者都记录自己的序号的实现方式,允许多个生产者与多个消费者共享相同的数据结构。
  3. 在每个对象中都能跟踪序列号(ring buffer,claim Strategy,生产者和消费者),加上神奇的cache line padding,就意味着没有为伪共享和和非预期的竞争。

校订:需要注意Disruptor2.0使用了与本文中不一样的名字。

转载自并发编程网 – ifeve.com

在美团呆了7年的架构师带你解读Disruptor系列并发框架 前言 理解Disruptor的最佳方式是,将其与一些容易理解和目的相似的东西比较。这里的参照物就是java里的阻塞队列(BlockingQueue)。 与BlockingQueue的异同: 同:目的相同,都是为了在同一进程的线程间传输数据。 异:对消费者多播事件;预分配事件内存;可选无锁。 核心概念 Ring Buffer : 曾经的核心。自从3.0以上,环形缓冲器只作为Disruptor存储和更新数据(事件)的容器。对于一些高级用法,可以完全替换为用户提供的容器。 Sequence:Dis 阅读详情

相关推荐

SpringBoot Disruptor框架遇到的问题

Disruptor的默认策略是BlockingWaitStrategy。在BlockingWaitStrategy内部是使用。

专注基础架构领域 1437

disruptor框架为什么不流行_Java并发编程框架Disruptor

Disruptor是什么?Disruptor是一个高性能的异步处理框架,一个轻量级的JMS,和JDK中的BlockingQueue有相似处,但是它的处理速度非常快,获得2011年程序框架创新大奖,号称“一个线程一秒钟可以处理600W个订单”(这有点吓人吧),并且Disruptor不仅仅只有buffer,它提供的功能非常强大,比如它可以帮助我们轻松构建数据流处理(比如一个数据先交给A和B这2个消费者...

weixin_39643255的博客 3063

并发编程框架Disruptor.zip

Disruptor 是一个 Java 的并发编程框架,大大的简化了并发程序开发的难度,在性能上也比 Java 本身提供的一些并发包要好。 标签:Disruptor

disruptor框架为什么不流行_从构建分布式秒杀系统聊聊Disruptor高性能队列

前言秒杀架构持续优化中,基于自身认知不足之处在所难免,也请大家指正,共同进步。文章标题来自码友的建议,希望可以把阻塞队列ArrayBlockingQueue这个队列替换成Disruptor,由于之前曾接触过这个东西,听说很不错,正好借此机会整合进来。简介LMAX Disruptor是一个高性能的线程间消息库。它源于LMAX对并发性,性能和非阻塞算法的研究,如今构成了Exchange基础架构的核心部...

weixin_39733821的博客 7874

手把手教你学Simulink--基于故障诊断与容错控制的场景实例:电动汽车电机驱动系统的故障检测与隔离

本文介绍了基于Simulink的电动汽车电机驱动系统故障诊断与隔离(FDI)仿真建模方法。通过构建永磁同步电机(PMSM)驱动系统模型,实现了IGBT开路、电流传感器漂移等典型故障的注入与检测。重点阐述了基于观测器的残差生成方法,以及利用Stateflow实现的故障隔离逻辑。仿真结果表明,该系统能在85ms内准确识别故障类型,隔离准确率达97%,并支持容错控制。文章还探讨了AI故障预测、数字孪生等高级应用方向,为电动汽车电驱系统安全提供了完整的仿真解决方案。

MHD0815的博客 961

使用 Cadence ams 仿真器 对 混合信号电路 进行仿真

文章目录Cadence 基于ams仿真器的混合信号电路仿真建立数字(Digital)模块建立 Analog - Digital 混合仿真原理图建立 Analog - Digital 混合仿真config文件配置混合信号电路仿真环境针对混合信号电路demo进行仿真 Cadence 基于ams仿真器的混合信号电路仿真 你好,这里是一篇关于使用 Cadence 软件通过 ams 仿真器对混合信号电路(D...

mymatin1004的博客 2万+

剖析Disruptor:为什么会这么快?(一)锁的缺点(转)

 “Disruptor究竟是什么?"。 目前我正准备在回答这个问题,但首先回答"为什么它会这么快?" 这些问题持续出现,但是我不能没有说它是干什么的就说它为什么会这么快,不能没有说它为什么这样做就说它是什么东西。 所以我陷入了一个僵局,一个如何写博客的僵局。 要打破这个僵局,我准备以最简单方式回答第一个问题,如果幸运的话,在以后博文里,如果需要解释的话我会重新提回:Disruptor提...

soledede 456

别再凭感觉估算!用ADS的EBOND库精确仿真Bonding线寄生电感(附完整参数设置指南)

本文详细介绍了在ADS中使用EBOND库精确仿真Bonding线寄生电感的方法,帮助射频工程师在毫米波频段实现高精度建模。通过三维物理参数和电磁场求解算法,EBOND模型能将误差控制在5%以内,显著提升电路性能。文章还提供了完整的参数设置指南和实战操作步骤,适用于5G毫米波前端模块等高频应用场景。

weixin_28730131的博客 506

剖析Disruptor:为什么如此快之一:(锁是坏的)

Disruptor是一个高性能的java读写缓冲架构,号称能支撑起每秒百万的读写能力,本文是笔者对国外一篇Disruptor介绍性文章进行的翻译。

moodys_zeng的专栏 678

实测对比:DJI O3、Walksnail Avatar、HDZero三大高清图传,谁才是低延迟王者?

本文对DJI O3、Walksnail Avatar V2和HDZero三大高清图传系统进行了实测对比,重点分析其在FPV飞行中的低延迟表现。通过专业测试环境和量化数据,揭示HDZero在原生低延迟架构上的优势,DJI O3在画质与延迟间的平衡,以及Walksnail的动态码率调整特点,为不同场景下的选购提供决策依据。

weixin_30756499的博客 446

步进电机梯形加减速算法的实现

步进电机梯形加减速算法的实现

m0_69251699的博客 1318

Disruptor系列2:Disruptor原理剖析

都说Disruptor是高性能、低延迟的内存队列,每秒可以处理600W的订单,但是它为什么这么快呢?这就需要我们从他的底层设计原理开始剖析。我觉得,学习了他的实现原理,对自身了解Java并发内存结构是有很大的好处的,因为它把如何基于Java内存结构实现高性能的并发操作,解决锁的性能开销问题发挥到了极致。 无锁(Lock-Free) 要想提高内存队列的性能,首先需要解决的就是并发环境下锁的开销问...

Coding Road 3967

管道机器人手臂机械结构设计cad【8张】+三维图+设计说明书+绛重

履带式管道机器人适用于各种管道环境,包括直管道、弯曲管道、水平管道、垂直管道等,其灵活性和适应性使得它可以应对不同形状和尺寸的管道,完成全面的检测和维护工作。管道机器人研究具有重要的现实意义和深远的社会影响,通过提高工作效率、降低人员风险、提升服务质量、节约成本资源和促进科技创新等方面的作用,为现代工程技术领域的发展和进步做出了积极的贡献。管道机器人是一种能够在管道内部执行各种任务的智能机器人,而管道机器人的机械臂对于管道机器人在管道内执行任务的灵活性、稳定性和效率至关重要。

kejigongjiang的博客 943

剖析Disruptor:为什么会这么快?(一)Ringbuffer的特别之处

剖析Disruptor:为什么会这么快?(一)Ringbuffer的特别之处 原文地址:http://ifeve.com/ringbuffer/ 作者:Trisha    译者:寒桐  校对:方腾飞 最近,我们开源了LMAX Disruptor,它是我们的交易系统吞吐量快(LMAX是一个新型的交易平台,号称能够单线程每秒处理数百万的订单)的关键原因。为什么我们要将其开源?我们意识到对

nobody 1003

把 SM30 用顺:从表设计到权限与传输,全面掌握 SAP Table Maintenance Generator

摘要 SAP的Table Maintenance Generator(TMG)是为配置表维护需求设计的经典工具,能够生成标准化的维护对话框(SM30)。它支持单表、维护视图(多表关联)和视图簇(导航式维护)三种维护对象类型。使用TMG前需注意三个关键设置:1)表维护权限选项;2)交付类(决定数据传输行为);3)客户端字段配置。通过SE54/SE11生成维护对话框时,需选择维护类型(一步/两步)和记录例程(控制传输)。TMG生成的对象包含函数组、屏幕和处理模块,但部分标准框架不可修改。实际应用中,可通过参数事

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 176

【Unity实战100例】Unity循环UI界面切换卡片功能

功能包括:可以设置半径,高度,速度,元素数量用户可以根据自己的实际需求接入 制作思路:创建每个元素对应的虚拟点,通过旋转管理这些虚拟点,然后把旋转信息和位置信息同步给实际的元素

纯技术流日常开发商业开发记录总结等 7718
上一篇: RocketMQ之Broker线程模型
下一篇: 剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值