ЦП ThreadPoolExecutor загружен на 100%

Мне нужно было выполнять свои воркеры на нескольких ядрах, потому что у меня есть возможность разделить свою задачу на несколько задач вручную, поэтому мне вяло нужно использовать 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%. Так что же будет не так?

Трудно сказать, не видя, что именно рассчитывается. Пожалуйста, отредактируйте пост и добавьте минимально воспроизводимый пример.

Turing85 07.01.2023 17:29

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

jbx 07.01.2023 17:38

@jbx, но без него не загружается

Acuna 07.01.2023 17:42

У вас есть бесконечный цикл без какого-либо блокирующего кода. Таким образом, потребуется 1 ядро ​​(или половина его, если у вас процессор с гиперпоточностью) и он будет использовать его как можно больше. Если вы используете пул потоков, он запустит несколько рабочих процессов и загрузит все ядра, используя 100% вашего ЦП. Не уверен, что вы ожидали вместо этого.

jbx 07.01.2023 17:52

Ух ты. Да, именно об этом я и спрашивал. Мне нужно отсортировать кучу случайных чисел в бесконечном цикле и найти нужные, поэтому я могу разделить эту задачу на несколько, потому что ни одному из них не нужно связываться с другими. Все отличается.

Acuna 07.01.2023 18:03

@Acuna, до сих пор не ясно, в чем на самом деле твоя задача. Но чтобы распределить свою задачу на несколько ядер, вам нужна некоторая стратегия распределения вашей работы по частям. Возможно, вы захотите использовать пул Fork-Join для реализации алгоритма кражи работы, чтобы рабочие брали части проблемы, а затем результат объединялся. Не зная вашей реальной проблемы трудно сказать.

jbx 07.01.2023 23:54

@jbx Я хочу просто разделить свои задачи с бесконечными циклами на несколько ядер. Потому что я думал, чем Java может это из коробки.

Acuna 08.01.2023 17:20

@Acuna да, вы можете, но вам нужно решить, как разделить задачу, как один поток узнает, что делать, и не повторять то же самое, что делает другая задача. Java не может знать, чего вы пытаетесь достичь, поэтому вам нужно решить, как разделить задачу и распределить ее.

jbx 09.01.2023 01:49

@jbx Я могу просто разделить свою задачу на несколько задач, каждая из которых проходит через несколько номеров, что не требуется для связи с другими задачами.

Acuna 10.01.2023 20:54

@Acuna, поэтому вы должны заметить ускорение, если вы разделите числа и используете пул потоков. В качестве альтернативы вы можете использовать ForkJoinPool, который концептуально разбивает большие задачи на более мелкие, а затем распределяет их между разными работниками, а затем объединяет результаты каждого работника. Шаблон как бы разработан для вашего варианта использования.

jbx 11.01.2023 21:31

@jbx ForkJoinPool предназначен для разделения задач в процессе, но мне просто нужно разделить его только при запуске и работать с ним в бесконечном цикле. Но я нашел для него библиотеку Java-Thread-Affinity (github.com/OpenHFT/Java-Thread-Affinity), которая разделяет пулы потоков даже с бесконечным циклом на отдельные ядра — одна задача на ядро, это именно то, что я нужно, очень рекомендую!

Acuna 15.01.2023 17:59
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
11
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Хотя неясно, что именно вы пытаетесь выяснить, вы должны помнить об этом.

Один поток будет использовать только одно (виртуальное) ядро ​​за раз. Обычно он поддерживается одним процессом ядра ОС (я говорю обычно, потому что есть много необычных вещей, которые некоторые фреймворки и виртуальные машины делают с виртуальными потоками, а что не в последнее время).

Когда поток блокируется (чтобы выполнить ввод-вывод или ждать чего-то, или находится в спящем режиме, или имеется явный вызов yield()), он уступает ядро ​​ЦП другому потоку.

Таким образом, запуск простого бесконечного цикла в одном рабочем потоке будет использовать только одно ядро. Вот почему вы наблюдаете загрузку от 10% до 20% (в зависимости от того, сколько виртуальных ядер у вас есть на вашей машине).

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

Это совершенно разные рабочие, поэтому вы не можете ожидать, что ваш первый рабочий завершится быстрее или что-то в этом роде. Во всяком случае, это будет медленнее, потому что вы исчерпали все ресурсы ЦП, а ОС и даже JVM теперь будут конкурировать, чтобы делать свое дело, например, запускать сборщик мусора и т. д.

Если под «причудливыми вещами» вы имеете в виду Project Loom , не путайте это с Green Threads с первых дней существования Java. Во-первых, зеленые потоки были полностью ограничены одним потоком хост-ОС, тогда как виртуальные потоки в Project Loom могут выполняться в нескольких потоках хост-ОС.

Basil Bourque 08.01.2023 09:59

@BasilBourque Я не говорил исключительно о «зеленых нитях» как таковых, просто хотел указать, что, хотя в своем объяснении я говорил о сопоставлении процессов Thread-to-OS, это может быть не всегда так. Существует множество вариантов моделей параллелизма в пользовательском пространстве (см. Golang). Виртуальные потоки Project Loom (я думаю, первоначально они назывались Fibers) отличаются от старых Java Green Threads (как вы говорите, они были привязаны к одному процессу ОС). Возможно, «зеленый поток» был неправильным термином (в старые времена, когда мы делали это вручную на C с помощью setjmp и longjmp, мы просто называли их «потоками пользовательского уровня»)

jbx 09.01.2023 01:45

Задача, связанная с ЦП, обязательно займет весь ЦП.

У вас есть бесконечный цикл, который не делает ничего, кроме выполнения вычислений. Этот код никогда не блокирует. То есть код никогда не перестает ждать внешних эффектов. Например, код никогда не связывается с базой данных, никогда не записывает в хранилище и никогда не делает вызов по сети.

Если нечего делать, кроме вычислений на ЦП, такая задача считается привязкой к ЦП. Такая задача будет использовать 100% ядра процессора.

Вы умножаете эту задачу, чтобы иметь одну такую ​​задачу на ядро ​​для каждого ядра в вашей машине. Таким образом, все ядра загружены на 100%. Скорее всего, ваша машина будет нагреваться, вентиляторы будут вращаться, а процессор будет тормозить.

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

Совет. Избегайте загрузки всех ядер задачами, привязанными к процессору. В ОС есть много процессов, которые необходимо запустить, и другие приложения должны быть запущены.

Ух ты. Да, я не знал, что это так работает, поэтому моя проблема в том, что я просто хочу использовать все свои ядра, чтобы теоретически ускорить свои задачи. Или я что-то пропустил?

Acuna 07.01.2023 19:03

Другие вопросы по теме