在 Java 的 ForkJoinTask 中,fork/join 的顺序重要吗?
In Java's ForkJoinTask, does the order of fork/join matter?
假设我们扩展了一个名为 MyRecursiveTask
的 RecursiveTask
。
然后在forkJoinTask的范围内创建两个子任务:
MyRecursiveTask t1 = new MyRecursiveTask()
MyRecursiveTask t2 = new MyRecursiveTask()
t1.fork()
t2.fork()
我认为 "t2" 将位于 Workqueue 的顶部(这是一个双端队列,它用作 worker 本身的堆栈),因为我看到了这样的 fork 方法的实现:
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
如果是这样,下面两个表达式的性能是否有差异:
表达式 1:
t1.join() + t2.join()
表达式 2:
t2.join() + t1.join()
我认为这可能很重要。 t1.join()
在 t2.join()
完成之前将一直阻塞(如果没有工作窃取),因为只能弹出 workQueue 顶部的任务。 (换句话说,t2 必须在 t1 弹出之前弹出)。下面是 doJoin
和 tryUnpush
.
的代码
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
return (s = status) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
externalAwaitDone();
}
/**
* Pops the given task only if it is at the current top.
* (A shared version is available only via FJP.tryExternalUnpush)
*/
final boolean tryUnpush(ForkJoinTask<?> t) {
ForkJoinTask<?>[] a; int s;
if ((a = array) != null && (s = top) != base &&
U.compareAndSwapObject
(a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
U.putOrderedInt(this, QTOP, s);
return true;
}
return false;
}
有人对此有想法吗?谢谢!
如果您的内核数比并行的两个线程 运行 都多,那么先启动哪个线程并不重要,因为完成时间很重要。因此,无论谁先完成,都必须等待另一个完成并计算结果。如果您有 1 个核心,那么您的想法可能是正确的,但对于 1 个核心,您为什么需要并行化作业?
您使用 Java7 还是 Java8 很重要。在 Java7 中,框架为 join() 创建延续线程。在 Java8 中,框架主要因 join() 而停止。 See here. 自 2010 年以来,我一直在撰写有关此框架的评论。
使用 RecursiveTask 的建议(来自 JavaDoc):
return f2.compute() + f1.join();
这样拆分线程自己继续操作。
不建议依赖 F/J 代码来指示方向,因为此代码经常更改。例如,在 Java8 中使用嵌套并行流会导致过多的补偿线程,因此在 Java8u40 中重新编写代码只会导致更多问题。 See here.
如果您必须执行多个连接,那么您连接 () 的顺序实际上无关紧要。每个 fork() 使任务可用于任何线程。
假设我们扩展了一个名为 MyRecursiveTask
的 RecursiveTask
。
然后在forkJoinTask的范围内创建两个子任务:
MyRecursiveTask t1 = new MyRecursiveTask()
MyRecursiveTask t2 = new MyRecursiveTask()
t1.fork()
t2.fork()
我认为 "t2" 将位于 Workqueue 的顶部(这是一个双端队列,它用作 worker 本身的堆栈),因为我看到了这样的 fork 方法的实现:
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
如果是这样,下面两个表达式的性能是否有差异:
表达式 1:
t1.join() + t2.join()
表达式 2:
t2.join() + t1.join()
我认为这可能很重要。 t1.join()
在 t2.join()
完成之前将一直阻塞(如果没有工作窃取),因为只能弹出 workQueue 顶部的任务。 (换句话说,t2 必须在 t1 弹出之前弹出)。下面是 doJoin
和 tryUnpush
.
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
return (s = status) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
externalAwaitDone();
}
/**
* Pops the given task only if it is at the current top.
* (A shared version is available only via FJP.tryExternalUnpush)
*/
final boolean tryUnpush(ForkJoinTask<?> t) {
ForkJoinTask<?>[] a; int s;
if ((a = array) != null && (s = top) != base &&
U.compareAndSwapObject
(a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
U.putOrderedInt(this, QTOP, s);
return true;
}
return false;
}
有人对此有想法吗?谢谢!
如果您的内核数比并行的两个线程 运行 都多,那么先启动哪个线程并不重要,因为完成时间很重要。因此,无论谁先完成,都必须等待另一个完成并计算结果。如果您有 1 个核心,那么您的想法可能是正确的,但对于 1 个核心,您为什么需要并行化作业?
您使用 Java7 还是 Java8 很重要。在 Java7 中,框架为 join() 创建延续线程。在 Java8 中,框架主要因 join() 而停止。 See here. 自 2010 年以来,我一直在撰写有关此框架的评论。
使用 RecursiveTask 的建议(来自 JavaDoc):
return f2.compute() + f1.join();
这样拆分线程自己继续操作。
不建议依赖 F/J 代码来指示方向,因为此代码经常更改。例如,在 Java8 中使用嵌套并行流会导致过多的补偿线程,因此在 Java8u40 中重新编写代码只会导致更多问题。 See here.
如果您必须执行多个连接,那么您连接 () 的顺序实际上无关紧要。每个 fork() 使任务可用于任何线程。