好问题!使用 Python 多处理,可以使用 3 种“启动方法”,它们都对您的问题有影响。 As the docs explain,他们是:
-
'spawn':父进程启动一个新的python解释器进程。子进程只会继承运行进程对象的run() 方法所需的资源。特别是,不会继承父进程中不必要的文件描述符和句柄。与使用 fork 或 forkserver 相比,使用这种方法启动进程相当慢。在 Unix 和 Windows 上可用。 Windows 和 macOS 上的默认设置。
-
'fork':父进程使用os.fork() fork Python 解释器。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全地分叉多线程进程是有问题的。仅在 Unix 上可用。 Unix 上的默认设置。
-
'forkserver'当程序启动并选择forkserver启动方式时,会启动一个服务器进程。从那时起,每当需要一个新进程时,父进程都会连接到服务器并请求它派生一个新进程。 fork 服务器进程是单线程的,因此使用os.fork() 是安全的。没有不必要的资源被继承。在支持通过 Unix 管道传递文件描述符的 Unix 平台上可用。
至于 Gunicorn 的 pre-fork 模型,你已经解释得很好了。每个工人都在自己的进程中运行。由于您尝试在工作人员中使用多处理,而不是与 Gunicorn 一起使用,这应该是可行的,但仍然会有点错误。
import multiprocessing
mp = multiprocessing.get_context('spawn')
这段代码为我们提供了mp 对象,它具有与多处理模块相同的API,但具有设置的启动方法。在上面的代码中,它被设置为'spawn'。这是在 Gunicorn worker 中使用多处理的最安全途径,因为它与创建它的进程最隔离,并且不太可能遇到意外共享资源的问题。
with mp.Pool(processes=4) as pool:
pool.map(some_expensive_function, some_data)
然后我们使用mp 对象来创建一个进程池,就像您所做的那样。此代码必须位于仅在工作进程中调用/使用的函数/模块内。如果在服务器进程中使用它可能会导致问题。
- 是否会启动其他操作系统进程?加速会是我所期望的吗? (即,类似于我在 Flask 生产环境之外运行
ProcessPool 吗?)
这里有很多问题。将启动其他操作系统进程。加速可能会有很大差异,并且取决于许多因素,例如:
- 还有多少其他进程正在运行? Gunicorn 运行了多少个工作处理器?
- 服务器负载过重吗?
- 处理器有多少个内核?
- 工作的并行性如何?
some_expensive_function(data_1) 是否必须等待 some_expensive_function(data_2) 才能工作?
要弄清楚使用多处理是否更快,以及它会快多少,您必须对其进行测试。在此之前,您可以做的最好的事情是根据上面列出的因素进行粗略估计。
- (续)如果 Gunicorn 创建了 4 个网络工作者,那么现在会有 7 个操作系统进程在运行吗? 9?有没有做太多的风险? Gunicorn 是否假设每个工人都不会分叉或不在乎?
如果有 4 个 Gunicorn 工作进程,并且每个都在用 4 个进程完成一个使用多处理的请求,那么将有 1 个 Gunicorn 父进程 + 4 个工作进程 + 4 * 4 个工作子进程 = 21 个进程,而不是提及 Nginx 使用的进程。
Gunicorn 建议您创建 (2 * num_cores) + 1 worker,但在您的情况下,您可能希望通过将其除以 4 来减少它,以说明您的 worker 进程本身在使用多个内核时工作得最好。要找到最有效的配置,您必须对各种配置进行基准测试,以找出最适合您的配置。
- 如果 web-worker 在启动 ProcessPool 后死亡或被杀死,上下文管理器是否会正确关闭它?
这取决于工人的死亡方式。如果它通过 SIGKILL 被杀死,或者遇到分段错误或其他一些严重错误,那么它将突然死亡,而无需运行任何终结代码。上下文管理器只能在 try-finally 块能够执行“finally”块的情况下完成其工作。有关更多信息,请查看此答案:Does 'finally' always execute in Python?
- 这是明智之举吗?有哪些替代方案?
这本身并不疯狂,但这不是我通常推荐的那种方法。一种替代方法是使用自己的服务器实现some_expensive_function。您的 Gunicorn 工作人员可以使用 IPC 或网络通信将工作发送到 some_expensive_function 服务器进程,它会处理在子进程之间分配这项工作。此类设计的一个优点是,如果性能需要,some_expensive_function 服务器进程可以轻松移动到另一台计算机上运行。
这类似于数据库通常作为自己的服务器进程运行的方式,并且可以位于同一台计算机或单独的计算机上(可能位于用于只读查询的负载平衡器或分片配置之后),具体取决于必须满足哪些性能要求。
如果您决定走这条路,您可能会发现 Python 包 Celery 可用于分发 Gunicorn 工作人员的工作。
如果您想这样做,您可能应该使用preload_app=True 运行 Gunicorn。