【问题标题】:Add the first element to a ConcurrentLinkedQueue atomically以原子方式将第一个元素添加到 ConcurrentLinkedQueue
【发布时间】:2012-08-02 22:14:53
【问题描述】:

我想以原子lock-free 方式使用ConcurrentLinkedQueue

几个并发线程将事件推送到队列中,其他一些线程将处理它们。队列未绑定,我不希望任何线程等待或被锁定。然而,阅读部分可能会注意到队列变空了。在无锁实现中,读取线程不能阻塞,而只会结束其任务并继续执行其他任务(即作为 ExecutorService)。因此,将第一个新事件推入空队列的写入者必须意识到它并应该重新启动读取器(即通过向 ExecutorService 提交新的 Runnable)来处理队列。提交第二个或第三个事件的任何其他线程都不会关心,因为它们可能会假设某些读者已经准备好/提交了。

不幸的是,ConcurrentLinkedQueue 的 add() 方法总是返回 true。在添加事件之前或之后询问队列是否 isEmpty() 将无济于事,因为它不是原子的。 我应该使用一些额外的 AtomicInteger 来监控队列大小()还是有一些更智能的解决方案?

迪特。

【问题讨论】:

  • 扩展ConcurrentLinkedQueue,覆盖add() 方法,如果需要,让它触发处理器线程。您可能有一个竞争条件,其中两个或多个处理器线程被触发,但它们可以使用同步锁来处理 - 唯一会发生的阻塞是在一个无论如何都会很快死亡的线程上,没有合法的工作线程被阻塞。跨度>

标签: java multithreading concurrency lock-free


【解决方案1】:

我不太明白您为什么不直接为此使用ExecutorService。它在内部使用BlockingQueue 并处理所有信号本身。

// open ended thread pool
ExecutorService threadPool = Executors.newFixedThreadPool(1);
for (Job job : jobsToDo) {
    threadPool.submit(new MyJobProcessor(job));
}

除非你有充分的理由,否则我不会自己重写同样的逻辑。

如果您试图以某种方式使用休眠线程,我强烈建议您不要打扰。线程相对便宜,因此分配一个线程来处理您的排队任务很好。重复使用线程是不必要的,对我来说似乎是过早的优化。

【讨论】:

  • 这取决于要求,如果需要高吞吐量或低延迟,则可以使用actor(轻量级线程,低内存占用)或破坏者(OS线程,预分配缓冲区)实现:infoq.com/presentations/LMAX跨度>
  • 嗯,当然@Andriy,但除非分析器告诉您ExecutorService 是您的瓶颈,否则让事情变得更复杂是没有意义的。
  • 我已经这样做了。我提到了一个线程消耗队列,但它确实是我的底层 ExecutorService 的一个 Runnable 任务。
  • 在提交的处理器准备好开始处理事件之前,一些事件可能已经入队,但我不想提交额外的处理器。最后,在处理完所有事件之后,处理器任务不能简单地等待(就像阻塞队列一样),因为它会阻塞我的 ExecutorService 的当前线程。相反,它必须终止(Runnable 任务,而不是线程)并且如果有更多事件进入,则必须重新启动。
    同时我认为一些 AtomicInteger 会完成这项工作。
【解决方案2】:

使用AtomicInteger 解决提交争用比锁定或synchronized 块更有效。

【讨论】:

  • 昨晚我为 MPSCQueue、AoMP 的 LockFreeQueue 和 CLQ 运行了单线程基准测试(使用 Google Caliper)。我通常看到 35ns 的 CLQ 和 500-600ns 的其他 CLQ 和高 CPU 峰值(可能是由于总线争用)。我的自定义无锁环形缓冲区性能最好,但我还没有弄清楚如何安全地处理 CQL 溢出。
  • 在我的笔记本电脑中,MPSCQueue 的性能优于 CLQ 约 1.5 倍:github.com/plokhotnyuk/actors/blob/master/out1.txt#L189
  • 我重写了benchmark,因为我在本地丢弃了它。结果相似。
  • 我玩过,并没有弄清楚为什么基准测试结果差异如此之大。在性能达到稳定状态之前,您的基准测试不会执行任何 JVM 预热或尝试评估,因此早期运行可能会受到不公平的惩罚。我的最佳猜测是 Caliper 正在同时运行多个测试,并且 TAS 风格的 cas 操作导致总线风暴和 CPU 峰值(未利用 mesi 缓存一致性进行 L1 旋转)。当然,它可能只是一个失败的基准。如果你能把我的移植到你的环境中,那就太好了。
  • 我不会如此盲目地相信基准测试框架...这是证明 MPSCQueue 性能更好的简单测试和输出:gist.github.com/3181092#gistcomment-380023
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
相关资源
最近更新 更多