disruptor_harden vol1disruptor_lmax.disruptorr

电脑杂谈  发布时间:2017-02-24 20:15:19  来源:网络整理

LMAX交易出品的一个高效的无锁并发框架

它高效核心在于其无锁队列RingBuffer的独特设计。

它可以用来进行线程之间的数据交互。

并发编程中,为了保证准确性,引入了锁的机制,包括乐观锁,悲观锁等。有锁就涉及到资源的竞争,竞争就可能出现死锁,这样的情况下,你只能重启你的机器了。

从1加到10亿,(测试机器 Mac Air)

单线程简单自增,耗时5S左右

单线程加锁自增,仅仅简单加锁,没有竞争,耗时40S左右,慢一个数量级

考虑并发

两个线程简单自增,耗时减少,但是结果无法保证准确性,比如存在脏读等问题。

两个线程加锁自增,按照理解,时间应该减半,但是因为引入了锁机制,导致竞争,实际时间更加长。

disruptor在需要保证线程安全的地方,用到了CAS操作,这是一个CPU级别的指令,类似于乐观锁,即Campare and Set/Swap. JA 从1.5版本新引入了AtomicLong等支持CAS指令的数据结构。

测试代码可以看出来,在引入了AtomicLong的情况下,单线程,耗时为18S左右,多线程,耗时26S左右。

测试代码

既然是数据交换,那就存在一个产生数据(producer),一个消费数据(consumer),和数据存储(RingBuffer),数据存储,我们可以理解,应该是一个队列,数据先到先处理,在java类库中,提供了例如LinkedBlockingQueue、ArrayBlockingQueue等,而disruptor之所以高效,是因为它没有直接使用java类库中提供的队列,而是自己写的RingBuffer。disruptor

它有什么特点呢?

顾名思义,它是一个环,准确说,是一个用数组实现的环形队列。

不像传统队列,维护对头,队尾,它只有Sequencer,指向下一个可用的数据缓存区

新产生的数据对原来数据进行写覆盖,不进行remove操作。

队列大小一定是2的N次方

它有什么好处呢?

相比于链表,它寻址更快,时间复杂度控制在O(n)

在初始化时,就已经分配好了内存,而且新产生的只覆盖,所以更少的GC

sequece 一直自增,进行位操作可以快速定位到实际slot , sequece & (array length-1) = array index,比如一共有8槽,9 &(8-1)= 1

对比例子代码

CPU和内存之间存在着多级缓存,我们都知道越靠近CPU的缓存越快,存储速度依次排列为L1,L2,L3,Memery,但是他们的存储空间大小依次排列为倒序,因此我们不能把所有数据都放在L1,我们需要把我们的数据从Memery中Load到cache里面,从而进行访问。

下图有一些数据:

如果你想让你端到端的延时为10ms,那你mermery中load耗时80ns相对来说是一个比较重的操作。

更为严重的问题是,我们的数据在缓存中,不是独立项存储的,你可以想象缓存为一个阵列,由多个缓存行组成,缓存行大小根据机器不同,有差异,常见为64个字节。每次LRU(或者其他算法)的时候,它会把你目标数据的相邻数据也load进来,放入一整行的缓存中。

现在假设我们要操作A.B两个数据,他们正好在内存中是紧挨着的,线程1想要对数据A进行写入操作,它把A从Memery中load到L1来,相应的B也被免费的load到L1中。

现程1需要对A进行写入,同时线程2需要对B进行写入,他们需要争夺对这个缓存行的所有权,加入线程1成功对A进行了写入,那线程2需要对自己的缓存置为失效。

通过这样的一个方式,说明了两个不相关的线程,本来操作自己的数据,但是因为另外一个线程对自己数据的更改,导致自己的数据需要重新从Memery中load,这样会把自己的整体速度给拖慢。


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-34283-1.html

相关阅读
    发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

    • 和磊
      和磊

      还是存e租宝吧一年14600

    • 包何
      包何

      雷总的公关能力真他妈强

    • 王苍
      王苍

      12海里领海和200海里专属经济区规定的既定事实

    热点图片
    拼命载入中...