Написание сокет-сервера на Python, рекомендуемые стратегии?
Недавно я читал этот документ, в котором перечислен ряд стратегий, которые можно использовать для реализации сокет-сервера. А именно они являются:
- Обслужите много клиентов с каждым потоком, и используйте неблокирующий ввод / вывод и уведомление о готовности, инициируемое уровнем
- Обслужите много клиентов с каждым потоком и используйте неблокирующий ввод-вывод и уведомление об изменении готовности
- Обслужите много клиентов с каждым потоком сервера, и используйте асинхронный ввод-вывод
- обслуживать одного клиента с каждым потоком сервера и использовать блокировку ввода-вывода
- Соберите код сервера в ядре
Теперь я был бы признателен за подсказку, которую следует использовать в CPython, которая, как мы знаем, имеет несколько положительных и отрицательных сторон. В основном меня интересует производительность в условиях высокого параллелизма, и да, некоторые текущие реализации слишком медленные.
Так что, если я могу начать с простого, "5" отсутствует, так как я не собираюсь ничего взламывать в ядре.
"4" Также похоже, что это должно быть из-за GIL. Конечно, вы могли бы использовать многопроцессорность вместо потоков здесь, и это дает значительный импульс. Блокирование ввода-вывода также имеет то преимущество, что его легче понять.
И здесь мои знания немного уменьшаются:
"1" - это традиционный выбор или опрос, который можно легко объединить с многопроцессорной обработкой.
"2" - это уведомление о готовности к смене, используемое более новыми epoll и kqueue
"3" Я не уверен, что есть какие-либо реализации ядра для этого, которые имеют оболочки Python.
Итак, в Python у нас есть набор отличных инструментов, таких как Twisted. Возможно, это лучший подход, хотя я тестировал Twisted и нашел его слишком медленным на многопроцессорной машине. Возможно, 4 витка с балансировщиком нагрузки могут сделать это, я не знаю. Любой совет будет принят во внимание.
6 ответов
asyncore
в основном "1" - он использует select
внутренне, и у вас есть только один поток, обрабатывающий все запросы. Согласно документам, он также может использовать poll
, (РЕДАКТИРОВАТЬ: Удалена ссылка Twisted, я думал, что он использует asyncore, но я был не прав).
"2" может быть реализовано с помощью python-epoll (просто погуглил - никогда раньше не видел). РЕДАКТИРОВАТЬ: (из комментариев) В Python 2.6 модуль select имеет встроенную функцию epoll, kqueue и kevent (на поддерживаемых платформах). Таким образом, вам не нужны никакие внешние библиотеки для запуска по краям.
Не исключайте "4", поскольку GIL будет отброшен, когда поток фактически выполняет или ожидает IO-операций (вероятно, большую часть времени). Это не имеет смысла, если у вас есть огромное количество соединений, конечно. Если у вас есть много обработки, то Python может не иметь смысла ни с одной из этих схем.
Для гибкости, возможно, посмотрите на Twisted?
На практике ваша проблема сводится к тому, сколько обработки вы собираетесь делать для запросов. Если у вас много обработки и вам нужно использовать преимущества многоядерной параллельной работы, то вам, вероятно, понадобится несколько процессов. С другой стороны, если вам просто нужно прослушивать множество соединений, выберите select или epoll, с небольшим количеством потоков.
Одним из решений является Gevent. Gevent использует собственный опрос событий на основе libevent с легким кооперативным переключением задач, реализованным greenlet.
Все, что вы получаете, - это производительность и масштабируемость системы событий с элегантной и простой моделью блокировки программирования ввода-вывода.
(Я не знаю, что такое условное соглашение об ответе на действительно старые вопросы, но решил, что все равно добавлю свои 2 цента)
Как насчет "вилки"? (Я предполагаю, что это то, что делает ForkingMixIn). Если запросы обрабатываются в архитектуре "без общих ресурсов" (кроме БД или файловой системы), fork() запускается довольно быстро на большинстве *nixes, и вам не нужно беспокоиться обо всех глупых багах и осложнениях от нарезки резьбы.
ИМХО, потоки - это болезнь дизайна, навязанная нам операционными системами с слишком тяжелыми процессами. Клонирование таблицы страниц с атрибутами копирования при записи кажется небольшой ценой, особенно если вы все равно используете интерпретатор.
Извините, я не могу быть более конкретным, но я больше программист по переходу с Perl на Ruby (когда я не работаю над массами Java на работе)
Обновление: я наконец сделал несколько моментов на нити против вилки в свое "свободное время". Проверьте это:
http://roboprogs.com/devel/2009.04.html
Расширен: http://roboprogs.com/devel/2009.12.html
Могу ли я предложить дополнительные ссылки?
cogen - это кроссплатформенная библиотека для сетевого ориентированного программирования на основе сопрограмм с использованием расширенных генераторов из python 2.5. На главной странице проекта cogen есть ссылки на несколько проектов с аналогичной целью.
Мне нравится ответ Дугласа, но в стороне...
Вы можете использовать централизованный поток / процесс отправки, который прослушивает уведомления о готовности, используя select
и делегирует пул рабочих потоков / процессов, чтобы помочь в достижении ваших целей параллелизма.
Однако, как упомянул Дуглас, GIL не будет удерживаться во время самых длительных операций ввода-вывода (поскольку в Python-API ничего не происходит), поэтому, если вас беспокоит задержка ответа, вы можете попробовать переместить критические части вашего код для CPython API.
http://docs.python.org/library/socketserver.html
Что касается многопроцессорных (многоядерных) машин. Благодаря CPython благодаря GIL для масштабирования вам потребуется как минимум один процесс на ядро. Поскольку вы говорите, что вам нужен CPython, вы можете попытаться сравнить это с ForkingMixIn
, С Linux 2.6 может дать некоторые интересные результаты.
Другой способ - использовать Stackless Python. Вот как ЕВА решила это. Но я понимаю, что это не всегда возможно.