Это появилось как "побочный эффект" для ответа по другому вопросу. Это больше о любопытстве, чем о реальной проблеме.
Java SE 7 предлагает то, что Oracle называет "картой fork/join". Это, по-видимому, превосходный способ запланировать работу нескольких процессоров. Хотя я понимаю, как он должен работать, я не понимаю, где это лучше, и претензии, связанные с кражей работы.
Возможно, у кого-то еще есть представление о том, почему этот подход был бы желателен (кроме того, что он имеет причудливое имя).
Подчиненными примитивами fork/join являются ForkJoinTask
s, которые являются Future
s, и идея либо выполняет работу немедленно [sic] (формулировка вводит в заблуждение как "немедленно" подразумевает, что это происходит синхронно в основном потоке, в действительности это происходит внутри Future
) ниже определенного порогового значения или, делят работу на две задачи рекурсивно до достижения порога.
Будущее - это концепция инкапсуляции задачи, которая выполняется асинхронно в объект непрозрачным и неуказанным способом. У вас есть функция, которая позволяет вам проверить, доступен ли результат, и вы получаете функцию, которая позволяет (ждать, и) извлекать результат.
Строго говоря, вы даже не знаете, будет ли будущее выполняться асинхронно, оно может выполняться внутри get()
. Реализация могла бы в теории также порождать поток для каждого будущего или использовать пул потоков.
На практике Java реализует фьючерсы как задачи в очереди задач, при этом добавляется пул потоков (то же самое верно для всей структуры fork/join).
Документация fork/join дает этот конкретный пример использования:
protected void compute() {
if (mLength < sThreshold) {
computeDirectly();
return;
}
int split = mLength / 2;
invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
new ForkBlur(mSource, mStart + split, mLength - split,
mDestination));
}
Это отправляет задачи в основную очередь задач threadpool, чтобы указать, насколько Mergesort пересечет их (благодаря рекурсии).
Скажем, например, что у нас есть массив из 32 "элементов" для обработки и имеет порог 4 и равномерно распределяется, он будет производить 8 задач с 4 "элементами" каждый и выглядит следующим образом:
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
.
00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15|16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
. . .
00 01 02 03 04 05 06 07|08 09 10 11 12 13 14 15|16 17 18 19 20 21 22 23|24 25 26 27 28 29 30 31
. . . . . . .
00 01 02 03|04 05 06 07|08 09 10 11|12 13 14 15|16 17 18 19|20 21 22 23|24 25 26 27|28 29 30 31
------------------------------------------------------------------------------------------------
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
В одноядерном процессоре это будет подчиняться/выполнять (очень сложным образом) группы задач 1-2-3-4-5-6-7-8 в порядке.
На двухъядерном процессоре это подает/выполнит (1,3) - (2,4) - (5,7) - (6,8) [1].
На четырехъядерном процессоре это будет подавать/исполнять (1,3,5,7) - (2,4,6,8).
Для сравнения, наивная реализация без всякой превосходной магии сразу отправит задачи 1-2-3-4-5-6-7-8 в очередь задач. Всегда.
В одноядерном процессоре это отправит/выполнит 1-2-3-4-5-6-7-8.
На двухъядерном процессоре это будет подавать/исполнять (1,2) - (3,4) - (5,6) - (7,8).
На четырехъядерном процессоре это будет подавать/выполнять (1,2,3,4) - (5,6,7,8).
Вопросы:
-
Вместо того, чтобы просто перебивать sпороговые последовательные элементы в одну задачу и отправлять одну задачу за другой в очередь задач пула потоков, генерируется иерархическая иерархия рекурсии. Это включает в себя построение, привязку и уничтожение объектов N + log2 (N) для N подзадач, которые фактически ничего не делают. Почему этот начальник?
-
Никакая местность ссылки не сохраняется. Ни кеширование процессоров, ни виртуальная память не обрабатываются. Почему этот начальник?
-
За исключением однопроцессорной системы, задачи гарантированно не планируются в порядке, близком к их первоначальному порядку. Это может быть проблемой, если это действительно не имеет значения, но это делает такие вещи, как например. забор или барьер в значительной степени неосуществимы. Единственный способ иметь что-то вроде забора - ждать завершения корневого объекта и только после этого отправлять новые задачи. Это эквивалентно полному конвейеру (это именно то, чего вы никогда не хотите делать).
-
Документация Oracle утверждает, что этот подход реализует кражу работы и поэтому лучше, чем пул потоков. Я этого не вижу. Все, что я вижу, это очень сложный способ отправки задач в обычный обычный пул потоков. Как это должно магически выполнять кражу работы?
[1] Пусть это не слишком сложно и предположим, что рабочие потоки не обгоняют друг друга, все задачи выполняют одно и то же время для обработки. В противном случае выполнение может, конечно, происходить в другом порядке, хотя представление будет таким же.