ForkJoinPool在invokeAll / join期间停止

时间:2013-06-03 10:44:03

标签: java java.util.concurrent lock-free fork-join

我尝试使用ForkJoinPool来并行化我的CPU密集型计算。 我对ForkJoinPool的理解是,只要任何任务可以执行,它就会继续工作。不幸的是,我经常观察工作线程空闲/等待,因此并非所有CPU都保持忙碌状态。有时我甚至会观察到额外的工作线程。

我没想到这一点,因为我严格尝试使用non blocking任务。 我的观察结果与ForkJoinPool seems to waste a thread非常相似。 在调试了很多ForkJoinPool之后我猜了一下:

我使用invokeAll()在子任务列表上分配工作。在invokeAll()完成后执行第一个任务本身,它开始加入其他任务。这很好,直到下一个要连接的任务位于执行队列之上。不幸的是,我提交了异步的其他任务而没有加入它们。我希望ForkJoin框架能够首先继续执行这些任务,然后再转回加入任何剩余的任务。

但它似乎不是这样工作的。相反,工作线程停止调用wait()直到等待的任务准备好(可能是由其他工作线程执行)。我没有验证这一点,但似乎是调用join()的一般缺陷。

ForkJoinPool提供asyncMode,但这是一个全局参数,不能用于单个提交。但我喜欢看到我的异步分叉任务很快就会执行。

那么,为什么ForkJoinTask.doJoin()不会简单地在其队列之上执行任何可用任务,直到它准备好(由自己执行或被其他人窃取)?

3 个答案:

答案 0 :(得分:3)

你对加入()是对的。两年前我写了this文章,指出了join()的问题。

正如我在那里所说,框架在完成之前的请求之前不能执行新提交的请求。并且每个WorkThread都不能窃取,直到它的当前请求结束,这导致wait()。

您看到的其他主题是"延续线程。"由于join()最终会发出wait(),因此需要这些线程,因此整个框架都不会停止。

答案 1 :(得分:3)

由于似乎没有其他人理解我的问题,我试着解释一下我调试后发现的内容:

如果所有fork / join调用都严格配对,ForkJoinTasks的当前实现很有效。通过一个开括号来说明一个叉子并通过一个闭合的连接,一个完美的二元叉连接模式可能如下所示:

{([] [])([] [])} {([] [])([] [])}

如果您使用invokeAll(),您还可以提交如下的子任务列表:

{([] [] [] [])([] [] [] [])([] [] [] [])}

我所做的却看起来像这种模式:

{([)([)} ...]]

你可能会认为这看起来很糟糕或者是对fork-join框架的误用。但唯一的限制是the tasks completion dependencies are acyclic,否则你可能遇到死锁。只要我的 [] 任务不依赖于()任务,我就不会发现任何问题。违规的]] 只是表示我不会明确地等待它们;它们可能会在某一天结束,对我来说无关紧要(在那一点上)。

实际上,当前的实现能够执行我的互锁任务,但只能通过产生额外的辅助线程来实现效率非常低。

缺陷似乎是join()的当前实现:加入期望在其执行队列的顶部看到相应的),但它找到了 [并且感到困惑。当前线程暂停(调用wait())而不是简单地执行 [] ,直到其他人来执行意外任务。这会导致性能急剧下降。

我的主要目的是将额外的工作放到队列上,以防止工作线程在队列空运行时挂起。不幸的是,相反的情况发生了:-(

答案 2 :(得分:2)

您没有将此框架用于非常狭隘的目的。

该框架始于2000年研究论文中的实验。从那时起它就被修改了,但是大型阵列上的基本设计fork-and-join仍然是相同的。基本目的是教本科生如何走下平衡树的叶子。当人们使用它而不是简单的数组处理时,会发生奇怪的事情。它在Java7中的作用超出了我的意义;这是本文的目的。

问题只会在Java8中变得更糟。它是驱动所有流并行工作的引擎。阅读该文章的第二部分。 lambda兴趣列表中包含线程停顿,堆栈溢出和内存不足错误的报告。

如果不将它用于纯大的数据结构的递归分解,则使用它需要您自担风险。即使这样,它创建的过多线程也会造成严重破坏。我不打算继续讨论这个问题。

相关问题