Сколько потоков слишком много?


312

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

Мой вопрос: какова хорошая точка отсечения для потоков ввода-вывода, подобных этим? Я знаю, что это будет приблизительная оценка, но мы говорим сотни? Тысячи?

Как бы я понял, что это за отсечка?


РЕДАКТИРОВАТЬ:

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


1
@ryeguy: Суть в том, что вы не должны устанавливать максимум в пуле потоков, если нет проблем с производительностью для начала. Большинство советов по ограничению пула потоков до ~ 100 потоков смешны, большинство пулов потоков имеют / способ / больше потоков и никогда не имеют проблем.
GEOCHET

см. дополнение к моему ответу ниже о том, что измерять.
paxdiablo

Не забывайте, что Python по своей природе не является многопоточным. В любой момент времени выполняется один код операции байт-кода. Это потому, что Python использует Global Interpreter Lock.
Спросите

1
@Jay D: Я бы сказал, что в тот момент, когда вы достигли предела, ваша производительность начинает падать.
ниндзя

6
@GEOCHET "Суть в том, что вы не должны устанавливать максимум в пуле потоков" Ммм ... скажи что? Пулы потоков фиксированного размера имеют преимущества постепенного ухудшения качества и масштабируемости. Например, в настройках сети, если вы создаете новые потоки на основе клиентских подключений, без фиксированного размера пула вы рискуете узнать ( трудный путь ), сколько потоков может обработать ваш сервер, и каждый отдельный подключенный клиент будет страдать. Пул фиксированного размера действует как труба клапана, не позволяя вашему серверу пытаться откусить больше, чем он может пережевать.
b1nary.atr0phy

Ответы:


206

Некоторые люди скажут, что двух нитей слишком много - я не совсем в этом лагере :-)

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

Если использование потоков достигает 3, то 100 - это слишком много. Если он остается на уровне 100 в течение большей части дня, увеличьте его до 200 и посмотрите, что произойдет.

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


Для уточнения и уточнения:

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

Я написал код пула потоков и соединений с базой данных, и у них есть следующие функции (которые я считаю важными для производительности):

  • минимальное количество активных тем.
  • максимальное количество потоков.
  • закрытие потоков, которые давно не использовались.

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

Вам необходимо сбалансировать использование ресурсов с наличием неиспользуемых потоков (A) и использованием ресурсов с отсутствием достаточного количества потоков для выполнения работы (B).

(A) - это, как правило, использование памяти (стеки и т. Д.), Поскольку поток, не выполняющий работу, не будет использовать большую часть ЦП. (B) обычно будет задержкой в ​​обработке запросов по мере их поступления, так как вам нужно ждать, пока поток станет доступным.

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

Первый - это количество доступных подключений к БД. Это может быть жестким пределом, если вы не можете увеличить его в СУБД - я собираюсь предположить, что ваша СУБД может принимать неограниченное количество соединений в этом случае (хотя в идеале вы должны также измерять это).

Затем количество потоков, которые вы должны иметь, зависит от вашего исторического использования. Минимум, который вы должны запустить, - это минимальное число, которое вы когда-либо использовали, + A%, с абсолютным минимумом (например, и сделать его настраиваемым так же, как A) 5.

Максимальное количество потоков должно быть вашим историческим максимумом + B%.

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


В ответ на «что именно я должен измерить?» вопрос:

Что вы должны конкретно измерить, так это максимальное количество потоков при одновременном использовании (например, ожидание возврата из вызова БД) под нагрузкой. Затем добавить коэффициент запаса прочности на 10% , например (выделено, так как другие плакаты , кажется, принять мои примеры как основные рекомендации).

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


Если потоки создаются для входящих запросов, то использование потоков будет отражать количество необслуживаемых запросов. Нет никакого способа определить «оптимальное» число из этого. Более того, вы обнаружите, что большее количество потоков приводит к увеличению конкуренции за ресурсы и, следовательно, количество активных потоков будет увеличиваться.
Эндрю Грант

@ Андрей, создание потока занимает время, и вы можете определить оптимальное число на основе исторических данных [+ N%] (следовательно, измеряйте, не угадывайте). Кроме того, большее количество потоков вызывает конфликт ресурсов только тогда, когда они выполняют работу, а не ожидают сигнала / семафора.
paxdiablo

Где эти данные о «создании потоков», вызывающие проблемы с производительностью при использовании пула потоков? Хороший пул потоков не будет создавать и уничтожать потоки между задачами.
GEOCHET

@Pax Если все ваши потоки ожидают выполнения одних и тех же семафоров для выполнения запросов к БД, это и есть определение конкуренции. Также неверно говорить, что потоки ничего не стоят, если они ожидают семафор.
Эндрю Грант

1
@ Андрей, я не понимаю, зачем вам семафор-блокировать запросы к БД, любая приличная БД разрешит параллельный доступ, и многие потоки ожидают ответов. И потоки не должны стоить никакого времени выполнения, пока семафор заблокирован, они должны находиться в заблокированной очереди, пока семафор не будет освобожден.
paxdiablo

