Планирование ForkJoinPool против ExecutorService - PullRequest
0 голосов
/ 09 октября 2018

Я немного смущен внутренним механизмом планирования ExecutorService и ForkJoinPool.

Я понял, что планирование ExecutorService выполнено таким образом .

Связказадач поставлены в очередь.Как только поток становится доступным, он обрабатывает первую доступную задачу и т. Д.

Между тем, ForkJoinPool представлен как отдельный, поскольку он использует алгоритм кражи работы.Если я правильно понимаю, это означает, что поток может украсть некоторые задачи из другого потока.

Тем не менее, я не совсем понимаю разницу с механизмом, реализованным в ExecutorService.Насколько я понимаю, оба механизма должны позволять максимально сократить время простоя каждого потока.

Я бы понял, если бы в случае ExecutorService каждый поток имел свою собственную очередь.Тем не менее, это не так, поскольку очередь является общей для разных потоков пула ...

Любое разъяснение будет приветствоваться!

1 Ответ

0 голосов
/ 10 октября 2018

Предположим, у вас очень большой массив целых, и вы хотите добавить их все.С ExecutorService вы можете сказать: давайте разделим этот массив на куски, скажем, числа потоков / 4. Итак, если у вас есть массив из 160 элементов (и у вас есть 4 ЦП), вы создаете 120 / 4 / 4 = 10, так что вы бысоздать 16 кусков, каждый из которых содержит 10 дюймов.Создайте runnables / callables и отправьте их в службу-исполнитель (и, конечно, подумайте о способе объединения этих результатов, как только они будут сделаны).

Теперь вы надеетесь, что каждый из процессоров выполнит 4 из этих задачи работать над ними.Теперь давайте также предположим, что некоторые числа очень сложно добавить (конечно, нет, но потерпите меня), может получиться, что 3 потока / ЦП закончили свою работу, в то время как один из них занят только первым чанком.,Никто не хочет этого, конечно, но может случиться.Плохо то, что вы ничего не можете с этим поделать.

То, что вместо этого делает ForkJoinPool, это сказать мне, как вы хотите разделить вашу задачу и реализацию для минимальной рабочей нагрузки, которую я должен сделатьи я позабочусь об остальном.В Stream API это делается с Spliterator с;в основном с двумя методами trySplit (которые либо возвращают null, что означает, что ничто не может быть разделено больше, либо новым Spliterator - что означает новый фрагмент) и forEachRemaning, которые будут обрабатывать элементы, если вы больше не можете разбивать свою задачу.И здесь вам поможет кража труда.

Вы говорите , как вычисляются ваши чанки (обычно делятся пополам) и что делать, если вы не можете больше делиться.ForkJoinPool отправит первый чанк всем потокам, и когда некоторые из них свободны - они закончили свою работу, они могут запросить другие очереди из других потоков и посмотреть, есть ли у них работа.Если они заметят, что в очередях других потоков есть чанки, они возьмут их, разделят их самостоятельно и поработают над ними.Может даже оказаться, что они не выполняют всю работу над этими чанками самостоятельно - какой-то другой поток может теперь запросить очередь этого потока и заметить, что еще есть работа, и так далее ... Это намного лучше, чемтеперь, когда эти 3 потока свободны, они могут выполнять какую-то другую работу, и все они заняты.


Этот пример немного упрощен, но не очень далек от реальности.Просто вам нужно иметь гораздо больше блоков, чем процессоров / потоков, для кражи работы;таким образом, обычно trySplit должен иметь умную реализацию, и вам нужно много элементов в источнике вашего потока.

...