Java ForkJoinPool具有非递归任务,窃取工作有效吗?


问题内容

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

forkJoinPool.submit(Runnable task)

注意,我使用的是JDK 7。

在后台,它们被转换为ForkJoinTask对象。我知道,当将任务递归拆分为较小的任务时,ForkJoinPool是有效的。

题:

如果没有递归,偷窃工作是否仍可以在ForkJoinPool中进行?

在这种情况下值得吗?

更新1: 任务很小,可以不平衡。即使对于严格相等的任务,诸如上下文切换,线程调度,停放,​​页面丢失等问题也会导致 不平衡

更新2: Doug Lea在并发JSR-166兴趣小组中写了一个提示:

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

我认为,当涉及到较小的CPU限制任务时,由于进行了这种优化,ForkJoinPool是必经之路。要点是这些任务已经很小,不需要递归分解。无论是大任务还是小任务,
窃取 工作都是有效的-任务可以由另一名自由工作者从忙碌的工人的Deque尾巴抓住。

更新3: ForkJoinPool的可伸缩性-
Akka
乒乓球团队进行的基准测试显示了很好的结果。

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


问题答案:

ForkJoinPool源代码有一个不错的部分,称为“实施概述”,请阅读以获取最终真相。以下说明是我对JDK 8u40的理解。

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

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

此后发生了很大变化。在确定此提交队列是严重的性能瓶颈之后,Doug
Lea等人。划分提交队列。事后看来,这是一个显而易见的主意:您可以重用大多数可用于工作队列的机制。您甚至可以为每个工作线程松散地分配这些提交队列。现在,外部提交进入提交队列之一。然后,没有工作需要上班的工作人员可以先查看与特定工作人员相关联的提交队列,然后四处逛逛以查看其他人的提交队列。人们也可以称其
“偷工作”。

我已经看到许多工作负载从中受益。ForkJoinPool甚至对于普通的非递归任务而言,这种特殊的设计优势早已为人所认识。许多在concurrency-
interest
@上的用户要求一个简单的,可以偷工减料的执行ForkJoinPool器。这就是为什么我们Executors.newWorkStealingPool()要从JDK
8开始的原因之一-当前委托给ForkJoinPool,但是愿意提供更简单的实现。