36

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

  1. Размер стека потоков: в Linux размер стека потоков по умолчанию составляет 8 МБ (вы можете использовать ulimit -a, чтобы выяснить это).
  2. Макс. Виртуальная память, поддерживаемая данным вариантом ОС. Linux Kernel 2.4 поддерживает адресное пространство памяти 2 ГБ. с ядром 2.6, я немного больше (3ГБ)
  3. [1] показывает расчеты для максимального количества потоков на данный Max VM Supported. Для 2.4 получается около 255 потоков. для 2.6 число немного больше.
  4. Какой у тебя вроде планировщик ядра. Сравнивая планировщик ядра Linux 2.4 с 2.6, последний дает вам планирование O (1), не зависящее от количества задач, существующих в системе, в то время как первое - больше O (n). Таким образом, SMP-возможности расписания ядра также играют важную роль в максимальном количестве устойчивых потоков в системе.

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

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

Я уверен, что планировщик ядра Windows также делает что-то подобное, чтобы справиться с чрезмерным использованием ресурсов

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/


17

Если ваши потоки выполняют какую-либо ресурсоемкую работу (ЦП / Диск), вы редко увидите преимущества, превышающие один или два, и слишком большое их количество очень быстро снизит производительность.

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

Хорошим решением является размещение запросов в пуле, которые затем отправляются рабочим потокам из пула потоков (и да, избегание непрерывного создания / уничтожения потоков - отличный первый шаг).

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


Да, и его следует использовать вместе с очередью или пулом запросов.
Эндрю Грант

2
@ Андрей: почему? Он должен добавлять задачу в пул потоков каждый раз, когда получает запрос. Пул потоков должен выделять поток для задачи, когда он доступен.
GEOCHET

Итак, что вы делаете, когда у вас есть сотни запросов на вход и нет потоков? Создать больше? Блок? Вернуть ошибку? Поместите ваши запросы в пул, который может быть настолько большим, насколько это необходимо, а затем направьте эти запросы в очереди в ваш пул потоков, когда потоки станут свободными.
Эндрю Грант

«Для выполнения ряда задач создано несколько потоков, которые обычно организованы в очередь. Как правило, задач гораздо больше, чем потоков. Как только поток завершит свою задачу, он запросит следующую задачу из очереди. пока все задачи не будут выполнены ".
GEOCHET

@Andrew: Я не уверен, какой пул потоков Python использует OP, но если вы хотите получить реальный пример этой функции, я описываю: msdn.microsoft.com/en-us/library/…
GEOCHET

10

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

Если вам действительно нужно максимально использовать многопоточный Python, вы можете рассмотреть возможность использования Jython или чего-то еще.


4
Прочитав это, я попытался запустить сито с задачами Эратосфена в трех потоках. Конечно же, это было на 50% медленнее, чем выполнение одних и тех же задач в одном потоке. Спасибо за внимание. Я запускал Eclipse Pydev на виртуальной машине, которой было выделено два процессора. Далее я попробую сценарий, который включает в себя несколько вызовов базы данных.
Дон Киркби

3
Существует два (как минимум) типа задач: привязка к процессору (например, обработка изображений) и привязка ввода / вывода (например, загрузка из сети). Очевидно, что «проблема» GIL не сильно повлияет на задачи, связанные с вводом / выводом. Если ваши задачи связаны с процессором, вы должны рассмотреть многопроцессорность, а не многопоточность.
iutinvg

1
да, поток Python улучшился, если у вас много сетевых io.Я меняю его на поток и получаю на 10 * быстрее, чем обычный код ...
Тянь

8

Как правильно сказал Пакс, измеряй, не угадывай . То, что я сделал для DNSwitness, и результаты были удивительными: идеальное количество потоков было намного выше, чем я думал, что-то вроде 15 000 потоков, чтобы получить самые быстрые результаты.

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

Полные меры (только на французском языке) в Combien de fils d'exécution? ,


1
15000? Это немного выше, чем я ожидал. Тем не менее, если это то, что вы получили, то это то, что вы получили, я не могу с этим спорить.
paxdiablo

2
Для этого конкретного приложения большинство потоков просто ждут ответа от DNS-сервера. Таким образом, чем больше параллелизма, тем лучше во время настенных часов.
bortzmeyer

18
Я думаю, что если у вас есть те 15000 потоков, которые блокируют некоторые внешние операции ввода-вывода, то лучшим решением было бы значительно меньшее количество потоков, но с асинхронной моделью. Я говорю из опыта здесь.
Стив

5

Я написал несколько многопоточных приложений. Обычно я допускаю, чтобы число потенциальных потоков указывалось в файле конфигурации. Когда я настроился на конкретных клиентов, я установил достаточно высокое число, чтобы загрузка всех ядер ЦП была довольно высокой, но не настолько высокой, чтобы я столкнулся с проблемами с памятью (это были 32-разрядные операционные системы в время).

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

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


1
Можете ли вы назвать некоторые числа, которые вы видели для подсчета потоков? Было бы полезно просто почувствовать это. Спасибо.
Ковач

3

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

