为什么分支/合并任务在公共的分支/合并池线程之外执行?

3

我的问题最好通过给出代码片段来解释:

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);
}

当在我的笔记本电脑上运行此程序时,该程序会打印出:

Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]
Thread[main,5,main]
Thread[ForkJoinPool.commonPool-worker-1,5,main]

为什么某些任务在主线程中运行,而主线程是通用分叉合并线程池之外的线程?
创建自定义分叉合并线程池时,这种情况不会发生。
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]

换句话说,公共池有什么特别之处?鉴于这一点,将长时间运行的任务放在公共池中是明智还是不明智的决策?


1
在接受错误答案之前,您应该等待获得多个答案。 - edharned
@edharned 随意创建一个新的答案并提供更多见解!很想听听更多。接受的答案总是可以更改,所以没有问题。 - Bas Opers
2个回答

5

有一些相当巧妙的事情发生了。

当您从不在线程中的线程调用ForkJoinTask::join时,似乎(根据ForkJoinPool.awaitJoin上的注释)当前线程可以“帮助”执行任务。

这就是为什么主线程在分叉-加入池中执行任务的原因。

但是,为什么在创建自定义池的情况下会有所不同呢?我猜您的自定义池具有足够的线程,因此不需要主线程。另外,默认情况下,“公共池”创建的线程数比可用处理器数量少一个。


要获取更多详情,请查看源代码。请注意,此行为未在javadoc中指定,因此可能会在未来的实现中进行更改。


2

对我的其他评论进行阐述:

使用提交线程作为工作线程始终是关于性能的。在一个以工作偷取为首要任务的程序中,提交线程将新请求放入提交队列,并通知工作线程有工作要做。(哪个线程被通知已经随时间而改变。)

只有通过从其他线程的双端队列中窃取或去到提交队列才能获取工作。 (由于只使用一个队列会出现可伸缩性问题,现在有多个提交队列。)线程如何找到工作并不重要。重要的是它很慢。线程需要唤醒并寻找工作。无法将工作分配给任何线程,因为线程的队列是双端队列。这种设计的主要原因是作者从Cilk中复制了该设计。

Cilk是一个群集fork/join程序。它主要在连接到网络的计算机的群集环境中运行。拥有一个工作共享算法,其中计算机查询其他计算机的队列以查看哪里有工作,或将分支任务放入挂起任务数量最少的队列中以进行负载平衡是禁止的。因此,在群集环境中,以工作偷取为首选且使用双端队列的方式更好。

在Java中,环境是共享内存的。查看其他线程队列的开销非常小。但是,对于框架的第一个请求需要唤醒线程并寻找工作,这会很慢。
对于Java8并行流,系统需要公共池和加速进程的方法。(并行线程的初始时间非常糟糕。)因此,作者想出了使用提交线程作为工作线程的方法。然而,这种技术引入了自己的问题,如在此处提到的:http://coopsoft.com/ar/Calamity2Article.html#submission

1
为什么使用提交线程作为工作线程不聪明?提交线程必须等待join()。因此,它可以等待并无所事事,或者尽最大努力提前完成工作?我不明白这个问题。 - Bas Opers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接