ForkJoinPool, кажется, тратить нить - PullRequest
6 голосов
/ 13 марта 2012

Я сравниваю два варианта тестовой программы. Оба работают с 4-нитью ForkJoinPool на машине с четырьмя ядрами.

В «режиме 1» я использую пул очень похоже на службу исполнителя. Я бросаю кучу задач в ExecutorService.invokeAll. Я получаю лучшую производительность, чем от обычной службы исполнения с фиксированными потоками (даже при том, что там есть вызовы Lucene, которые выполняют некоторые операции ввода-вывода).

Здесь нет разделяй и властвуй. Буквально я делаю

ExecutorService es = new ForkJoinPool(4);
es.invokeAll(collection_of_Callables);

В «режиме 2» я отправляю одну задачу в пул, и в этой задаче вызывается ForkJoinTask.invokeAll для отправки подзадач. Итак, у меня есть объект, который наследуется от RecursiveAction, и он передается в пул. В методе вычисления этого класса я вызываю invokeAll для коллекции объектов из другого класса, который также наследуется от RecursiveAction. В целях тестирования я отправляю только по одному из первых объектов. То, что я наивно ожидал увидеть, что все четыре потока заняты, так как поток, вызывающий invokeAll, будет сам брать одну из подзадач вместо того, чтобы просто сидеть и блокировать. Я могу подумать о некоторых причинах, по которым это может не сработать.

При просмотре в VisualVM в режиме 2 один поток почти всегда ожидает. Я ожидаю увидеть поток, вызывающий invokeAll, который сразу же начнет работать над одной из вызванных задач, а не просто сидеть на месте. Это, безусловно, лучше, чем взаимоблокировки, которые могут возникнуть при попытке использовать эту схему с обычным пулом потоков, но все же, что случилось? Он удерживает один поток на случай, если что-то еще будет отправлено? И если да, то почему не та же проблема в режиме 1?

До сих пор я запускал это, используя jsr166 jar, добавленный в путь к загрузочному классу java 1.6.

Ответы [ 2 ]

3 голосов
/ 15 марта 2012

Классическое использование invokeAll для пула Fork Join - это разветвление одной задачи и вычисление другой (в этом выполняющемся потоке).Поток, который не разветвляется, присоединится после его вычисления.Воровство работы приходит с обеими задачами.Когда каждая задача вычисляется, ожидается, что она выполнит свои собственные подзадачи (пока не будет достигнут некоторый порог).

Я не уверен, что invokeAll вызывается для вашего RecursiveAction.compute(), но если это invokeAll, который принимает два RecursiveActionон разветвляется один, вычисляет другой и ждет завершения разветвленной задачи.

Это отличается от простой службы executor, потому что каждая задача ExecutorService является просто Runnable в очереди.Нет необходимости для двух задач службы ExecutorService знать результат другой.Это основной вариант использования пула FJ.

3 голосов
/ 14 марта 2012

ForkJoinTask.invokeAll разветвляется на все задачи, но первая в списке.Первое задание это запускает само.Тогда это присоединяется к другим задачам.Его поток никак не попадает в пул.Таким образом, вы видите то, что блокируете поток для выполнения других задач.

...