Существует множество способов поддержки многопроцессорной работы для витой программы. Однако одним из важных вопросов для ответа на этот вопрос является то, что вы ожидаете от своей модели параллелизма, и как ваше приложение имеет дело с общим состоянием.
В одном процессе Скрученное приложение, параллелизм все взаимодействует (с помощью асинхронных API ввода-вывода Twisted), а общее состояние может храниться в любом месте объекта Python. Ваш код приложения работает, зная, что до тех пор, пока он не откажется от контроля, больше ничего не будет запущено. Кроме того, любая часть вашего приложения, которая хочет получить доступ к некоторой части общего состояния, может, вероятно, сделать это довольно легко, поскольку это состояние, вероятно, хранится в скучном старом объекте Python, к которому легко получить доступ.
Если у вас есть несколько процессов, даже если они все запущены приложениями с витой поддержкой, то у вас есть две формы параллелизма. Один из них аналогичен предыдущему случаю - в рамках конкретного процесса параллелизм является совместным. Тем не менее, у вас новый вид, в котором работают несколько процессов. Планировщик процессов вашей платформы может переключать выполнение между этими процессами в любое время, и у вас очень мало контроля над этим (а также очень мало видимости того, когда это произойдет). Он может даже планировать два из ваших процессов одновременно работать на разных ядрах (возможно, это даже то, на что вы надеетесь). Это означает, что вы теряете некоторые гарантии относительно согласованности, поскольку один процесс не знает, когда второй процесс может прийти и попытаться работать в каком-то общем состоянии. Это приводит к другой важной области рассмотрения, как вы фактически разделяете состояние между процессами.
В отличие от единой модели процесса, у вас больше нет удобных и легкодоступных мест для хранения вашего состояния, где весь ваш код может достичь этого. Если вы поместите его в один процесс, весь код в этом процессе может легко получить доступ к нему как к обычным объектам Python, но любой код, запущенный в любом из ваших других процессов, больше не имеет легкого доступа к нему. Возможно, вам придется найти систему RPC, чтобы ваши процессы взаимодействовали друг с другом.Или вы можете архивировать свой процесс, чтобы каждый процесс получал запросы, которые требуют сохранения состояния в этом процессе. Примером этого может быть веб-сайт с сеансами, где все состояние о пользователе хранится в их сеансе, а их сеансы идентифицируются с помощью файлов cookie. Интерфейсный процесс может получать веб-запросы, проверять куки-файлы, искать, какой серверный процесс отвечает за этот сеанс, а затем перенаправить запрос на этот внутренний процесс. Эта схема означает, что back-end обычно не требуется связываться (пока ваше веб-приложение достаточно простое, то есть до тех пор, пока пользователи не будут взаимодействовать друг с другом или работают с общими данными).
Обратите внимание, что в этом примере модель предварительного кодирования не подходит. Передний процесс должен иметь собственный порт прослушивания, чтобы он мог проверять все входящие запросы до того, как они будут обработаны внутренним процессом.
Конечно, существует много типов приложений, в которых используется множество других моделей для управления состоянием. Для выбора правильной модели для многопроцессорной обработки необходимо сначала понять, какой параллелизм имеет смысл для вашего приложения, и как вы можете управлять состоянием вашего приложения.
С учетом того, что с очень новыми версиями Twisted (невыпущенными по состоянию на данный момент) довольно легко разделить прослушивающий TCP-порт между несколькими процессами. Вот фрагмент кода, который демонстрирует один из способов вы можете использовать некоторые новые API-интерфейсы для достижения этой цели:
from os import environ
from sys import argv, executable
from socket import AF_INET
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main(fd=None):
root = File("/var/www")
factory = Site(root)
if fd is None:
# Create a new listening port and several other processes to help out.
port = reactor.listenTCP(8080, factory)
for i in range(3):
reactor.spawnProcess(
None, executable, [executable, __file__, str(port.fileno())],
childFDs={0: 0, 1: 1, 2: 2, port.fileno(): port.fileno()},
env=environ)
else:
# Another process created the port, just start listening on it.
port = reactor.adoptStreamPort(fd, AF_INET, factory)
reactor.run()
if __name__ == '__main__':
if len(argv) == 1:
main()
else:
main(int(argv[1]))
В старых версиях, вы можете иногда убраться с помощью fork
разделить порт. Однако, это скорее к ошибкам, не будет работать на некоторых платформах, и не поддерживаются способ использования Twisted:
from os import fork
from twisted.internet import reactor
from twisted.web.server import Site
from twisted.web.static import File
def main():
root = File("/var/www")
factory = Site(root)
# Create a new listening port
port = reactor.listenTCP(8080, factory)
# Create a few more processes to also service that port
for i in range(3):
if fork() == 0:
# Proceed immediately onward in the children.
# The parent will continue the for loop.
break
reactor.run()
if __name__ == '__main__':
main()
Это работает из-за нормальное поведение вилки, где вновь созданный процесс (ребенок) наследует все дескрипторы памяти и файла из исходного процесса (родителя). Поскольку процессы в противном случае изолированы, эти два процесса не мешают друг другу, по крайней мере, до того, как выполняется код Python, который они выполняют. Поскольку файловые дескрипторы унаследованы, родительский или любой из дочерних объектов может принимать соединения в порту.
Поскольку перенаправление HTTP-запросов - такая легкая задача, я сомневаюсь, что вы заметите большую часть улучшения производительности, используя любой из этих методов. Первое немного лучше, чем проксирование, потому что оно упрощает развертывание и упрощает работу с приложениями, отличными от HTTP. Последнее, вероятно, является большей частью ответственности, чем того стоит.
Jean-Paul, прежде всего, спасибо за этот подробный ответ. Очень признателен! Мое приложение довольно просто, между процессами вообще нет общего состояния, нет сеансов для обработки, нет необходимости в общении между процессами и т. Д. Моя задача хорошо подходит для параллельных вычислений и (теоретически) не заботьтесь, чтобы мои процессы выполнялись на одном сервере или каждый процесс запускался на его собственной машине. Причина, по которой мне нужно использовать более крупные серверы с многоядерными процессорами, - это то, что Amazon предлагает лучшую производительность ввода-вывода. –
немного переработал источник Twisted trunk (posixbase.py, tcp.py), и похоже, что изменения в повторном использовании существующего сокета действительно очень новые. Обязательно буду следить за этими новыми функциями и надеяться увидеть их в выпуске 12.1 :) –
Я провел несколько тестов, и у меня есть два вопроса: 1) не только рабочие, но и мастер принимают входящие соединения - как я могу заставить только рабочих принять? 2) верно ли, что установка «backlog» на вызов «reactor.listenTCP» будет применяться к сокету в целом - это глубина очереди в ядре и, следовательно, для всех рабочих вместе? – oberstet