我试图了解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
。
分割任务的首选方法是什么?为什么?
答案 0 :(得分:1)
我说首选的方法是以相同的方式分叉和处理所有子任务。原因如下:
ForkJoinPool
实现了ExecutorService
。请注意,ExecutorService
中的所有方法都是异步。有一个原因 - 你可以经常异步地在后台产生一些计算,而你的主线程可以做一些其他有用的工作,直到它需要计算结果,例如,产生更多异步任务。
更容易推理。如果您以相同的方式处理所有子问题,而不是在任务中引入某种不对称性,则代码通常看起来更清晰。
不分叉并在主线程上进行部分计算并没有任何优势。如果你分叉所有任务然后等待连接,你的主线程处于等待状态并且几乎不消耗资源,工作线程可以充分利用处理器。
我不能代表维基百科的作者,但我的猜测是,她或者试图让事情变得简单,或者她有一些抽象语言的背景,其中分叉/加入并不像Java那么简单。
更新:对于过多的线程阻塞,这不是ForkJoinPool
的问题。正如here所解释的那样,关于ForkJoinPool
的特殊之处在于,在join
调用中确实发生了工作窃取。