【问题标题】:Why are fork join tasks executed outside the common fork join pool threads?为什么 fork join 任务在公共 fork join 池线程之外执行?
【发布时间】:2018-02-04 04:06:28
【问题描述】:

我的问题最好通过给出代码 sn-p 来解释:

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]

为什么某些任务跑在主线程中,主线程是普通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]

那么,换句话说,公共池有什么特别之处?有了这些知识,在公共池中执行长时间运行的任务是明智的还是不明智的想法?

【问题讨论】:

  • 你应该等到得到几个答案后再接受错误的答案。
  • @edharned 随意创建一个包含更多见解的新答案!很想听到更多。接受的答案总是可以更改的,所以没有问题。

标签: java java.util.concurrent fork-join forkjoinpool


【解决方案1】:

发生了一些相当聪明的事情。

当您从不在线程中的线程调用 ForkJoinTask::join 时,它会显示(来自 ForkJoinPool.awaitJoin 的评论)当前线程可以“帮助”任务执行。

所以这就是主线程在 fork-join 池中执行任务的原因。

但是为什么在您创建自定义池的情况下会有所不同?好吧,我的猜测是您的自定义池有足够的线程,不需要主线程。因为另一件事是,默认情况下,“公共池”创建时使用的线程少于可用处理器的数量。


有关更多详细信息,请查看源代码。请注意,此行为未在 javadocs 中指定,因此它可能会在未来的实现中发生变化。

【讨论】:

    【解决方案2】:

    阐述我的其他cmets:

    使用提交线程作为工作线程一直是关于性能的。在工作窃取优先程序中,提交线程将新请求放入提交队列并通知工作线程有工作。 (确切通知的线程随着时间的推移发生了变化。)

    工作窃取线程只能通过从其他线程的双端队列中窃取或进入提交队列来获得工作。 (由于只使用一个的扩展问题,现在有多个提交队列。)线程如何找到工作是无关紧要的。相关的是它很慢。线程需要唤醒并去寻找工作。由于线程的队列是双端队列,因此无法将工作分配给任何线程。这个设计的主要原因是作者抄袭了 Cilk 的设计。

    Cilk 是集群 fork/join 程序。它主要工作在计算机连接网络的集群环境中。有一个工作共享算法,其中计算机查询其他计算机的队列以查看哪里有工作,或者,将分叉的任务放在哪里(放入队列中用于负载平衡的挂起任务数量最少的队列)是令人望而却步的。因此,在集群环境中,使用双端队列工作窃取优先。

    在 Java 中,环境是共享内存。在其他线程的队列中查找的开销很小。在任何情况下,对框架的第一个请求都需要从线程中唤醒、寻找工作,而且速度很慢。

    对于 Java8 并行流,系统需要公共池和加速处理的方法。 (并行线程的初始时间很糟糕。)因此,作者提出了将提交线程用作工作线程的想法。然而,这种技术也带来了它自己的问题,如下所述:http://coopsoft.com/ar/Calamity2Article.html#submission

    【讨论】:

    • 为什么使用提交线程作为工作线程不聪明?提交线程必须按照join() 等待。那么它可以要么等待什么都不做,要么也可以尽量早点完成工作?我不明白?
    猜你喜欢
    • 2015-03-05
    • 2015-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多