Gevent和Subprocess问题
1、复现
在main文件中调用gevent、并做了monkey pathch, 然后再调用subprocess.Popen(),出现一直卡住的问题
- Python 标准库里的很多 IO(网络 socket、ssl、time.sleep 等)都是 阻塞的:
- 调用
socket.recv()
会阻塞整个线程,其他 greenlet 无法运行。
- 调用
- 为了让这些阻塞调用 在等待时让出控制权,gevent 需要“接管”标准库里的阻塞函数 → 这就是 monkey patch。
from gevent import monkey
monkey.patch_all()import subprocessp = subprocess.Popen("echo 111", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)p.wait()
2、原因
monkey.patch_all()
会对 Python 的标准库做打猴子补丁,其中包括 subprocess
。而被打过补丁的 subprocess
在某些情况下(尤其是涉及到 Popen
、communicate
、wait
或交互式命令)会出现 阻塞/卡住 的情况
subprocess
被 gevent 补丁后不完全兼容
gevent 会用协程友好的 IO 替换底层调用,但subprocess
的管道和底层select/poll
行为会变得不可预期。- 所有
Popen
、communicate
、wait
都是标准阻塞版本,不会和 gevent 的事件循环配合
3、解决方法
以下是两种解决办法, 确认是否需要协程化的子进程
- 如果只是跑系统命令、不需要协程化,建议用 原生
subprocess
,用方法1 - 如果需要和 gevent 事件循环兼容,再考虑
gevent.subprocess
, 用方法2
方法1:不要 monkey patch subprocess
在调用 monkey.patch_all()
时,可以显式排除:
from gevent import monkey
monkey.patch_all(subprocess=False)
方法2:使用 gevent.subprocess
代替
如果你确实想在 gevent 下用子进程,可以用:
from gevent import subprocess
p = subprocess.Popen(["cmd", "/c", "echo hello"], stdout=subprocess.PIPE)
out, err = p.communicate()
print(out.decode())
这个是 gevent 提供的封装,适配了它的事件循环。
博客内容仅供参考,部分参考他人优秀博文,仅供学习使用