Сколько потоков создавать и когда? - PullRequest
10 голосов
/ 04 февраля 2009

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

Как мне решить, сколько потоков мне нужно обработать? Я полагаю, я не могу открыть поток для каждого потока RTP, так как могут быть тысячи. Стоит ли учитывать количество ядер процессора? Что еще имеет значение? Спасибо.

Ответы [ 7 ]

22 голосов
/ 04 февраля 2009

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

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

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

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

10 голосов
/ 04 февраля 2009

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

Количество исполнительных блоков (XU)

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

Отношение ввода-вывода к вычислениям (%IO)

Если потоки никогда не ожидают ввода-вывода, но всегда вычисляют (% IO = 0), использование большего количества потоков, чем XU, только увеличивает нагрузку на память и переключение контекста. Если потоки всегда ждут ввода-вывода и никогда не вычисляют (% IO = 1), то использование варианта poll() или select() может быть хорошей идеей.

Для всех других ситуаций XU / %IO дает приблизительное количество потоков, необходимых для полного использования доступных XU.

Доступная память (Mem)

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

Другие факторы

Производительность всей системы все еще может быть ограничена другими факторами, даже если вы можете угадать или (лучше) измерить числа выше. Например, в системе может быть запущена другая служба, которая использует некоторые XU и память. Другая проблема - общая доступная пропускная способность ввода-вывода (IOCap). Если вам нужно меньше вычислительных ресурсов на передаваемый байт, чем обеспечивает ваш XU, очевидно, вам нужно меньше заботиться об их полном использовании и больше об увеличении пропускной способности ввода-вывода.

Подробнее об этой последней проблеме см. В этом Google Talk о модели линии крыши .

4 голосов
/ 04 февраля 2009

Я бы сказал, попробуйте использовать только один поток; это делает программирование намного проще. Хотя вам нужно использовать что-то вроде libevent для мультиплексирования соединений, у вас не возникнет никаких неожиданных проблем с синхронизацией.

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

Даже если многопоточная реализация необходима, может быть проще разбить ее на несколько процессов вместо потоков (т. Е. Не разделять адресное пространство; либо fork (), либо выполнить несколько копий процесса из родительского), если они не у меня много общих данных.

Вы также можете использовать что-то вроде «Twisted» в Python, чтобы упростить реализацию (для этого она и предназначена).

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

2 голосов
/ 04 февраля 2009

Слушайте людей, которые советуют вам использовать libevent (или специальные утилиты для ОС, такие как epoll / kqueue). В случае многих соединений это абсолютно необходимо, потому что, как вы сказали, создание потоков будет огромным ударом по производительности, а select () также не совсем обрезает.

2 голосов
/ 04 февраля 2009

Я бы посмотрел в пул потоков для этого приложения.

http://threadpool.sourceforge.net/

Разрешить пулу потоков управлять вашими потоками и очередью.

Вы можете настроить максимальное и минимальное количество используемых потоков на основе профилирования производительности позже.

1 голос
/ 04 февраля 2009

Пусть ваша программа решит. Добавьте к нему код, который измеряет пропускную способность и динамически увеличивает / уменьшает количество потоков, чтобы максимизировать его.

Таким образом, ваше приложение всегда будет работать хорошо, независимо от количества ядер выполнения и других факторов

0 голосов
/ 04 февраля 2009

Рекомендуется избегать попыток создания одного (или даже N) потоков для каждого запроса клиента. Этот подход является классически не масштабируемым, и у вас наверняка возникнут проблемы с использованием памяти или переключением контекста. Вместо этого вы должны рассмотреть использование подхода с пулом потоков и рассматривать входящие запросы как задачи для любого потока в пуле для обработки. Масштабируемость этого подхода затем ограничивается идеальным количеством потоков в пуле - обычно это связано с количеством ядер ЦП. Вы хотите попробовать, чтобы каждый поток использовал ровно 100% ЦП на одном ядре - поэтому в идеальном случае у вас будет 1 поток на ядро, это сведет переключение контекста к нулю. В зависимости от характера задач это может оказаться невозможным, возможно, потокам придется ждать внешних данных, или читать с диска, или что-то еще, чтобы вы могли обнаружить, что число потоков увеличивается с некоторым коэффициентом масштабирования.

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