Java ForkJoinPool与非递归任务,工作窃取工作工作吗?

我想通过一种方法将任务提交到ForkJoinPool中:Runnable

forkJoinPool.submit(Runnable task)

请注意,我使用 JDK 7。

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

问题:

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

在这种情况下,值得吗?

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

更新 2:Doug Lea在并发JSR-166兴趣小组中写道,对此进行了提示:

当所有任务都是异步的并提交到池而不是分叉时,这也大大提高了吞吐量,这成为构建执行组件框架以及许多可能使用ThreadPoolExecutor的普通服务的合理方法。

我认为,当涉及到相当小的CPU密集型任务时,ForkJoinPool是要走的路,这要归功于这种优化。重点是这些任务已经很小,不需要递归分解。偷工是有效的,不管是大任务还是小任务——任务可以被另一个自由工人从繁忙工人的Deque尾巴上抓取。

更新3:ForkJoinPool的可扩展性 - Akka乒乓球团队的基准测试显示出很好的效果。

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


答案 1

ForkJoinPool源代码有一个很好的部分叫做“实现概述”,阅读一个终极真理。下面的解释是我对JDK 8u40的理解。

从第一天起,每个工作线程都有一个工作队列(我们称之为“工作线程队列”)。分叉的任务被推送到本地工作线程队列中,准备再次被工作线程弹出并执行 - 换句话说,从工作线程的角度来看,它看起来像一个堆栈。当工作线程耗尽其工作线程队列时,它会四处走动并尝试从其他工作线程队列中窃取任务。这就是“偷工”。ForkJoinPool

现在,在(IIRC)JDK 7u12之前,有一个全局提交队列。当工作线程用完本地任务以及要窃取的任务时,他们到达那里并尝试查看外部工作是否可用。在这种设计中,与常规设计相比没有任何优势,例如,由 .ForkJoinPoolThreadPoolExecutorArrayBlockingQueue

在那之后,它发生了重大变化。在将此提交队列确定为严重的性能瓶颈后,Doug Lea 等人也对提交队列进行了条带化。事后看来,这是一个显而易见的想法:您可以重用大多数可用于工作线程队列的机制。您甚至可以松散地按工作线程分配这些提交队列。现在,外部提交将进入其中一个提交队列。然后,没有工作要做的工作人员可以首先查看与特定工作线程关联的提交队列,然后四处游荡,查看其他人的提交队列。人们也可以称之为“工作偷窃”。

我看到许多工作负载从中受益。这种特殊的设计优势甚至适用于普通的非递归任务,很久以前就已经得到了认可。许多并发interest@要求一个简单的工作窃取执行器,而无需所有arcanery。这也是为什么我们在 JDK 8 以后有 Executors.newWorkStealingPool() 的原因之一 -- 目前委托给 ,但开放以提供更简单的实现。ForkJoinPoolForkJoinPoolForkJoinPool


答案 2