在Heroku+Gunicorn+gevent环境下Flask应用启动多线程超时如何解决
问题解决办法
核心问题根源
- 你使用gevent worker时没有提前打猴子补丁,标准库
threading创建的线程会阻塞gevent的事件循环,导致worker进程无法向Gunicorn主进程发送心跳,触发30秒默认超时被主进程强制杀死,请求只能等新worker启动后才能返回,所以耗时极长。 - Heroku路由器本身存在30秒请求硬超时限制,任何web请求超过30秒未返回都会被路由直接断开。
- 在web进程中运行永久循环的常驻任务不符合架构设计,web进程的核心职责是处理HTTP请求,常驻任务会持续占用worker资源,只要worker重启(包括代码部署、dyno自动休眠、超时被杀),你的后台线程就会直接销毁,任务完全无法稳定运行。
- 你示例代码中
setDaemon(true)写法错误,Python布尔值为大写True,语法错误也可能导致线程启动异常。
分步解决方案
第一步:修复gevent适配问题
在app.py文件最开头(所有import语句之前)添加gevent猴子补丁代码,让标准库的线程、IO操作全部适配gevent的异步模型:
from gevent import monkey monkey.patch_all() # 后续再写import flask、threading等其他代码
修改完成后你的现有代码就可以正常启动后台线程,启动后会立即返回页面,不会再阻塞请求触发超时。
第二步:架构优化(推荐,保证后台任务稳定运行)
不要在web进程中跑永久循环的后台任务,拆分独立的worker进程处理:
- 新增Heroku worker dyno,修改
Procfile添加worker启动配置:
web: gunicorn -k gevent --worker-connections 100 --timeout 60 app:app worker: python worker.py
- 引入消息队列(可使用Heroku免费Redis Addon,搭配rq轻量队列库),表单提交时往队列发送任务参数,web进程直接返回页面:
# app.py 代码调整 import redis from rq import Queue import os r = redis.from_url(os.getenv("REDIS_URL")) q = Queue(connection=r) @app.route("/form", methods=["POST"]) def form(): q.enqueue(func, request.form["code"]) return render_template("index.html")
- 把永久循环的
func逻辑放到worker.py中,由独立的worker进程运行,不会影响web请求的处理。
临时测试方案
如果只是临时测试不想拆分架构,完成第一步打猴子补丁的操作后,在Procfile的gunicorn启动命令中添加--timeout 60参数调大worker超时时间即可,线程启动后请求会立即返回,不会触发30秒超时。
内容的提问来源于stack exchange,提问作者GLaw1300
相关产品推荐
相关产品推荐

