Flask自带开发服务器是单进程单线程的,无法利用多核CPU;需用Gunicorn等WSGI服务器通过多进程分发请求,推荐worker数为2×CPU核心数+1,并注意进程隔离与初始化问题。

为什么直接用 flask run 无法利用多核CPU
Flask 自带的开发服务器是单进程、单线程的,flask run 启动后只跑在一个 CPU 核心上,哪怕你机器有 16 核,它也只吃满 1 个。这不是配置问题,是设计如此——它压根不支持多进程/多线程模式,也不处理信号、超时、平滑重启等生产需求。
真正能分发请求到多个工作进程(worker)并绑定多核的是 WSGI 服务器,比如 Gunicorn。它通过 fork 多个子进程(每个进程可再开线程),让每个进程独立处理请求,从而摊薄 CPU 负载。
- Flask 应用本身仍是单线程对象,Gunicorn 负责“复制”它到多个进程空间
- 进程间不共享内存,所以全局变量、缓存(如
dict)不会跨 worker 生效 -
app.run()必须移除,否则会和 Gunicorn 的事件循环冲突,导致启动卡死或报错Address already in use
如何用 Gunicorn 启动 Flask 并设置合理 worker 数量
Gunicorn 默认使用同步 worker(sync),每个 worker 一次只处理一个请求;对 I/O 密集型(如数据库查询、HTTP 调用)场景,可以改用 gevent 或 eventlet,但需额外安装且调试成本上升。多数 Web API 场景,调好 --workers 就够了。
推荐 worker 数量公式:2 × CPU核心数 + 1(官方建议下限),但实际要结合应用特性压测调整:
立即学习“Python免费学习笔记(深入)”;
- CPU 密集型(如图像处理):worker 数 ≈ CPU 核心数,避免过度上下文切换
- I/O 密集型(如调外部 API、查 Redis):可设为
4–8 × CPU 核心数,靠更多进程掩盖阻塞等待 - 内存受限时:每个 worker 占用约 50–100MB 内存,
--worker-tmp-dir /dev/shm可减少磁盘临时文件开销
启动命令示例(假设入口文件是 app.py,其中定义了 app):
gunicorn --workers 4 --bind 0.0.0.0:8000 --timeout 30 app:app
注意:app:app 表示模块名:应用实例名,不是文件路径;如果写成 app.py:app 会报 ImportError。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
常见错误:启动后请求 502/503 或 worker 频繁重启
这类问题八成出在 worker 生命周期管理或资源竞争上,尤其当 Flask 应用里做了不兼容的操作:
-
app.before_first_request在每个 worker 进程里各执行一次,若里面初始化了全局连接(如sqlite3.connect()),会导致多进程争抢同一文件,出现database is locked - 使用
threading.local()或contextvars.ContextVar时未考虑多进程隔离,状态不会跨 worker 传递 -
--timeout设太小(如 5 秒),而某个视图耗时稍长(如慢 SQL),Gunicorn 会直接 kill 掉该 worker,返回 502 - 没配
--preload,导致每个 worker 启动时都重新导入模块+执行顶层代码,可能触发重复日志初始化、重复注册信号等副作用
修复建议:
- 把数据库连接池(如
SQLAlchemy)放在 worker 启动后首次请求时 lazy 初始化,或用gunicorn.conf.py的post_fork钩子做 per-worker 初始化 - 加
--preload参数,让 Gunicorn 先加载一次应用再 fork,避免重复导入开销 - 用
--access-logfile - --error-logfile -把日志打到 stdout,方便配合docker logs或systemd捕获真实错误
进阶:用配置文件代替长命令行参数
命令行参数一多就难维护,也容易漏掉关键项(比如忘记 --timeout)。Gunicorn 支持 Python 风格的配置文件,更清晰可控。
新建 gunicorn.conf.py:
import multiprocessing <p>bind = "0.0.0.0:8000" bind_address = "0.0.0.0:8000" workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "sync" timeout = 30 keepalive = 5 max_requests = 1000 max_requests_jitter = 100 preload = True accesslog = "-" errorlog = "-" loglevel = "info" capture_output = True pidfile = "/tmp/gunicorn.pid"</p>
然后启动:gunicorn -c gunicorn.conf.py app:app。这样改参数不用改启动脚本,CI/CD 里也容易注入环境变量(比如用 os.getenv("WORKERS", "4") 动态控制)。
真正麻烦的从来不是起多少进程,而是每个进程里的状态是否干净、资源是否独占、日志是否可追溯。worker 崩溃时,别急着加数量,先看 errorlog 里最后一行是不是 Worker failed to boot 或 Connection reset by peer——那大概率是你的 Flask 初始化逻辑没扛住 fork。

















