gevent要求整个I/O链路必须被monkey.patch_all()覆盖,否则未patch的阻塞调用(如time.sleep、requests.get)会让worker卡死;patch必须置于所有import之前,且需验证日志出现“Monkey patched”及gevent.getcurrent()返回Greenlet实例。

gevent 不是给老应用“加个协程装饰器”就能提速的——它要求整个 I/O 调用链路都必须被 monkey.patch_all() 覆盖,否则一个未 patch 的 time.sleep() 或 requests.get() 就会让整个 worker 卡死。
为什么老代码一跑就变单线程?
旧多线程 Flask/Django 应用常依赖 time.sleep()、threading.Lock、subprocess.Popen().wait()、urllib.request.urlopen() 等原生阻塞调用。这些在 gevent 下不会自动让出控制权,greenlet 会原地挂起,表面看线程数没少,实际并发能力归零。
常见现象:压测时 CPU 长期低于 20%,QPS 却上不去,日志里大量超时或连接拒绝 —— 这不是负载不够,是协程调度被某处阻塞卡住了。
-
monkey.patch_all()必须放在所有import之前,包括from flask import Flask、import requests、import sqlalchemy - 验证是否生效:启动日志中必须出现
Monkey patched字样;运行gevent.getcurrent()应返回Greenlet实例而非Thread - 漏掉 patch 的典型模块:
ssl、socket、select、threading(尤其threading.Event.wait())
哪些阻塞调用必须立刻替换?
不是所有标准库函数都能靠 monkey.patch_all() 完全接管。以下几类必须手动改写:
立即学习“Python免费学习笔记(深入)”;
-
time.sleep(n)→ 改为gevent.sleep(n)(原生sleep仍会阻塞整个 greenlet) -
subprocess.Popen(...).wait()→ 改用gevent.subprocess.Popen(...).wait()或异步封装(原生subprocess默认未 patch) -
threading.Event.wait(timeout=...)→ 改为gevent.event.Event().wait(timeout=...) -
queue.Queue.get(block=True)→ 改为gevent.queue.Queue().get(block=True) - 自定义的 while 循环轮询(如
while not flag: time.sleep(0.1))→ 改为gevent.sleep(0.1)并配合gevent.event.Event
Gunicorn 部署时最容易配错的三个参数
只改代码不调 Gunicorn,等于白改。worker 进程和协程数量不匹配,轻则吞吐上不去,重则 OOM 或 502。
-
--worker-class gevent:必须显式指定,不能省略;默认是sync,哪怕装了gevent也不生效 -
--worker-connections 1000:每个 worker 最大并发 greenlet 数,建议设为800–1200(4 核机器),超过易触发内存溢出(每个 greenlet 栈约 8KB) -
--timeout 30:防止某个未 patch 的 I/O 操作无限阻塞,应设为最长业务耗时的 1.5 倍;太短会误杀正常请求,太长会让故障扩散
别盲目堆 --workers:按 2 * CPU核心数 + 1 设置即可,gevent 的价值在于用更少进程承载更多并发,不是靠多进程硬扛。
数据库连接池怎么配才不拖后腿?
协程并发上去了,但 SQLAlchemy/PyMySQL 连接池还是同步阻塞模式,就会变成瓶颈。关键点不在“池大小”,而在“是否适配协程”:
- SQLAlchemy:启用
pool_pre_ping=True和pool_recycle=3600,避免空闲连接失效;连接池大小建议设为worker-connections × 0.8(例如--worker-connections 1000→pool_size=800) - PyMySQL:必须用
gevent兼容版本(如pymysql1.1+),并确保monkey.patch_all()已覆盖socket和ssl - Redis:用
redis-py≥ 4.0,配合ConnectionPool(max_connections=...),max_connections 建议 ≥worker-connections
最常被忽略的一点:所有 DB 初始化代码(比如 db.init_app(app))必须在 monkey.patch_all() 之后执行,否则连接对象底层仍是同步 socket。


















