有多种方法可以支持 Twisted 应用程序的多进程操作。不过,一开始要回答的一个重要问题是,您希望并发模型是什么,以及您的应用程序如何处理共享状态。
在单进程 Twisted 应用程序中,并发都是协作的(在 Twisted 的异步 I/O API 的帮助下),共享状态可以保存在 Python 对象所在的任何地方。您的应用程序代码运行时知道,在它放弃控制之前,不会运行其他任何东西。此外,您的应用程序中想要访问某个共享状态的任何部分都可以很容易地做到这一点,因为该状态可能保存在一个易于访问的无聊的旧 Python 对象中。
当您有多个进程时,即使它们都在运行基于 Twisted 的应用程序,您也有两种形式的并发。一种与前一种情况相同——在特定进程中,并发是协作的。但是,您有一种新类型,其中正在运行多个进程。您平台的进程调度程序可能会随时在这些进程之间切换执行,而您对此几乎没有控制权(也几乎无法看到它何时发生)。它甚至可以安排您的两个进程在不同的内核上同时运行(这甚至可能是您所希望的)。这意味着您失去了一些关于一致性的保证,因为一个进程不知道第二个进程何时会出现并尝试在某些共享状态上进行操作。
与单进程模型不同,您不再有任何方便、易于访问的位置来存储您的所有代码都可以访问的状态。如果将它放在一个进程中,则该进程中的所有代码都可以像普通 Python 对象一样轻松访问它,但是在任何其他进程中运行的任何代码都无法再轻松访问它。您可能需要找到一个 RPC 系统来让您的进程相互通信。或者,您可以构建您的流程划分,以便每个流程只接收需要存储在该流程中的状态的请求。这方面的一个例子可能是一个带有会话的网站,其中有关用户的所有状态都存储在他们的会话中,并且他们的会话由 cookie 标识。前端进程可以接收 Web 请求,检查 cookie,查找负责该会话的后端进程,然后将请求转发到该后端进程。这种方案意味着后端通常不需要通信(只要您的 Web 应用程序足够简单 - 即,只要用户不相互交互,或对共享数据进行操作)。
请注意,在该示例中,预分叉模型是不合适的。前端进程必须独占监听端口,以便它可以在后端进程处理所有传入请求之前检查它们。
当然,有许多类型的应用程序,以及许多其他用于管理状态的模型。为多处理选择正确的模型需要首先了解哪种并发对您的应用程序有意义,以及如何管理应用程序的状态。
话虽如此,对于非常新版本的 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()
这是因为 fork 的正常行为,其中新创建的进程(子进程)从原始进程(父进程)继承所有内存和文件描述符。由于进程在其他方面是隔离的,因此这两个进程不会相互干扰,至少就它们正在执行的 Python 代码而言。由于文件描述符是继承的,因此父级或任何子级都可以接受端口上的连接。
由于转发 HTTP 请求是一项如此简单的任务,我怀疑您是否会注意到使用这些技术中的任何一种都可以显着提高性能。前者比代理好一点,因为它简化了您的部署并且更容易地适用于非 HTTP 应用程序。后者可能更多的是一种负担,而不是值得接受。