Подтвердить что ты не робот

Java ForkJoinPool с нерекурсивными задачами, работает ли воровство?

Я хочу отправить задачи Runnable в ForkJoinPool с помощью метода:

forkJoinPool.submit(Runnable task)

Заметьте, я использую JDK 7.

Под капотом они превращаются в объекты ForkJoinTask. Я знаю, что ForkJoinPool эффективен, когда задача разбивается на более мелкие рекурсивно.

Вопрос:

В ForkJoinPool все еще работает воровство, если нет рекурсии?

Стоит ли в этом случае?

Обновление 1: Задачи небольшие и могут быть неуравновешенными. Даже для строго равных задач такие вещи, как переключение контекста, планирование потоков, парковка, пропуски страниц и т.д., Мешают дисбалансу.

Обновление 2: Дуг Ли написал в группе Concurrency JSR-166 Interest, указав на это:

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

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

Обновление 3: Масштабируемость ForkJoinPool - бенчмаркинг команды пинг-понга Akka показывает отличные результаты.

Несмотря на это, для более эффективного применения ForkJoinPool требуется настройка производительности.

4b9b3361

Ответ 1

ForkJoinPool Исходный код имеет приятный раздел под названием "Обзор реализации", прочитанный для получения окончательной правды. Ниже приводится объяснение для JDK 8u40.

Начиная с первого дня, ForkJoinPool имел рабочую очередь на рабочий поток (назовите их "рабочие очереди" ). Разбитые задачи вставляются в локальную рабочую очередь, готовые к повторной загрузке рабочего и выполняются - другими словами, это выглядит как стек из перспективы рабочего потока. Когда рабочий истощает свою рабочую очередь, он обходит и пытается украсть задания из других рабочих очередей. Это "кража работы".

Теперь, перед (IIRC) JDK 7u12, ForkJoinPool была одна глобальная очередь подачи. Когда рабочие потоки исчерпали локальные задачи, а также задачи кражи, они добрались туда и попытались выяснить, доступна ли внешняя работа. В этом дизайне нет преимущества против регулярного, скажем, ThreadPoolExecutor, поддерживаемого ArrayBlockingQueue.

После этого он значительно изменился. После того, как эта очередь представления была идентифицирована как серьезное узкое место в производительности, Doug Lea и др. чередуя очереди отправки. Оглядываясь назад, это очевидная идея: вы можете повторно использовать большую часть механики, доступную для рабочих очередей. Вы даже можете свободно распределять эти очереди отправки на рабочие потоки. Теперь внешняя подача переходит в одну из очередей отправки. Затем, работники, которые не имеют работы для того, чтобы намазать, могут сначала просмотреть очередь представления, связанную с конкретным работником, а затем бродить вокруг, глядя в очереди отправки других. Можно также называть это "кражу работы".

Я видел много рабочих нагрузок, пользующихся этим. Это особое преимущество дизайна ForkJoinPool даже для простых нерекурсивных задач было признано давно. Многие пользователи в concurrency -interest @попросили простого исполняющего роль рабочего исполнителя без всего ForkJoinPool arcanery. Это одна из причин, почему у нас Executors.newWorkStealingPool() в JDK 8 onward - в настоящее время делегируется ForkJoinPool, но открывается для предоставления более простая реализация.