直接替换requests会出错,根本原因是requests默认使用阻塞式socket,而gevent协程依赖猴子补丁(monkey.patch_all())接管i/o;若未在最顶部执行patch,requests仍调用原生阻塞socket,导致协程无法让出控制权、应用退化为单线程。

为什么直接替换 requests 会出错?
旧版同步 Web 应用(比如用 flask + requests 写的 API)在接入 gevent 后,常出现请求卡死、超时或连接复用异常——根本原因不是代码写错了,而是 requests 默认走的是阻塞式 socket,而 gevent 的协程调度依赖对底层 I/O 的猴子补丁(monkey patching)。
必须在所有其他 import 之前执行:
from gevent import monkey monkey.patch_all()
否则 requests 仍调用原生阻塞 socket,协程无法让出控制权,整个应用就退化成单线程。常见坑点:
-
monkey.patch_all()必须放在最顶部,连logging或os都不能提前 import - 若用了
gevent.pool.Pool但没 patch,pool.spawn()看似并发,实际仍是串行等待 - Django 用户注意:
monkey.patch_all(thread=False)可避免与 Django 内部线程管理冲突
如何安全替换 urllib 和 requests 调用?
即使打了补丁,requests 在高并发下仍可能因连接池复用导致 fd 耗尽或 DNS 缓存不一致。更稳妥的做法是切换到原生支持协程的 HTTP 客户端。
推荐两个轻量方案:
- 用
geventhttpclient替代requests:它专为gevent设计,连接池自动协程友好,且兼容大部分requests接口习惯 - 若必须保留
requests,至少禁用其连接池:requests.Session().close()每次请求后显式关闭,或设置pool_connections=0避免复用
示例对比:
# ❌ 危险:未控制连接生命周期
resp = requests.get("https://www.php.cn/link/46b315dd44d174daf5617e22b3ac94ca")
<h1>✅ 推荐:显式 session 控制 + timeout</h1><p>session = requests.Session()
session.headers.update({"User-Agent": "gevent-app"})
resp = session.get("<a href="https://www.php.cn/link/46b315dd44d174daf5617e22b3ac94ca">https://www.php.cn/link/46b315dd44d174daf5617e22b3ac94ca</a>", timeout=5)
session.close() # 防止 fd 泄露</p>
gevent.pywsgi.WSGIServer 替换 Flask.run() 的关键配置
旧版 flask.run(host="0.0.0.0", port=5000) 是单线程开发服务器,无法发挥 gevent 并发能力。必须换成 gevent.pywsgi.WSGIServer,但默认配置容易踩坑。
- 务必设置
spawn参数:例如spawn=gevent.pool.Pool(100),否则默认无限制 spawn,可能耗尽内存或触发系统 open files 限制 - 不要忽略
log和error_log:默认输出到 stdout,线上环境需重定向,否则日志丢失且影响性能 -
backlog建议设为1024或更高,避免在突发流量下丢弃连接请求
启动示例:
from gevent.pywsgi import WSGIServer
from gevent.pool import Pool
<p>app = create_flask_app() # 你的 Flask 实例
http_server = WSGIServer(
("0.0.0.0", 5000),
app,
spawn=Pool(50), # 限制最大并发 greenlet 数
log=None, # 关闭默认日志
error_log=None,
)
http_server.serve_forever()</p>
数据库连接怎么不拖慢整个协程池?
同步数据库驱动(如 psycopg2、pymysql)在 gevent 下仍是阻塞的,一个慢查询就能堵住整个协程池。不能只靠 monkey.patch_all() 解决。
可行路径只有两条:
- 换异步驱动:如
psycopg2cffi(兼容 psycopg2 接口)或aiomysql+gevent兼容层(需额外封装) - 把 DB 操作扔进线程池隔离:用
gevent.threadpool.ThreadPool包裹cursor.execute(),避免阻塞主线程,但要注意连接对象不能跨线程复用
简单隔离示例:
from gevent.threadpool import ThreadPool <p>db_pool = ThreadPool(10) # 限制最多 10 个 DB 线程</p><p>def query_db(sql): return db_pool.spawn(_execute_sql, sql).get(timeout=3)</p><p>def _execute_sql(sql): conn = get_sync_db_conn() # 每次新建连接,用完 close try: cursor = conn.cursor() cursor.execute(sql) return cursor.fetchall() finally: conn.close()</p>
真实改造中,DB 层往往是性能瓶颈所在,比 HTTP 客户端更难平滑过渡——连接池、事务边界、连接复用策略都得重新评估,不能只改一层 patch。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











