石凉档案解密希特勒:Disruptor的并发优化

来源:百度文库 编辑:中财网 时间:2024/04/28 17:19:34

Disruptor没有像JDK的LinkedBlockQueue等那样使用锁,针对CPU高速缓存进行了优化。

原来我们以为多个线程同时写一个类的字段会发生争夺,这是多线程基本原理,所以使用了锁机制,保证这个共用字段(资源)能够某个时刻只能一个线程写,但是这样做的坏处是:有可能发生死锁。

比如1号线程先后访问共享资源A和B;而2号线程先后访问共享资源B和A,因为在资源A和资源B都有锁,那么1号在访问资源A时,资源A上锁了,准备访问资源B,但是无法访问,因为与此同时;而2号线程在访问资源B,资源B锁着呢,正准备访问资源A,发现资源A被1号线程锁着呢,结果彼此无限等待彼此下去,死锁类似逻辑上自指悖论。

所以,锁是坏的,破坏性能,锁是并发计算的大敌。

我们回到队列上,一把一个队列有至少两个线程:生产者和消费者,这就具备了资源争夺的前提,这两个线程一般彼此守在队列的进出两端,表面上好像没有访问共享资源,实际上队列存在两个共享资源:队列大小或指针. 

除了共享资源写操作上存在资源争夺问题外,Disruptor的LMAX团队发现Java或C#在多核CPU情况下有伪共享问题:
CPU会把数据从内存加载到高速缓存中 ,这样可以获得更好的性能,高速缓存默认大小是64 Byte为一个区域,CPU机制限制只能一个CPU的一个线程访问(写)这个高速缓存区。

CPU在将主内存中数据加载到高速缓冲时,如果发现被加载的数据不足64字节,那么就会加载多个数据,以填满自己的64字节,悲催就发生了,恰恰otspot JVM中对象指针等大小都不会超过64字节,这样一个高速缓冲中可能加载了两个对象指针,一个CPU一个高速缓冲,双核就是两个CPU各自一个高速缓冲,那么两个高速缓冲中各有两个对象指针,都是指向相同的两个对象。

因为一个CPU只能访问(写)自己高速缓存区中数据,相当于给这个数据加锁,那么另外一个CPU同时访问自己高速缓冲中同样数据时将会被锁定不能访问。

这就发生与锁机制类似的性能陷进,Disruptor的解决办法是填满高速缓冲的64字节,不是对象指针等数据不够64字节吗?那么加一些字节填满64字节,这样CPU将数据加载到高速缓冲时,就只能加载一个了,刚刚好啊。

所以,尽管两个线程是在写两个不同的字段值,也会因为双核CPU底层机制发生伪装的共享,并没有真正共享,其实还是排他性的独享。

现在我们大概知道RingBuffer是个什么东东了:
1.ring buffer是一个大的数组.
2.RingBuffer里所有指针都是Java longs (64字节) 不断永远向前计数,如后面图,不断在圆环中循环。
3.RingBuffer只有当前序列号,没有终点序列号,其中数据不会被取出后消除,这样以便实现从过去某个序列号到当前序列号的重放,这样当消费者说没有接受到生产者发送的消息,生产者还可以再次发送,这点是一种原子性的“事务”机制。

鉴于Disruptor如此革命性的优点,JdonFramework 6.4新版 采取Disruptor作为其Domain Events实现机制。 即可以方便简单享用Disruptor的新特点;又能根据自己的要求继续深化使用Disruptor,最极致的情况如运行的LMAX系统一样,每秒处理6百万个订单。

[该贴被admin于2011-09-08 16:06修改过]
[该贴被admin于2011-12-22 10:56修改过]