2013-06-03 3 views
4

Я пытаюсь использовать ForkJoinPool для распараллеливания вычислений с интенсивным вычислением процессора. Мое понимание ForkJoinPool заключается в том, что он продолжает работать до тех пор, пока любая задача доступна для выполнения. К сожалению, я часто наблюдал за рабочими потоками на холостом ходу/ожидании, поэтому не все CPU остаются занятыми. Иногда я даже наблюдал дополнительные рабочие потоки.ForkJoinPool stalls во время invokeAll/join

Я этого не ожидал, так как я строго старался использовать non blocking задач. Мое наблюдение очень похоже на наблюдение ForkJoinPool seems to waste a thread. После отладки в ForkJoinPool у меня есть предположение:

Я использовал invokeAll() для распространения работы над списком подзадач. После того, как invokeAll() закончил выполнение первой задачи, он начинает соединяться с другими. Это отлично работает, пока следующая задача для присоединения не находится поверх очереди выполнения. К сожалению, я выполнил дополнительные задачи асинхронно, не присоединившись к ним. Я ожидал, что ForkJoin Framework продолжит выполнение этой задачи сначала, а затем вернется к объединению любых оставшихся задач.

Но, похоже, это не работает. Вместо этого рабочий поток блокируется вызовом wait(), пока задача, ожидающая получения, не будет готова (предположительно, выполнена другим рабочим потоком). Я не проверял это, но, похоже, это общий недостаток вызова join().

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

Итак, почему ForkJoinTask.doJoin() не просто выполняет любую доступную задачу поверх своей очереди, пока она не будет готова (либо выполнена сама по себе, либо украдена другими)?

+0

Вы можете поделиться с нами своим кодом? Когда-то 3 строки кода говорят более 30 строк прозы. – Fildor

+0

Я положил что-то здесь: http://pastebin.com/kgHuJZMM – Ditz

ответ

3

Вы мертвы правильно о join(). Я написал статью this два года назад, которая указывает на проблему с join().

Как я уже сказал, фреймворк не может выполнять вновь отправленные запросы до тех пор, пока не закончит предыдущие. И каждый WorkThread не может украсть, пока не закончится текущий запрос, который приводит к wait().

Дополнительные темы, которые вы видите, являются «продолжением потоков». Поскольку join() в конечном итоге выдает wait(), эти потоки необходимы, чтобы вся инфраструктура не останавливалась.

+0

да, я прочитал вашу статью и на деле это привело меня к моему заключению. Я согласен с вами в том, что F/J Framework не может обрабатывать задачи, которые блокируются внешними ресурсами. Но я до сих пор не понимаю, почему WorkerThread не может выполнить любую другую доступную работу, либо украденную из любого хвоста, либо взятую из ее собственного верха, пока ожидающая готовность не будет готова. – Ditz

+0

Подотчетность. Если в задании другого запроса есть исключение, то найти владельца нельзя. В пуле используется только один UncaughtExceptionHandler. Вы можете сами посмотреть код. Это запутанный беспорядок, но вы можете проследить его. – edharned

+0

Не убежден: та же проблема возникает, если выполнение задачи украдено из другой очереди. Текущая FJTask отвечает за их захват и вызывает this.setExceptionalCompletion() вместо того, чтобы передавать их в пулы UncaughtExceptionHandler. Остается вопрос: почему бы не выполнять отложенные задачи с головы так же, как выполнение задач, украденных из хвоста. – Ditz

3

Поскольку никто не кажется, чтобы понять мой вопрос, я пытаюсь объяснить, что я нашел после того, как несколько ночей отладки:

Текущая реализация ForkJoinTasks работает хорошо, если все вилки/Объединить строго парными. Иллюстрируя вилку с помощью открывающего кронштейна и соединяясь замыкающим, идеальный бинарный шаблон объединения вилок может выглядеть так:

{([] []) ([] [])} {([] []) { ([] [])}

Если вы используете invokeAll(), вы можете также представить список подзадач, как это:

{([] [] [] []) ([] [] [ ] []) ([] [] [] [])}

То, что я тем не менее выглядит этот шаблон:

{([) ([)} ...]]

Вы можете утверждать, что это выглядит плохо или является неправильным использованием каркаса fork-join. Но единственным ограничением является то, что the tasks completion dependencies are acyclic, иначе вы можете столкнуться с тупиком. Пока мои задачи [] не зависят от задач (), я не вижу в этом никаких проблем. Оскорбительный ]] просто выражает, что я не жду их явно; они могут закончиться когда-нибудь, для меня это не имеет значения (на тот момент).

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

Брешь, как представляется, текущей реализации присоединиться(): присоединение к ) ожидает увидеть его соответствующий ( на вершине своей очереди выполнения, но он находит [ и недоумевает Вместо. просто выполнение [], чтобы избавиться от него, текущего потока приостанавливается (не вызывая ожидания()), пока кто-то приходит, чтобы выполнить неожиданную задачу. Это вызывает резкое выступление ломается.

Моих основным намереваются было добавьте дополнительную работу в очередь, чтобы предотвратить приостановку рабочего потока, если очередь запущена пустым. Unfortuna то происходит обратное :-(

1

Вы не используете этот каркас для очень узкой цели, для которой он предназначался.

Каркас начал свою жизнь в качестве эксперимента в исследовательской работе 2000 года. Он был изменен с тех пор, но базовый дизайн, fork-and-join на больших массивах, остается тем же. Основная цель - научить студентов, как идти по листьям сбалансированного дерева. Когда люди используют его для других, чем простая обработка массивов, происходят странные вещи. То, что он делает в Java7, вне меня; что является целью статьи.

Проблемы только ухудшаются в Java8. Там это двигатель, чтобы управлять всей параллельной работой потока. Прочитайте во второй части этой статьи. Списки интересов лямбда заполняются сообщениями о потоках, переполнении стека и ошибках памяти.

Вы используете его на свой страх и риск, когда не используете его для чисто рекурсивной декомпозиции больших структур данных. Даже тогда чрезмерные потоки, которые он создает, могут вызвать хаос. Я не буду продолжать эту дискуссию.