我对ExecutorService和ForkJoinPool的内部调度机制有点困惑。
我知道ExecutorService的调度是做这边请的。
许多任务都在排队。一旦线程可用,它将处理第一个可用任务,依此类推。
同时,由于ForkJoinPool使用了的偷功算法,所以它被表示为不同.如果我正确理解,这意味着一个线程可以从另一个线程中窃取一些任务。
然而,我并不真正理解在ExecutorService和ForkJoinPool中实现的机制之间的区别。据我理解,这两种机制都应该尽可能地减少每个线程的空闲时间。
如果在ExecutorService的情况下,每个线程都有自己的队列,我会理解。然而,情况并非如此,因为队列由池的不同线程共享.
任何澄清都是非常欢迎的!
发布于 2018-10-09 21:45:49
假设您有一个非常大的ints数组,并且希望将它们全部添加。使用ExecutorService,您可能会说:让我们将该数组划分为块,例如线程数/ 4。因此,如果您有一个由160个元素组成的数组(并且您有4个CPU),您可以创建160 / 4 / 4 = 10,因此您将创建16个块,每个块包含10个int。创建runnables/callables,并将其提交给executor服务(当然,一旦这些结果完成,可以考虑合并这些结果的方法)。
现在,您希望每个CPU将承担其中的4个任务并完成它们。现在,让我们也假设一些数字是非常复杂的添加(当然不是,但请相信我),结果可能是3个线程/CPU完成了他们的工作,而其中一个只忙于第一个块。当然,没有人希望这样,但有可能发生。现在不好的是你什么也做不了。
相反,ForkJoinPool所做的是向我提供您想要如何将您的任务和实现拆分到我必须完成的最低工作量,剩下的我来处理。在Stream API中,这是用Spliterator完成的;主要是通过两种方法来完成:trySplit (或者返回null,意思是什么都不能拆分,或者返回一个新的Spliterator,意思是一个新块)和forEachRemaning,一旦不能再拆分任务,forEachRemaning就会处理元素。这就是偷工作能帮你的忙。
你说你的块是如何被计算出来的(通常被分成两半),以及当你不能再分开的时候该怎么做。ForkJoinPool将向所有线程分派第一个块,当其中一些线程空闲时,它们可以从其他线程查询其他队列,并查看它们是否有工作。如果他们注意到在其他线程队列中有一些块,他们会拿走它们,将它们分开,然后对它们进行处理。它甚至可以证明他们没有单独完成所有的工作--其他一些线程现在可以查询这个线程的队列并注意到还有工作要做等等……这比现在要好得多,当这3个线程空闲时,它们可以做一些其他的工作--而且它们都很忙。
这个例子有点简单,但离现实并不太远。它只是需要比CPU/线程多得多的块才能工作;因此通常情况下,trySplit必须有一个智能实现,并且您需要在流的源文件中包含很多元素。
https://stackoverflow.com/questions/52717900
复制相似问题