具有非递归任务的Java ForkJoinPool是否可以正常工作?

时间:2015-05-05 07:52:27

标签: java multithreading fork-join forkjoinpool work-stealing

我想通过以下方法将Runnable任务提交到ForkJoinPool:

forkJoinPool.submit(Runnable task)

注意,我使用的是JDK 7.

在引擎盖下,它们被转换为ForkJoinTask对象。 我知道当一个任务以递归方式分成较小的任务时,ForkJoinPool是有效的。

问题:

如果没有递归,工作窃取是否仍然可以在ForkJoinPool中工作?

在这种情况下值得吗?

更新1: 任务很小,可能不平衡。即使对于严格平等的任务,诸如上下文切换,线程调度,停车,页面未命中等事情也会妨碍导致不平衡

更新2: Doug Lea在Concurrency JSR-166 Interest小组中写道,给出了一个暗示:

  

当所有任务都是异步时,这也极大地提高了吞吐量   提交到池而不是分叉,这变得合理   构建actor框架的方法,以及许多普通的服务   否则你可能会使用ThreadPoolExecutor。

我认为,当涉及到相当小的CPU绑定任务时,由于这种优化,ForkJoinPool是可行的方法。重点是这些任务已经很小,不需要递归分解。 工作窃取有效,无论是大型还是小型任务 - 任务都可以被Deque尾巴忙碌的工作人员抓住。

更新3: Scalability of ForkJoinPool - Akka乒乓球队的基准测试显示了很好的成绩。

尽管如此,要更有效地应用ForkJoinPool还需要进行性能调整。

1 个答案:

答案 0 :(得分:15)

ForkJoinPool源代码有一个很好的部分名为"实施概述",阅读最终真相。下面的解释是我对JDK 8u40的理解。

从第一天开始,ForkJoinPool每个工作线程都有一个工作队列(让我们调用它们"工作队列")。分叉的任务被推送到本地工作队列,准备好再次被工作者弹出并执行 - 换句话说,它看起来像工作线程角度的堆栈。当一个工作人员耗尽其工作队列时,它会绕过并试图从其他工作队列中窃取任务。那是"工作窃取"

现在,在(IIRC)JDK 7u12之前,ForkJoinPool有一个全局提交队列。当工作线程用尽本地任务以及窃取任务时,他们到达那里并试图查看外部工作是否可用。在这种设计中,对ThreadPoolExecutor支持的常规ArrayBlockingQueue没有任何优势。

此后发生了重大变化。在此提交队列被确定为严重的性能瓶颈之后,Doug Lea等人。条纹提交队列也是如此。事后看来,这是一个显而易见的想法:您可以重用大多数可用于工作队列的机制。您甚至可以按工作线程松散地分发这些提交队列。现在,外部提交进入其中一个提交队列。然后,没有工作的工作人员可以首先查看与特定工作者关联的提交队列,然后四处寻找其他人的提交队列。人们可以致电 "偷窃"太

我看到很多工作负载从中受益。很久以前就已经认识到ForkJoinPool的这种特殊设计优势,即使对于简单的非递归任务也是如此。并发兴趣@中的许多用户都要求一个简单的工作窃取执行程序,而没有ForkJoinPool所有的{arxnery'。这就是为什么我们在JDK 8之前有Executors.newWorkStealingPool()的原因之一 - 目前委托给ForkJoinPool,但是开放提供更简单的实现。