Flask中实现长轮询需用threading.Event配合RLock安全共享数据并阻塞等待,避免轮询;启动时加--threads参数支持多线程,服务端设超时防连接耗尽,teardown_request清理状态。

Flask 中如何让请求挂起等待数据更新
Flask 默认使用同步 WSGI 服务器(如 Werkzeug 开发服务器),每个请求会独占一个线程,但不会自动“挂起”等待外部事件。要实现长轮询,必须手动控制响应时机——即不立即返回,而是阻塞当前请求线程,直到有新数据或超时。
关键不是“让 Flask 挂起”,而是你主动延迟 return,并在等待期间检查条件。常见错误是直接用 time.sleep() 轮询数据库或全局变量,既低效又易漏更新;更稳妥的做法是用线程安全的信号机制,比如 threading.Event 或 queue.Queue。
- 避免在请求处理函数里写
while not data_ready: time.sleep(0.1)—— 这会浪费 CPU 且无法及时响应变化 - 推荐用
event.wait(timeout=30),它会真正让出线程,被event.set()唤醒后立刻继续执行 - 注意:Werkzeug 开发服务器默认只支持单线程,需启动时加
--threads 4参数,否则多个长轮询请求会互相阻塞
如何安全地在多请求间共享和通知数据变更
长轮询依赖“一个地方改数据,多个挂起请求能感知”。不能靠读文件或轮询数据库,得用内存级通信原语。Flask 应用实例本身是线程共享的,但直接操作全局变量(如 latest_data = None)存在竞态风险。
最轻量、线程安全的方案是组合使用 threading.Event 和 threading.RLock:
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
from threading import Event, RLock
<p>data_lock = RLock()
data_store = {}
data_updated = Event()</p><p>def update_data(new_item):
with data_lock:
data_store.update(new_item)
data_updated.set() # 唤醒所有 wait() 中的请求
data_updated.clear() # 防止重复触发(因 Event 是“脉冲式”的)
-
Event只负责“通知有更新”,不传递内容;真实数据仍需通过受保护的结构(如data_store)读取 - 每次
set()后必须clear(),否则后续wait()会立即返回,导致“假唤醒” - 不要用
threading.Condition—— 它需要严格配对acquire()/release(),在 Flask 请求生命周期中容易遗漏释放,引发死锁
如何防止客户端重试风暴与服务端连接耗尽
浏览器或移动端发起长轮询时,若响应超时或网络中断,客户端大概率立刻重发新请求。若服务端未限制并发连接数,几十个活跃长轮询就能吃光线程池,导致新请求排队甚至 503。
必须从两层设防:
- 客户端侧:设置最小重试间隔(如
Math.min(1000 * (2 ** attempt), 30000)),避免指数退避失效 - 服务端侧:用
g.event_timeout = 25类似方式,在请求上下文中记录超时值,并在wait()中显式传入,确保最长阻塞不超过阈值 - 关键细节:Flask 的
after_request钩子在响应已发出后才运行,无法用于清理挂起状态;应改用teardown_request,它在请求上下文销毁前触发,适合调用event.clear()或记录日志
为什么不用 asyncio + Quart 替代 Flask
async/await 确实更适合 I/O 挂起场景,但迁移到 Quart 并非无成本:中间件兼容性差、调试工具链不熟、部署时需换用 Hypercorn 而非 gunicorn,且多数企业项目已有成熟 Flask 生态(如 Flask-SQLAlchemy)。
除非你明确需要同时支撑数千并发长轮询,否则用线程 + Event 在 Flask 中完全可行。性能瓶颈通常不出现在挂起逻辑,而在于数据源(如 DB 查询慢、Redis PUB/SUB 延迟高)。先压测验证,再决定是否升级异步栈。
真正容易被忽略的是:每个长轮询请求都占用一个完整的 Python 线程(约 8MB 栈空间),即使它只是在 wait()。如果你的服务器内存紧张,或者平均并发长轮询超过 200,就得认真考虑改用 gevent 打补丁,或切到真正的异步方案了。

















