2015-05-05 69 views
21

我想通过一个方法提交Runnable任务到ForkJoinPool:带有非递归任务的Java 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 - 由阿卡队乒乓的基准显示出巨大的效果。

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

+0

我已经想过这个我自己,我想这是你提到的线程的停车和锁定。这就是为什么我认为ring buffers(disruptor)比unwrapped fork join更快(至少对于我的非递归事件任务)。 –

+0

@Adam Gent我们也使用了Disruptor - 它速度惊人。是的,RingBuffer的想法应用于许多现代数据结构中。但是,它们在使用情况上有所不同:1. Disruptor使用1个线程1用户模型进行流水线操作,但无法窃取工作,而2. FJ是一群工作人员 - 工作分布在多个线程(m:n)中。 –

+0

我很熟悉fj-rb的差异,但我认为他们都可能会减少锁定,但我不是专家。 –

回答

13

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

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

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

之后它发生了显着变化。在提交队列被确定为严重的性能瓶颈之后,Doug Lea等人也提交了提交队列。事后看来,这是一个明显的想法:您可以重用可用于工作队列的大部分机制。你甚至可以松散地为每个工作者线程分配这些提交队列。现在,外部提交进入提交队列之一。然后,没有工作的工作人员可以先查看与特定工作人员相关的提交队列,然后四处寻找其他人的提交队列。也可以拨打“偷工减料”。

我看到很多受益于此的工作负载。即使对于简单的非递归任务,这种特殊的设计优势ForkJoinPool早已得到了认可。 concurrency-interest @的许多用户要求提供一个简单的盗取工作的执行程序,而不需要所有的秘密。这是为什么我们在JDK 8中有Executors.newWorkStealingPool()以后的原因之一 - 目前正在委托给ForkJoinPool,但为了提供更简单的实现而打开。