Мне нужно было выполнять свои воркеры на нескольких ядрах, потому что у меня есть возможность разделить свою задачу на несколько задач вручную, поэтому мне вяло нужно использовать newFixedThreadPool
, но мой воркер поднимает 100% загрузку процессора. Если он использует все ядра для этого, то все в порядке, но он работает только в 2 раза быстрее, чем без пула потоков, поэтому кажется, что его использование бессмысленно.
Я пытался использовать простой newFixedThreadPool
:
ExecutorService pool = Executors.newFixedThreadPool (Runtime.getRuntime ().availableProcessors ());
for (int i = 0; i < Runtime.getRuntime ().availableProcessors (); i++)
pool.execute (new MyTask (this));
и ThreadPoolExecutor
с пройденной очередью:
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<> ();
for (int i = 0; i < Runtime.getRuntime ().availableProcessors (); i++)
queue.add (new MyTask (this));
ThreadPoolExecutor pool = new ThreadPoolExecutor (Runtime.getRuntime ().availableProcessors (), Runtime.getRuntime ().availableProcessors (), 0, TimeUnit.MILLISECONDS, queue);
pool.prestartAllCoreThreads ();
А результат все тот же.
MyTask тривиален и написан, например:
protected static class MyTask implements Runnable {
@Override
public void run () {
int i = 0;
while (true)
i++;
}
После запуска воркеров без ThreadPoolExecutor
загрузка процессора нормальна и колеблется в пределах 10-20%. Так что же будет не так?
Вы обвиняете ThreadPoolExecutor
, хотя на самом деле, скорее всего, ваш рабочий код заставляет ваш процессор загружаться на 100%.
@jbx, но без него не загружается
У вас есть бесконечный цикл без какого-либо блокирующего кода. Таким образом, потребуется 1 ядро (или половина его, если у вас процессор с гиперпоточностью) и он будет использовать его как можно больше. Если вы используете пул потоков, он запустит несколько рабочих процессов и загрузит все ядра, используя 100% вашего ЦП. Не уверен, что вы ожидали вместо этого.
Ух ты. Да, именно об этом я и спрашивал. Мне нужно отсортировать кучу случайных чисел в бесконечном цикле и найти нужные, поэтому я могу разделить эту задачу на несколько, потому что ни одному из них не нужно связываться с другими. Все отличается.
@Acuna, до сих пор не ясно, в чем на самом деле твоя задача. Но чтобы распределить свою задачу на несколько ядер, вам нужна некоторая стратегия распределения вашей работы по частям. Возможно, вы захотите использовать пул Fork-Join для реализации алгоритма кражи работы, чтобы рабочие брали части проблемы, а затем результат объединялся. Не зная вашей реальной проблемы трудно сказать.
@jbx Я хочу просто разделить свои задачи с бесконечными циклами на несколько ядер. Потому что я думал, чем Java может это из коробки.
@Acuna да, вы можете, но вам нужно решить, как разделить задачу, как один поток узнает, что делать, и не повторять то же самое, что делает другая задача. Java не может знать, чего вы пытаетесь достичь, поэтому вам нужно решить, как разделить задачу и распределить ее.
@jbx Я могу просто разделить свою задачу на несколько задач, каждая из которых проходит через несколько номеров, что не требуется для связи с другими задачами.
@Acuna, поэтому вы должны заметить ускорение, если вы разделите числа и используете пул потоков. В качестве альтернативы вы можете использовать ForkJoinPool, который концептуально разбивает большие задачи на более мелкие, а затем распределяет их между разными работниками, а затем объединяет результаты каждого работника. Шаблон как бы разработан для вашего варианта использования.
@jbx ForkJoinPool предназначен для разделения задач в процессе, но мне просто нужно разделить его только при запуске и работать с ним в бесконечном цикле. Но я нашел для него библиотеку Java-Thread-Affinity (github.com/OpenHFT/Java-Thread-Affinity), которая разделяет пулы потоков даже с бесконечным циклом на отдельные ядра — одна задача на ядро, это именно то, что я нужно, очень рекомендую!
Хотя неясно, что именно вы пытаетесь выяснить, вы должны помнить об этом.
Один поток будет использовать только одно (виртуальное) ядро за раз. Обычно он поддерживается одним процессом ядра ОС (я говорю обычно, потому что есть много необычных вещей, которые некоторые фреймворки и виртуальные машины делают с виртуальными потоками, а что не в последнее время).
Когда поток блокируется (чтобы выполнить ввод-вывод или ждать чего-то, или находится в спящем режиме, или имеется явный вызов yield()
), он уступает ядро ЦП другому потоку.
Таким образом, запуск простого бесконечного цикла в одном рабочем потоке будет использовать только одно ядро. Вот почему вы наблюдаете загрузку от 10% до 20% (в зависимости от того, сколько виртуальных ядер у вас есть на вашей машине).
С другой стороны, если вы создадите пул потоков из такого количества ядер, какое у вас есть, и все они будут выполнять один и тот же бесконечный цикл в отдельных потоках, каждый возьмет одно доступное ядро и будет использовать его, что приведет к загрузке вашего процессора до 100%.
Это совершенно разные рабочие, поэтому вы не можете ожидать, что ваш первый рабочий завершится быстрее или что-то в этом роде. Во всяком случае, это будет медленнее, потому что вы исчерпали все ресурсы ЦП, а ОС и даже JVM теперь будут конкурировать, чтобы делать свое дело, например, запускать сборщик мусора и т. д.
Если под «причудливыми вещами» вы имеете в виду Project Loom , не путайте это с Green Threads с первых дней существования Java. Во-первых, зеленые потоки были полностью ограничены одним потоком хост-ОС, тогда как виртуальные потоки в Project Loom могут выполняться в нескольких потоках хост-ОС.
@BasilBourque Я не говорил исключительно о «зеленых нитях» как таковых, просто хотел указать, что, хотя в своем объяснении я говорил о сопоставлении процессов Thread-to-OS, это может быть не всегда так. Существует множество вариантов моделей параллелизма в пользовательском пространстве (см. Golang). Виртуальные потоки Project Loom (я думаю, первоначально они назывались Fibers) отличаются от старых Java Green Threads (как вы говорите, они были привязаны к одному процессу ОС). Возможно, «зеленый поток» был неправильным термином (в старые времена, когда мы делали это вручную на C с помощью setjmp
и longjmp
, мы просто называли их «потоками пользовательского уровня»)
У вас есть бесконечный цикл, который не делает ничего, кроме выполнения вычислений. Этот код никогда не блокирует. То есть код никогда не перестает ждать внешних эффектов. Например, код никогда не связывается с базой данных, никогда не записывает в хранилище и никогда не делает вызов по сети.
Если нечего делать, кроме вычислений на ЦП, такая задача считается привязкой к ЦП. Такая задача будет использовать 100% ядра процессора.
Вы умножаете эту задачу, чтобы иметь одну такую задачу на ядро для каждого ядра в вашей машине. Таким образом, все ядра загружены на 100%. Скорее всего, ваша машина будет нагреваться, вентиляторы будут вращаться, а процессор будет тормозить.
Это кажется довольно очевидным. Возможно, вам следует отредактировать свой вопрос, чтобы объяснить, почему вы ожидали другого поведения.
Совет. Избегайте загрузки всех ядер задачами, привязанными к процессору. В ОС есть много процессов, которые необходимо запустить, и другие приложения должны быть запущены.
Ух ты. Да, я не знал, что это так работает, поэтому моя проблема в том, что я просто хочу использовать все свои ядра, чтобы теоретически ускорить свои задачи. Или я что-то пропустил?
Трудно сказать, не видя, что именно рассчитывается. Пожалуйста, отредактируйте пост и добавьте минимально воспроизводимый пример.