Там, где это невозможно, учтите, что у вас есть грибные ресурсы (ЦП) и не-грибные ресурсы (оружие). Для процессоров не критично назначать каждый поток определенному процессору (хотя это помогает в управлении кэшем), но для рук, если вы не можете назначить поток для руки, вы попадаете в теорию очередей и какое оптимальное число для хранения рук занятый. Обычно я думаю, что если вы не можете направить запросы на основе используемой руки, то иметь 2-3 потока на руку будет правильным.

Сложность возникает, когда единица работы, передаваемая потоку, не выполняет достаточно атомарную единицу работы. Например, вы можете иметь поток в одной точке доступа к диску, а в другой точке ждать в сети. Это увеличивает количество «взломов», когда дополнительные потоки могут войти и выполнять полезную работу, но также увеличивает возможность для дополнительных потоков загрязнять кеши друг друга и т. Д. И перегружать систему.

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

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


2

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


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

1
@ ммр: хм нет. Идея потоков состоит в том, чтобы блокировать ввод-вывод и другие задачи.
GEOCHET

4
Я сделал следующее заявление: количество ядер на машине представляет собой жесткое ограничение на количество потоков, которые могут выполнять работу в данный момент времени, что является фактом. Конечно, другие потоки могут ожидать завершения операций ввода-вывода, и этот вопрос является важным соображением.
newdayrising

1
В любом случае - у вас есть GIL в Python, который делает потоки только теоретически параллельными. Одновременно может работать не более 1 потока, поэтому важны только операции реагирования и блокировки.
Абган

2
+1 Для понимания того, как работают компьютеры. @mmr: Вы должны понимать разницу между несколькими процессорами и несколькими процессорами. @Rich B: Пул потоков - это только один из многих способов обработки коллекции потоков. Это хороший, но, конечно, не единственный.
скорбим

2

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


1
Для Python это особенно верно, так как несколько процессов могут работать параллельно, а несколько потоков - нет. Стоимость, однако, довольно высока. Вы должны каждый раз запускать новый интерпретатор Python и подключаться к БД с каждым процессом (или использовать перенаправление некоторых каналов, но это также имеет свою цену).
Абган

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

Справедливо. Я не уверен, почему именно поэтому я получаю -2 очка, если только люди действительно не хотят видеть ответы только для потоков, вместо того, чтобы включать другие ответы, которые работают.
MMR

@mmr: Учитывая вопрос о / thread / pool, да, я думаю, что люди должны ожидать ответа о темах.
GEOCHET

Создание процесса может быть выполнено один раз при запуске (т. Е. Пул процессов вместо пула потоков). Амортизация в течение срока действия приложения может быть небольшой. Они не могут легко делиться информацией, но это дает им возможность работать на многопроцессорных системах, поэтому этот ответ полезен. +1.
paxdiablo

1

ryeguy, в настоящее время я занимаюсь разработкой подобного приложения, и мой номер потока установлен на 15. К сожалению, если я увеличу его до 20, он вылетает. Так что, да, я думаю, что лучший способ справиться с этим - это определить, позволяет ли ваша текущая конфигурация больше или меньше числа потоков.


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

-6

В большинстве случаев вы должны позволить пулу потоков обрабатывать это. Если вы разместите некоторый код или предоставите более подробную информацию, может быть легче увидеть, если по какой-то причине поведение пула потоков по умолчанию будет не лучшим.

Вы можете найти больше информации о том, как это должно работать здесь: http://en.wikipedia.org/wiki/Thread_pool_pattern


1
@Pax: Это было бы не в первый раз, когда большинство людей не хотело бы ответить на поставленный вопрос (или понять его). Я не волнуюсь.
GEOCHET

-10

Столько потоков, сколько ядер ЦП - это то, что я слышал очень часто.


5
@Rich, хотя бы объясни почему :-). Это практическое правило применяется только тогда, когда все потоки связаны с процессором; они получают один «процессор» каждый. Когда многие потоки связаны с вводом / выводом, обычно лучше иметь гораздо больше потоков, чем ЦП (ЦП указан в кавычках, поскольку он применяется к физическим потокам исполнения, например, к ядрам).
paxdiablo

1
@Abgan, я не был уверен в этом, думая, что, возможно, Python создаст «настоящие» потоки ОС (запущенные на нескольких процессорах). Если то, что вы говорите, верно (у меня нет оснований сомневаться), тогда количество ЦП не имеет значения - многопоточность полезна только тогда, когда большинство потоков чего-то ждут (например, ввод / вывод БД).
paxdiablo

1
@Rich: когда (реальная) многопоточность имеет значение для ЦП, так как вы можете одновременно запускать несколько потоков без ожидания. С одним процессором запускается только один, и преимущество заключается в том, что многие другие потоки ожидают ресурсов без процессора.
paxdiablo

1
@Pax: Думаю, вы не понимаете концепцию пулов потоков.
GEOCHET

1
@Rich, я понимаю, что пулы потоков отлично; кажется, я (и другие здесь) также понимаю аппаратное обеспечение лучше, чем вы. С одним ЦП может работать только один поток выполнения, даже если другие ожидают ЦП. Два процессора, два могут работать. Если все потоки ожидают ЦП, идеальное число потоков равно ...
paxdiablo
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.