fork-join:fork所有子任务或为当前线程留一个

时间:2016-05-13 14:39:09

标签: java fork-join

我试图了解fork-join如何工作的细节。

Wikipedia有以下示例用于merge-sort,其中左半部分是分叉的,右半部分是由当前线程处理的。

mergesort(A, lo, hi):
    if lo < hi:                     // at least one element of input
        mid = ⌊(hi - lo) / 2⌋
        fork mergesort(A, lo, mid)  // process (potentially) in parallel with main task
        mergesort(A, mid, hi)       // main task handles second recursion
        join
        merge(A, lo, mid, hi)

然而,大多数Java示例我已经看到fork 所有子任务并等待他们的结果:

for (Document document : folder.getDocuments()) {
    DocumentSearchTask task = new DocumentSearchTask(document, searchedWord);
    forks.add(task);
    task.fork();
}
for (RecursiveTask<Long> task : forks) {
    count = count + task.join();
}
return count;

维基百科的例子对我来说更有意义,因为线程会做一些有用的事情,而不是阻塞和等待子任务。

另一方面,如果我们分叉所有任务,我们会避免递归,并且无法获得StackOverflowError

分割任务的首选方法是什么?为什么?

1 个答案:

答案 0 :(得分:1)

我说首选的方法是以相同的方式分叉和处理所有子任务。原因如下:

    Java中的
  1. ForkJoinPool实现了ExecutorService。请注意,ExecutorService中的所有方法都是异步。有一个原因 - 你可以经常异步地在后台产生一些计算,而你的主线程可以做一些其他有用的工作,直到它需要计算结果,例如,产生更多异步任务。

  2. 更容易推理。如果您以相同的方式处理所有子问题,而不是在任务中引入某种不对称性,则代码通常看起来更清晰。

  3. 不分叉并在主线程上进行部分计算并没有任何优势。如果你分叉所有任务然后等待连接,你的主线程处于等待状态并且几乎不消耗资源,工作线程可以充分利用处理器。

  4. 不过,这不仅仅是一种偏好而是一种严格的选择。除了您提到的潜在堆栈溢出外,它们在功能上是等效的。

    我不能代表维基百科的作者,但我的猜测是,她或者试图让事情变得简单,或者她有一些抽象语言的背景,其中分叉/加入并不像Java那么简单。

    更新:对于过多的线程阻塞,这不是ForkJoinPool的问题。正如here所解释的那样,关于ForkJoinPool的特殊之处在于,在join调用中确实发生了工作窃取。