Потребление ресурсов в ожидании потоков

Моя проблема:

Сколько больших потоков в JVM потребляет много ресурсов (память, ЦП), когда потоки TIMED_WAIT состояния (не спящие) > 99,9% времени? Когда потоки ждут, сколько накладных расходов процессора стоит поддерживать, если они вообще нужны?

Отвечает ли ответ на не связанные с JVM среды (например, ядра Linux)?

Контекст:

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

В настоящее время я достигаю этих функциональных возможностей, сохраняя все пакеты в памяти или на диске. Каждые 5 минут я удаляю истекшие пакеты из хранилища и просматриваю оставшиеся пакеты для подсчета атрибутов. Этот метод использует много памяти и имеет сложную временную сложность (O(n) для времени и памяти, где n - количество оставшихся пакетов). Это делает масштабируемость программы ужасной.

Одним из альтернативных способов решения этой проблемы является увеличение количества атрибутов каждый раз, когда приходит пакет, и запускает поток Timer(), который уменьшает количество атрибутов после истечения срока действия пакета. Это устраняет необходимость хранения всех громоздких пакетов и сокращения временной сложности до O(1). Однако это создает еще одну проблему, так как моя программа начнет иметь O(n) количество потоков, что может привести к снижению производительности. Поскольку большинство потоков будет находиться в состоянии TIMED_WAIT (Javas Timer() вызывает метод Object.wait(long)) подавляющее большинство их жизненного цикла, все равно влияет на процессор очень большим образом?

Ответ 1

Во-первых, поток Java (или .NET)! = Поток ядра/ОС.

Java, Thread высокого уровня обертка, которая абстрагирует некоторые из функциональных возможностей системы резьбы; эти виды потоков также известны как управляемые потоки. На уровне ядра поток имеет только 2 состояния: работает и не работает. Есть некоторая управляющая информация (стек, указатели инструкций, идентификатор потока и т.д.), Которую отслеживает ядро, но на уровне ядра нет такой вещи, как поток, находящийся в состоянии TIMED_WAITING (.NET эквивалентно WaitSleepJoin государство). Эти "состояния" существуют только в таких контекстах (часть того, почему C++ std::thread не имеет члена state).

Сказав это, когда управляемый поток блокируется, это делается несколькими способами (в зависимости от того, как запрашивается блокировка на управляемом уровне); реализации, которые я видел в OpenJDK для многопоточного кода, используют семафоры для обработки управляемых ожиданий (что я видел в других C++ средах, которые имеют своего рода "управляемый" класс потока, а также в .NET Core библиотеки) и использовать мьютекс для других типов ожиданий/блокировок.

Поскольку в большинстве реализаций используется какой-то механизм блокировки (например, семафор или мьютекс), ядро обычно делает то же самое (по крайней мере, в том, что касается вашего вопроса); то есть ядро извлечет поток из очереди "run" и поместит его в очередь "wait" (переключение контекста). Начало планирования потоков и, в частности, то, как ядро управляет выполнением потоков, выходит за рамки этих вопросов и ответов, тем более что ваш вопрос касается Java, и Java можно запускать на нескольких разных типах ОС (каждый из которых обрабатывает резьба совершенно по другому).

Отвечая на ваши вопросы более прямо:

Много ли потоков в JVM потребляет много ресурсов (память, процессор), когда потоки находятся в состоянии TIMED_WAIT (не спит)> 99,9% времени?

К этому следует обратить внимание на пару вещей: созданный поток потребляет память для JVM (стек, ID, сборщик мусора и т.д.), А ядро использует память ядра для управления потоком на уровне ядра. Эта память, которая израсходована, не изменится, если вы специально не скажете. Так что, если поток спит или работает, память такая же.

Процессор - это то, что изменится в зависимости от активности потока и количества запрошенных потоков (помните, что поток также потребляет ресурсы ядра, поэтому должен управляться на уровне ядра, поэтому чем больше потоков нужно обрабатывать, тем больше ядра время должно быть потрачено на управление ими).

Имейте в виду, что время ядра для планирования и запуска потоков крайне незначительно (это часть проекта), но все же стоит подумать, планируете ли вы запускать много потоков; Кроме того, если вы знаете, что ваше приложение будет работать на ЦП (или кластере) только с несколькими ядрами, чем меньше у вас будет ядер, тем больше у ядра будет для переключения контекста, добавляя дополнительное время в целом.

Когда потоки ожидают, сколько ресурсов ЦП стоит для их обслуживания, если они вообще нужны?

Никто. См. Выше, но загрузка ЦП, используемая для управления потоками, не изменяется в зависимости от контекста потока. Дополнительный ЦП может использоваться для переключения контекста, и, скорее всего, дополнительный ЦП будет использоваться самими потоками, когда он активен, но при этом ЦП не потребует дополнительных "затрат" для поддержки ожидающего потока по сравнению с работающим потоком.

Относится ли ответ также к средам, не относящимся к JVM (например, к ядрам Linux)?

И да и нет. Как уже говорилось, управляемые контексты обычно применяются к большинству этих типов сред (например, Java,.NET, PHP, Lua и т.д.), Но эти контексты могут различаться, и идиомы потоков и общие функциональные возможности зависят от используемого ядра. Таким образом, хотя одно конкретное ядро может обрабатывать потоки 1000+ на процесс, у некоторых могут быть жесткие ограничения, у других могут быть другие проблемы с большим числом потоков на процесс; вам придется обратиться к спецификациям ОС/ЦП, чтобы увидеть, какие ограничения у вас могут быть.

Поскольку большинство потоков будет находиться в состоянии TIMED_WAIT (Javas Timer() вызывает метод Object.wait(long)) в подавляющем большинстве их жизненного цикла, все равно влияет ли он на ЦП очень сильно?

Нет (часть точки заблокированного потока), но кое-что нужно учитывать: что, если (крайний случай) все (или> 50%) из этих потоков должны работать в одно и то же время? Если у вас есть только несколько потоков, управляющих вашими пакетами, это может не быть проблемой, но, скажем, у вас есть 500+; Одновременное пробуждение 250 потоков вызовет массовую нагрузку на процессор.

Поскольку вы не опубликовали никакого кода, трудно сделать конкретные предложения для вашего сценария, но можно склониться к тому, чтобы хранить структуру атрибутов как класс и сохранять этот класс в списке или хэш-карте, на которые можно ссылаться в Timer (или отдельный поток), чтобы увидеть, соответствует ли текущее время времени истечения пакета, тогда будет запущен код "expire". Это сокращает количество потоков до 1 и время доступа к O(1); но опять же, без кода, это предложение может не сработать в вашем сценарии.

Надеюсь, это поможет.