ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flask + Gevent 实战指南:greenlet 并发、asyncio 协同与 libuv 事件循环

Flask + Gevent 实战指南:greenlet 并发、asyncio 协同与 libuv 事件循环 Flask Gevent 实战指南greenlet 并发、asyncio 协同与 libuv 事件循环【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flaskGevent 通过修补patchPython 标准库让 Flask 应用在不写任何async def/await的情况下获得处理大量长连接并发的能力。本文以 docs/gevent.rst 为骨架结合本仓库 Flask 源码 中的async_to_sync、stream_with_context、copy_current_request_context实现与 Gunicorn / uWSGI 部署文档完整讲解 gevent 的启用方式、视图内并发任务写法、与 asyncio 混合使用的边界以及 libuv 事件循环的切换方法。读完本文你将掌握一套可直接落地的 Flask gevent 高并发方案及其底层原理。Gevent 是什么绿色线程与标准库补丁Gevent 将 Python 标准库修补为运行在名为greenlet绿色线程的特殊异步协程之上。它诞生于 Python 原生 asyncio 出现之前而 Flask 从诞生之初就与它兼容见 docs/gevent.rst 开篇说明。它的核心价值在于以极低的改造代价实现与 ASGI / asyncio 相近的并发能力。你不需要在代码里写async def或await一切依赖 gevent 与 greenlet 对 Python 解释器的底层操纵monkey patch来达成。这使得 gevent 成为处理大量、长生命周期的并发连接例如长轮询、WebSocket 代理、流式响应场景的可靠手段。不过选择 gevent、Quart 还是其他方案最终取决于你对项目具体需求的理解——gevent 不是async/await也不是 ASGI 服务器规范它是一条独立的技术路线。启用 Gevent尽早执行 monkey patch启用 gevent 的第一步是在你的代码尽可能早的位置应用 gevent 的补丁。这能让 gevent 底层的事件循环接管许多 Python 内部实现。在项目模块的顶部或顶层__init__.py中加入import gevent.monkey gevent.monkey.patch_all()patch_all()会替换 socket、ssl、threading 等标准库模块使阻塞调用自动让出控制权给事件循环。尽可能早是关键约束——如果补丁在模块导入之后才执行某些已被导入的模块将无法被正确替换并发能力会大打折扣。生产部署Gunicorn 的 gevent worker当部署到生产环境时官方文档docs/deploying/gunicorn.rst推荐使用 Gunicorn 并指定 gevent worker。Gunicorn 是纯 Python 实现的 WSGI 服务器配置简单、无需额外编译依赖且内置基于 gevent 的异步 worker。安装方式$ cd hello-app $ python -m venv .venv $ . .venv/bin/activate $ pip install . # install your application $ pip install gunicorn运行 Flask 应用{module_import}:{app_variable}语法也支持create_app()工厂函数调用$ gunicorn -k gevent hello:create_app() Starting gunicorn 20.1.0 Listening at: http://127.0.0.1:8000 (x) Using worker: gevent Booting worker with pid: x关键参数-k gevent指定异步 worker 类型为 gevent默认是sync适合大多数常规场景-w 4指定进程数起步值可设为CPU * 2--access-logfile-默认不打印每个请求的访问日志此选项可输出到 stdout。文档同时给出版本约束使用 gevent 时要求greenlet1.0使用 PyPy 时要求PyPy7.3.7。该约束在 docs/gevent.rst 的注释与 docs/deploying/gunicorn.rst 的 Async with gevent 一节中重复出现是部署时务必检查的硬性前提。生产部署uWSGI 的 gevent 支持uWSGIdocs/deploying/uwsgi.rst同样提供基于 gevent 的异步 worker通过--gevent参数指定并发连接数$ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app *** Starting uWSGI 2.0.20 (64bit) on [x] *** *** Operational MODE: async *** mounting hello:app on / spawned uWSGI master process (pid: x) spawned uWSGI worker 1 (pid: x, cores: 100) spawned uWSGI http 1 (pid: x) *** running gevent loop engine [addr:x] ***注意输出中的 Operational MODE: async 与 running gevent loop engine——这说明 uWSGI 已切换为 gevent 事件循环引擎。若使用 app factory 模式需要先写一个wsgi.py创建应用再指向它from hello import create_app app create_app()与 Gunicorn 一节相同uWSGI 的 gevent 模式同样要求greenlet1.0PyPy 下要求PyPy7.3.7。在视图内并发执行任务gevent.spawn()启用 gevent 后你可以在自己的代码例如视图函数中通过gevent.spawn()并发执行任务。文档给出的典型场景是发邮件——把耗时的发送动作丢到后台 greenlet立即返回响应app.post(/send) def send_email(): gevent.spawn(email.send, toexampleexample.example, textexample) return Email is being sent.后台任务如何访问 Flask 上下文gevent.spawn()出的函数运行在新的 greenlet 中默认访问不到request等 Flask 上下文全局变量。如果你确实需要在被 spawn 的函数里访问上下文官方给出两个装饰器见 docs/gevent.rststream_with_context位于 src/flask/helpers.py通过 ContextVar_cv_app捕获当前应用上下文并重新进入其实现会先执行一次next(wrapped_g)提前捕获上下文再在每次迭代时with ctx:保持上下文存活适用于响应生成器场景copy_current_request_context位于 src/flask/ctx.py在装饰应用时保存当前激活的上下文供后台任务重入。copy_current_request_context的 docstring 里就自带一个与 gevent 联用的官方示例src/flask/ctx.pyimport gevent from flask import copy_current_request_context app.route(/) def index(): copy_current_request_context def do_some_work(): # do some work here, it can access flask.request or # flask.session like you would otherwise in the view function. ... gevent.spawn(do_some_work) return Regular response不过文档与源码都给出了同样的建议与其依赖上下文装饰器不如在spawn时把真正需要的数据作为参数显式传递。这样更简单、也更安全避免后台任务在请求结束后仍持有上下文带来的隐性依赖。源码注释还特别指出如果任务会访问session父代码里也应先访问一次以保证Vary: cookie头被正确设置尽量避免在后台任务中修改session因为它可能在响应 Cookie 写回之后才执行。与 async/await 协同覆盖 async_to_syncGevent 的 monkey patch 与 Flask 内建的 asyncio 支持不能很好地共存。如果你坚持要在同一应用里同时使用 gevent 和 asyncio官方给出的方案是覆盖Flask.async_to_sync方法让 async 函数运行在 gevent 内部的 asyncio 事件循环上。Flask 的 async 调度机制先理解底层机制。Flask 2.0 起通过ensure_sync/async_to_sync支持 async 视图src/flask/app.pyensure_sync检测函数是否为协程函数iscoroutinefunction是则交给async_to_sync包装否则原样返回async_to_sync默认实现依赖asgiref.sync.async_to_sync需安装 Flask 的asyncextra否则抛出RuntimeError。默认情况下async 视图是在asgiref的事件循环策略中运行的。要把它换成 gevent 管理的 asyncio 事件循环只需重写async_to_syncimport gevent.monkey gevent.monkey.patch_all() import asyncio from flask import Flask, request loop asyncio.EventLoop() gevent.spawn(loop.run_forever) class GeventFlask(Flask): def async_to_sync(self, func): def run(*args, **kwargs): coro func(*args, **kwargs) future asyncio.run_coroutine_threadsafe(coro, loop) return future.result() return run app GeventFlask(__name__) app.get(/) async def greet(): await asyncio.sleep(1) return fHello, {request.args.get(name, World)}!这段代码的工作方式docs/gevent.rst Combining with async/await 一节在 gevent worker 中启动一个 asyncio 事件循环gevent.spawn(loop.run_forever)由 gevent 负责调度重写async_to_sync把协程通过asyncio.run_coroutine_threadsafe投递到该事件循环并同步等待结果于是async def视图中的await asyncio.sleep(1)可以在 gevent 进程内正确执行。async_to_sync的 docstring 明确写着Override this method to change how the app converts async code to be synchronously callablesrc/flask/app.py这正是官方预留的扩展点本示例即是对它的直接利用。文档同时提醒该方案仍可能有局限当使用其他 asyncio 实现时可能需要进一步修改。另外需注意原文档示例中的loop asyncio.EventLoop()写法实际运行时通常应使用asyncio.new_event_loop()创建事件循环再调用loop.run_forever()。一个容易踩的坑由于 gevent 的 patch 与 Flask 内建 asyncio 支持的兼容性问题如果你没有重写async_to_sync就同时使用两者异步任务可能被调度到错误的事件循环或直接阻塞 greenlet。建议要么纯 gevent同步视图 gevent.spawn要么按上述方式显式接管async_to_sync不要依赖默认行为。切换 libuv 事件循环gevent 默认使用基于 select/poll 的 libev 事件循环但它也支持 libuv一个跨平台的高性能事件库。官方在 docs/gevent.rst 的 libuv 一节给出启用方法必须在gevent.monkey.patch_all()之前、代码的最顶端设置配置import gevent gevent.config.loop libuv import gevent.monkey gevent.monkey.patch_all()需要注意的几点存在一个叫uvloop的项目它把 libuv 引入 asyncio但这里要使用 libuv 的话应当用 gevent 自身的 libuv 支持而不是 uvloop——uvloop 面向 asyncio与 gevent 是两套体系理论上可以把上一节的async_to_sync重写代码进一步改造以配合 uvloop 工作但目前并不确定可行文档对此持保留态度顺序敏感gevent.config.loop必须在patch_all()之前设置否则事件循环已经初始化切换不会生效。小结何时选择 Gevent综合 docs/gevent.rst 与相关部署文档给出如下决策要点需要大量、长生命周期的并发连接长轮询、流式响应、后台任务密集且不愿引入async def/await的代码改造时gevent 是最低侵入的并发方案生产环境通过gunicorn -k gevent或uwsgi --gevent N启动并确认满足greenlet1.0PyPy 为7.3.7的版本前提视图内的并发任务用gevent.spawn()优先传参而非依赖上下文装饰器确需上下文时用stream_with_context或copy_current_request_context需要 asyncio 时重写Flask.async_to_sync让协程跑在 gevent 管理的事件循环上需要 libuv 时在patch_all()前设置gevent.config.loop libuv若项目重度依赖原生async/await生态则应评估 Quart 或纯 ASGI 路线而不是强行在 gevent 上叠加 asyncio。进一步的参考材料完整的生产部署配置见 docs/deploying/gunicorn.rst 与 docs/deploying/uwsgi.rstFlask 原生 asyncio 支持的完整说明见 docs/async-await.rst上下文复制与流式上下文的源码实现在 src/flask/ctx.py 与 src/flask/helpers.py。【免费下载链接】flaskThe Python micro framework for building web applications.项目地址: https://gitcode.com/gh_mirrors/fl/flask创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表