2015-05-05 11 views
21

Я хочу представить Runnable задачи в ForkJoinPool с помощью метода:Java ForkJoinPool с нерекурсивными задачами, работает ли воровство?

forkJoinPool.submit(Runnable task) 

Обратите внимание, я использую JDK 7.

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

Вопрос:

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

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

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

Update 2: Doug Lea писал в Concurrency JSR-166 Interest группы, давая намек на это:

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

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

Update 3: Scalability of ForkJoinPool - бенчмаркинга Akka команда пинг-понг показывает отличные результаты.

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

+0

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

+0

@Adam Gent Мы тоже использовали Disruptor - это потрясающе быстро. И да, идея RingBuffer применяется во многих современных структурах данных. Тем не менее, они различаются по порядку: 1. Разрушитель использует 1 нить - 1 потребительскую модель для конвейерной обработки и не может красть работу, тогда как 2. FJ - это пул работников - работа распределяется по числу потоков (m: n). –

+0

Я знаком с различиями в fj-rb, но я думаю, что оба они могут пригодиться в меньшей блокировке, но я не специалист каким-либо образом. –

ответ

13

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

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

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

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

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