多线程_生产者消费者

img

[TOC]

生产者消费者

synchronized版本

lock+condition的同步

BlockingQueue版实现

Disruptor方式

提高消费者的响应时间:选择合适的策略

当有新数据在Disruptor的环形缓冲区中产生时,消费者如何知道这些新产生的数据呢?或者说,消费者如何监控缓冲区中的信息呢?为此,Disruptor提供了几种策略,这些策略由WaitStrategy接口进行封装,主要有以下几种实现。

  • BlockingWaitStrategy:这是默认的策略。使用BlockingWaitStrategy和使用BlockingQueue是非常类似的,它们都使用锁和条件(Condition)进行数据的监控和线程的唤醒。因为涉及到线程的切换,BlockingWaitStrategy策略是最节省CPU,但是在高并发下性能表现最糟糕的一种等待策略。

  • SleepingWaitStrategy:这个策略也是对CPU使用率非常保守的。它会在循环中不断等待数据。它会先进行自旋等待,如果不成功,则使用Thread.yield()让出CPU,并最终使用LockSupport.parkNanos(1)进行线程休眠,以确保不占用太多的CPU数据。因此,这个策略对于数据处理可能产生比较高的平均延时。它比较适合于对延时要求不是特别高的场合,好处是它对生产者线程的影响最小。典型的应用场景是异步日志。

  • YieldingWaitStrategy:这个策略用于低延时的场合。消费者线程会不断循环监控缓冲区变化,在循环内部,它会使用Thread.yield()让出CPU给别的线程执行时间。如果你需要一个高性能的系统,并且对延时有较为严格的要求,则可以考虑这种策略。使用这种策略时,相当于你的消费者线程变身成为了一个内部执行了Thread.yield()的死循环。因此,你最好有多于消费者线程数量的逻辑CPU数量(这里的逻辑CPU,我指的是“双核四线程”中的那个四线程,否则,整个应用程序恐怕都会受到影响。

  • BusySpinWaitStrategy:这个是最疯狂的等待策略了。它就是一个死循环!消费者线程会尽最大努力疯狂监控缓冲区的变化。因此,它会吃掉所有的CPU资源。你只有在对延迟非常苛刻的场合可以考虑使用它(或者说,你的系统真的非常繁忙)。因为在这里你等同开启了一个死循环监控,所以,你的物理CPU数量必须要大于消费者线程数。注意,我这里说的是物理CPU,如果你在一个物理核上使用超线程技术模拟两个逻辑核,另外一个逻辑核显然会受到这种超密集计算的影响而不能正常工作。

CPU Cache的优化:解决伪共享问题

​ 什么是伪共享问题呢?我们知道,为了提高CPU的速度,CPU有一个高速缓存Cache。在高速缓存中,读写数据的最小单位为缓存行(Cache Line),它是从主存(memory)复制到缓存(Cache)的最小单位,一般为32字节到128字节。

​ 如果两个变量存放在一个缓存行中时,在多线程访问中,可能会相互影响彼此的性能。如图5.4所示,假设X和Y在同一个缓存行。运行在CPU1上的线程更新了X,那么CPU2上的缓存行就会失效,同一行的Y即使没有修改也会变成无效,导致Cache无法命中。接着,如果在CPU2上的线程更新了Y,则导致CPU1上的缓存行又失效(此时,同一行的X又变得无法访问)。这种情况反反复复发生,无疑是一个潜在的性能杀手。如果CPU经常不能命中缓存,那么系统的吞吐量就会急剧下降。

并行流水线

Last updated

Was this helpful?