ThreadPoolExecutor vs ForkJoinPool: кража подзадач

Из java-документов,

ForkJoinPool отличается от других видов ExecutorService главным образом тем, что использует перехват работы: все потоки в пуле пытаются найти и выполнить подзадачи, созданные другими активными задачами (в конечном итоге блокируя ожидание работы, если таковой не существует).

Это обеспечивает эффективную обработку, когда большинство задач порождают другие подзадачи (как это происходит с большинством задач ForkJoinTask). При установке для asyncMode значения true в конструкторах ForkJoinPools также может подходить для использования с задачами в стиле событий, которые никогда не объединяются.

После прохождения ниже Пример ForkJoinPool. В отличие от ThreadPoolExecutor, я не видел параметра для установки размера очереди. Я не понял, как работает механизм кражи ForkJoinPool.

//creating the ThreadPoolExecutor

ThreadPoolExecutor executorPool = new ThreadPoolExecutor(2, 10, 60, TimeUnit.SECONDS, 
new ArrayBlockingQueue<Runnable>(3000), threadFactory, rejectionHandler);

Предположим, я создал ThreadPoolExecutor с 10 потоками и отправил 3000 вызываемых задач. Как эти потоки распределяют нагрузку по выполнению подзадач?

И как пул ForkJoin ведет себя по-разному для одного и того же варианта использования?


person Ravindra babu    schedule 31.10.2015    source источник


Ответы (2)


В ForkJoinPool есть два вида очередей — пул, который вы в основном использовали при отправке задачи, и очередь, специфичная для потока (т.е. по одной для каждого потока). Из ForkJoinTask вы можете вызывать новые задачи (как правило, часть вашей проблемы).

Эти новые задачи предлагаются не в очередь пула, а в конкретную очередь потока. Таким образом, они берутся/вытягиваются в приоритете перед пулом, как будто вы сделали всю работу в одном задании. Кроме того, задача инициатора блокируется для выполнения подзадачи.

На самом деле «заблокированное время» тратится на выполнение подзадач. Будет глупо позволять другим тредам «бездельничать», пока один из них завален работой. Итак, имеет место «воровство работы».

Чтобы выйти за пределы. Чтобы быть эффективным, «кража работы» берет/вытягивает задачу из противоположной границы. Это значительно снижает конкуренцию за запись в очередь.

Всегда эффективно, лучше всего разделить проблему на две подзадачи и позволить подзадаче разделяться снова и снова. Даже если вы знаете, что проблема должна быть разделена непосредственно на N частей. Это связано с тем, что «воровство работы» требует одновременной записи на общий ресурс, поэтому ограничьте его активацию и конкуренцию!

person LoganMzz    schedule 31.10.2015
comment
так что у нас есть две очереди в пуле ForkJoin? Один глобальный, а другой на уровне потока. Кража происходит на уровне потока? Как установить глобальную очередь и размер задачи потока? - person Ravindra babu; 31.10.2015
comment
два вида очереди. Есть пул и поток s. Чтобы быть более точным, сбор задач потока — это FIFO (стек). Кража работы всегда происходит в первом потоке. Взять задачу из первого пула — это просто базовое поведение диспетчера Executor. Насколько я знаю, WorkQueue (реализация, используемая ForkJoin) не привязаны к размеру. - person LoganMzz; 01.11.2015

Если у вас есть заранее 3000 задач, и они не будут порождать другие задачи, они не будут вести себя существенно по-разному: с 10 потоками 10 задач будут выполняться одновременно, пока все они не будут выполнены.

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

person Warren Dew    schedule 31.10.2015
comment
Каков механизм назначения 3000 подзадач 10 потокам? Это круговая система и в порядке живой очереди? Как мы можем установить одинаковые значения для ForkJoinPool? - person Ravindra babu; 31.10.2015
comment
ThreadPoolExecutor обычно принимает задачи из очереди, поэтому, если очередь обслужена в порядке очереди, задачи будут обрабатываться примерно в порядке очереди. То, как они распределяются по потокам в пуле потоков, зависит от конкретного типа используемого пула; вы можете думать об этом как о циклическом переборе, но на самом деле это не имеет значения, поскольку задачи обязательно даются свободным потокам, которые не отличаются. ForkJoinPool по умолчанию выполняет задачи в порядке поступления последним, но вы можете настроить его так, чтобы он выполнялся первым. См. Javadoc для обоих классов для более подробной информации. - person Warren Dew; 31.10.2015