Почему ExecutorService создается через зло newCachedThreadPool? - PullRequest
8 голосов
/ 16 мая 2011

Пол Тима презентация имеет следующую строку:

Executors.newCacheThreadPool зло, умри, умри, умри

Почему это зло?

Я рискну догадаться: это потому, что количество потоков будет неограниченным образом расти. Таким образом, сервер с косой чертой, вероятно, умрет, если будет достигнуто максимальное число потоков JVM?

Ответы [ 3 ]

17 голосов
/ 23 мая 2011

(это Пол)

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

AПул потоков по своей сути представляет собой очередь и точку передачи работы в системе.То есть что-то питает его работу (и это может кормить работу и в других местах).Если пул потоков начинает расти, потому что он не может удовлетворить спрос.

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

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

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

IIRC, оригинальные серверы SEDA Мэтта Уэлша (которые являются асинхронными) создавали пулы потоков, которые изменяли свой размер в соответствии с характеристиками сервера.

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

Между прочим, JVM ограничивают потоки 16k (обычно) или 32k потоками в зависимости от JVM.Но если вы привязаны к процессору, это ограничение не очень важно - запуск еще одного потока в системе с привязкой к процессору контрпродуктивен.

Я с радостью запустил системы с 4 или 5 тысячами потоков.Но при приближении к пределу в 16 Кб, как правило, все затихает (это ограничение применяется JVM - у нас было намного больше потоков в linux C ++), даже если нет привязки к процессору.

8 голосов
/ 16 мая 2011

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

4 голосов
/ 17 мая 2011

Есть несколько проблем с этим.Неограниченный рост с точки зрения потоков - очевидная проблема - если у вас есть задачи, связанные с процессором, то для их запуска гораздо больше, чем доступно для ЦП, просто создайте накладные расходы планировщика с переключением контекста потоков повсеместно, и ни один из них не будет значительно прогрессировать.Если ваши задачи связаны с IO, то все становится более тонким.Знать, как определить размер пулов потоков, ожидающих сетевой или файловый ввод-вывод, гораздо сложнее и во многом зависит от задержек этих событий ввода-вывода.Более высокие задержки означают, что вам нужно (и вы можете поддерживать) больше потоков.

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

Другая большая проблема с пулом кэшированных потоковявляется то, что это может быть медленным для потока задачи производителя.Пул настроен с SynchronousQueue для задач, которые будут предложены.Эта реализация очереди в основном имеет нулевой размер и работает только тогда, когда есть соответствующий потребитель для производителя (существует опрос потока, когда другой предлагает).Реальная реализация была значительно улучшена в Java6, но она все еще сравнительно медленна для производителя, особенно когда он терпит неудачу (поскольку производитель несет ответственность за создание нового потока для добавления в пул).Часто для потока-производителя более идеальным является просто отбросить задачу в фактической очереди и продолжить.

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

Разработчики видят пул кэшированных потоков и используют его вслепую, не задумываясь о последствиях..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...