ForkJoinPool, кажется, тратить нить

Я сравниваю два варианта тестовой программы. Оба работают с 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 ответа

Решение

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

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

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

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

Другие вопросы по тегам