为什么 fork join 任务在公共 fork join 池线程之外执行?
Why are fork join tasks executed outside the common fork join pool threads?
我的问题可以通过给出代码片段得到最好的解释:
public static void main(final String[] a) {
Stream.of(1, 2, 3, 4).map(i -> ForkJoinPool.commonPool().submit(new RecursiveAction() {
@Override
protected void compute() {
System.out.println(Thread.currentThread());
}
})).forEach(ForkJoinTask::join);
}
当 运行 在我有 4 个核心的笔记本电脑上时,会打印:
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
为什么某些任务运行在主线程中,主线程是common fork join线程池之外的线程?
创建自定义 fork join 线程池时,不会发生这种情况:
public static void main(final String[] a) {
final ForkJoinPool p = new ForkJoinPool(4);
Stream.of(1, 2, 3, 4).map(index -> p.submit(new RecursiveAction() {
@Override
protected void compute() {
System.out.println(Thread.currentThread());
}
})).forEach(ForkJoinTask::join);
}
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
那么,换句话说,公共池有什么特别之处呢?根据这些知识,在公共池中执行长 运行 任务是明智还是不明智的想法?
有些聪明的事情发生了。
当您从不在线程中的线程调用 ForkJoinTask::join
时,(根据 ForkJoinPool.awaitJoin
上的注释)显示当前线程可以 "helps" 执行任务。
所以这就是主线程在 fork-join 池中执行任务的原因。
但是为什么在您创建自定义池的情况下会有所不同?好吧,我的猜测是您的自定义池有足够的线程,不需要主线程。因为另一件事是默认情况下 "common pool" 是用一个少于可用处理器数量的线程创建的。
有关更多详细信息,请查看源代码。请注意,此行为未在 javadoc 中指定,因此它可能会在未来的实现中发生变化。
阐述我的其他评论:
将提交线程用作工作线程一直与性能有关。在工作窃取优先程序中,提交线程将新请求放入提交队列并通知工作线程有工作。 (具体通知哪个线程已随时间发生变化。)
工作窃取线程只能通过从其他线程的双端队列中窃取或进入提交队列来获得工作。 (现在有多个提交队列,因为只使用一个提交队列的扩展问题。)线程如何找到工作是无关紧要的。相关的是它很慢。线程需要唤醒并去寻找工作。没有办法给任何线程工作,因为线程的队列是双端队列。这样设计的主要原因是作者抄袭了Cilk的设计
Cilk 是集群 fork/join 程序。它主要工作在计算机连接在网络中的集群环境中。拥有一种工作共享算法,其中计算机查询其他计算机的队列以查看哪里有工作,或者将分叉任务放在哪里(放入具有最少数量的待处理任务的队列以进行负载平衡)是令人望而却步的。因此,在集群环境中,使用双端队列先窃取工作是更可取的。
在Java中,环境是Shared Memory。查看其他线程的队列的开销很小。在任何情况下,对框架的第一个请求都需要从线程唤醒、寻找工作,而且速度很慢。
对于 Java8 个并行流,系统需要公共池和加快进程的方法。 (并行线程的初始时间很糟糕。)因此,作者提出了使用提交线程作为工作者的想法。但是,此技术引入了其自身的问题,如此处所述:http://coopsoft.com/ar/Calamity2Article.html#submission
我的问题可以通过给出代码片段得到最好的解释:
public static void main(final String[] a) {
Stream.of(1, 2, 3, 4).map(i -> ForkJoinPool.commonPool().submit(new RecursiveAction() {
@Override
protected void compute() {
System.out.println(Thread.currentThread());
}
})).forEach(ForkJoinTask::join);
}
当 运行 在我有 4 个核心的笔记本电脑上时,会打印:
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
为什么某些任务运行在主线程中,主线程是common fork join线程池之外的线程?
创建自定义 fork join 线程池时,不会发生这种情况:
public static void main(final String[] a) {
final ForkJoinPool p = new ForkJoinPool(4);
Stream.of(1, 2, 3, 4).map(index -> p.submit(new RecursiveAction() {
@Override
protected void compute() {
System.out.println(Thread.currentThread());
}
})).forEach(ForkJoinTask::join);
}
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
Thread[ForkJoinPool-1-worker-1,5,main]
那么,换句话说,公共池有什么特别之处呢?根据这些知识,在公共池中执行长 运行 任务是明智还是不明智的想法?
有些聪明的事情发生了。
当您从不在线程中的线程调用 ForkJoinTask::join
时,(根据 ForkJoinPool.awaitJoin
上的注释)显示当前线程可以 "helps" 执行任务。
所以这就是主线程在 fork-join 池中执行任务的原因。
但是为什么在您创建自定义池的情况下会有所不同?好吧,我的猜测是您的自定义池有足够的线程,不需要主线程。因为另一件事是默认情况下 "common pool" 是用一个少于可用处理器数量的线程创建的。
有关更多详细信息,请查看源代码。请注意,此行为未在 javadoc 中指定,因此它可能会在未来的实现中发生变化。
阐述我的其他评论:
将提交线程用作工作线程一直与性能有关。在工作窃取优先程序中,提交线程将新请求放入提交队列并通知工作线程有工作。 (具体通知哪个线程已随时间发生变化。)
工作窃取线程只能通过从其他线程的双端队列中窃取或进入提交队列来获得工作。 (现在有多个提交队列,因为只使用一个提交队列的扩展问题。)线程如何找到工作是无关紧要的。相关的是它很慢。线程需要唤醒并去寻找工作。没有办法给任何线程工作,因为线程的队列是双端队列。这样设计的主要原因是作者抄袭了Cilk的设计
Cilk 是集群 fork/join 程序。它主要工作在计算机连接在网络中的集群环境中。拥有一种工作共享算法,其中计算机查询其他计算机的队列以查看哪里有工作,或者将分叉任务放在哪里(放入具有最少数量的待处理任务的队列以进行负载平衡)是令人望而却步的。因此,在集群环境中,使用双端队列先窃取工作是更可取的。
在Java中,环境是Shared Memory。查看其他线程的队列的开销很小。在任何情况下,对框架的第一个请求都需要从线程唤醒、寻找工作,而且速度很慢。
对于 Java8 个并行流,系统需要公共池和加快进程的方法。 (并行线程的初始时间很糟糕。)因此,作者提出了使用提交线程作为工作者的想法。但是,此技术引入了其自身的问题,如此处所述:http://coopsoft.com/ar/Calamity2Article.html#submission