Flask 遇到 GIL 性能瓶颈的解决方案
1. 什么是 GIL
Python 中的 GIL(全局解释器锁,Global Interpreter Lock)是一个机制,确保在任何时候只有一个线程可以执行 Python 字节码。这是为了简化 CPython 的内存管理,但是它也带来了一些性能问题,尤其是在多线程应用中。
2. GIL 的影响
多线程限制:由于 GIL 的存在,即使在多核 CPU 上,Python 的多线程程序也不能真正并行执行多个线程的 Python 代码。只有一个线程能持有 GIL 并执行 Python 代码,其他线程只能等待。
- I/O 密集型任务:GIL 对 I/O 密集型任务的影响较小,因为当一个线程等待 I/O 操作时,它会释放 GIL,其他线程可以执行。
- 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