根本原因是gevent/eventlet补丁劫持I/O模块后,某些模块导入时触发的网络、证书或线程初始化操作依赖尚未启动的协程调度器,导致循环等待;必须将patch_all()置于所有import之前,且避免隐式导入引发的竞态。

为什么打猴子补丁后 import 会卡住?
根本原因是 gevent.monkey.patch_all() 或 eventlet.monkey_patch() 会劫持 Python 的底层 I/O 模块(比如 socket、ssl、threading),而某些模块(如 requests、urllib3、pkg_resources、甚至部分 logging 配置)在导入时会触发网络探测、证书加载或线程初始化——这些操作一旦被补丁接管,又恰好依赖尚未完成初始化的协程调度器(如 gevent.hub),就会陷入“等 hub 启动 → hub 等模块导入完成 → 模块卡在被补丁改造的阻塞调用里”的循环。
补丁必须放在所有 import 之前,但有些 import 无法避免
常见陷阱是:在 patch_all() 前导入了看似“安全”的模块,结果它内部间接触发了问题模块。例如:
-
import logging→ 某些 handler 初始化时读取系统时区(触发time.tzset或文件 I/O) -
import requests→ 触发urllib3.util.ssl_加载根证书(可能访问文件系统或调用ssl.create_default_context()) -
from pkg_resources import iter_entry_points→ 内部使用zipfile和os.stat,补丁后可能阻塞
解决办法不是“尽量少导”,而是控制补丁时机和范围:
- 把
gevent.monkey.patch_all()放在脚本最顶部,且前面只允许有注释、空行、__future__导入 - 避免在 patch 前做任何非 trivial 的 import;若必须,用
importlib.util.find_spec()替代直接import来探测模块是否存在 - 对已知高危模块(如
requests、psycopg2),启用补丁时显式关闭其相关子模块:gevent.monkey.patch_all(socket=True, ssl=True, threading=True, time=True, select=True, subprocess=True, os=True, errno=True, sys=True),但跳过dns=True(如果不用 gevent-dns)
eventlet 在 Flask/Django 中的特殊死锁场景
Django 的 django.setup() 或 Flask 的 app = Flask(__name__) 可能触发模板引擎初始化、日志配置、数据库连接池预热——这些过程若发生在补丁之后、但 hub 尚未启动(比如还没进入 eventlet.spawn 或 gevent.spawn),就容易卡住。
典型表现是进程 CPU 为 0、无日志输出、strace 显示卡在 futex 或 epoll_wait。
应对策略:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- Flask:确保
eventlet.monkey_patch()在if __name__ == '__main__':块内,且紧邻app.run()前;不要在模块顶层 patch - Django:在
manage.py的if __name__ == '__main__':分支中 patch,再调用execute_from_command_line();避免在settings.py或__init__.py中 patch - 使用
eventlet.listen()或gevent.pywsgi.WSGIServer替代默认服务器,绕过框架自带的同步启动逻辑
调试死锁:快速定位哪个 import 卡住了
加一句 import faulthandler; faulthandler.enable() 在 patch 之前,然后 Ctrl+\ 发送 SIGQUIT,看 traceback 最深的帧是否停在 ssl、certifi、threading.py 或 importlib._bootstrap 里。
更直接的办法是临时替换 import 行为:
import builtins
_original_import = builtins.__import__
def _debug_import(name, *args, **kwargs):
print(f"[IMPORT] {name}")
return _original_import(name, *args, **kwargs)
builtins.__import__ = _debug_import
配合 gevent.monkey.patch_all() 放在最顶上,运行时就能看到卡在哪一行 import。注意:该调试代码仅用于定位,不能长期保留,否则影响性能且与补丁冲突。
真正难处理的是那些隐式导入——比如某个 C 扩展模块在 __init__.py 里调用了 ssl.get_default_verify_paths(),这种只能靠二分注释 + strace 配合排查。补丁本身不坏,坏的是补丁和模块初始化顺序之间的竞态。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










