Flask 遇到 GIL 性能瓶颈的解决方案

1. 什么是 GIL

Python 中的 GIL(全局解释器锁,Global Interpreter Lock)是一个机制,确保在任何时候只有一个线程可以执行 Python 字节码。这是为了简化 CPython 的内存管理,但是它也带来了一些性能问题,尤其是在多线程应用中。

2. GIL 的影响

多线程限制:由于 GIL 的存在,即使在多核 CPU 上,Python 的多线程程序也不能真正并行执行多个线程的 Python 代码。只有一个线程能持有 GIL 并执行 Python 代码,其他线程只能等待。

  1. I/O 密集型任务:GIL 对 I/O 密集型任务的影响较小,因为当一个线程等待 I/O 操作时,它会释放 GIL,其他线程可以执行。
  2. CPU 密集型任务:对于需要大量计算的 CPU 密集型任务,多线程的性能会受到 GIL 的限制,无法充分利用多核 CPU。

解决 GIL 的方法

多进程

使用多进程而不是多线程,因为每个进程都有自己的 Python 解释器和 GIL,可以在多核 CPU 上并行执行。

使用 C 扩展

对于性能关键的部分,可以使用 C 扩展模块,避免 Python 代码中的 GIL 限制。

替代 Python 实现

使用没有 GIL 的 Python 实现,例如 Jython 或 IronPython。不过这些实现通常不如 CPython 普及,且生态系统支持可能有限。

使用 Gunicorn 部署多进程 Flask

什么是 Gunicorn

Gunicorn(Green Unicorn)是一个基于 Python 的 WSGI HTTP 服务器。它是一个预分叉(pre-fork)工作模型服务器,旨在支持并发请求,特别适合部署在生产环境中的 Flask、Django 等 WSGI 应用。Gunicorn 以其简单、高效和易用著称,是许多 Python Web 应用程序的推荐选择。

WSGI(Web Server Gateway Interface)是 Python 应用和 Web 服务器之间的一种标准接口。WSGI 由 PEP 3333 定义,旨在促进 Web 服务器和 Web 应用/框架之间的通用性和互操作性。WSGI 的出现使得开发者能够编写兼容多种服务器和网关的 Web 应用,同时也使得服务器开发者能够创建支持多种应用和框架的服务器。

Gunicorn 的工作方式

主进程启动

Gunicorn 启动时,会首先创建一个主进程(master process)。
主进程负责管理工作进程(worker processes),包括启动、监控和重启工作进程。

工作进程预分叉

根据配置(如 workers 参数),主进程会预先创建多个工作进程。
每个工作进程都是主进程的一个子进程,独立于其他工作进程。
工作进程将负责实际处理客户端请求。

请求处理

工作进程通过监听指定的端口和地址,等待客户端的请求(操作系统将 socket 连接即客户端的请求分配给工作进程)。
每个工作进程独立处理请求,执行应用逻辑并生成响应。
处理完成后,工作进程将响应返回给客户端。

并发处理

Gunicorn 支持多种工作模型,如同步(sync)、异步(async)、事件驱动(eventlet/gevent)等。
可以通过配置 worker_class 参数选择不同的工作模型。
支持多线程(通过 threads 参数)和多进程(通过 workers 参数)并发处理请求。

信号处理

主进程和工作进程使用信号(signals)进行通信。
主进程可以接收和处理外部信号,如启动、停止、重启等。
主进程可以向工作进程发送信号,以控制其行为。

工作进程管理

主进程会持续监控工作进程的状态。
如果某个工作进程异常退出,主进程会自动重启一个新的工作进程,以保持工作进程的数量稳定。

简单例子

gunicorn -w 4 -b 127.0.0.1:8000 myapp:app

-w 4:启动 4 个工作进程。
-b 127.0.0.1:8000:绑定到 127.0.0.1 的 8000 端口。
myapp:app:myapp 是 Python 模块名,app 是 Flask 应用实例名。

使用 Gunicorn/Flask-Script 优雅的部署多进程 Flask

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

run.py

from flask_script import Manager, Command
from app import app
import os

manager = Manager(app)

## 命令执行方式
# class GunicornServer(Command):
#     """Run the app within Gunicorn"""

#     def run(self):
#         os.system("gunicorn -w 4 -b 0.0.0.0:8000 app:app")


## 内置参数设置
class GunicornServer(Command):
    """
    GunicornServer
    """

    def run(self):
        """
        run
        """
        from gunicorn.app.base import Application

        class FlaskApplication(Application):
            """
            https://docs.gunicorn.org/en/stable/settings.html#worker-processes
            """

            def init(self, parser, opts, args):
                """
                init
                """

                return {
                    'bind': '{0}:{1}'.format('0.0.0.0', APP_PORT),
                    'workers': APP_WORKERS,
                    'threads': APP_THREADS,
                    'worker_class': 'gthread',
                    'post_fork': post_fork
                }

            def load(self):
                """
                load
                """
                return app

        FlaskApplication().run()
        return

manager.add_command('runserver', GunicornServer())

if __name__ == "__main__":
    manager.run()

运行应用

python run.py runserver