Windows10下Python3.9使用ProcessPoolExecutor按两次Ctrl+C无限挂起问题
问题产生原因
- 首先是Python 3.9版本在Windows平台对
ProcessPoolExecutor的实现存在已知BUG:你使用with上下文管理器托管进程池时,上下文退出会自动调用shutdown(wait=True),你在异常捕获逻辑中又手动调用了一次shutdown,两次调用叠加导致关闭逻辑出现异常。 - 第一次按下
Ctrl+C触发KeyboardInterrupt进入异常处理逻辑后,第二次按下Ctrl+C刚好命中shutdown内部的管理线程join()调用,Windows平台下该调用的锁获取逻辑没有处理中断唤醒机制,会直接陷入永久阻塞。 - Python 3.9版本的Windows平台中,
shutdown传入的cancel_futures=True参数对已经启动的工作进程无效,无法主动终止正在运行的子进程,进一步加剧了挂起问题。
可行解决方案
方案1:升级Python版本
该BUG在Python 3.10及以上版本已经被官方修复,优化后的进程池中断处理逻辑支持正常响应两次Ctrl+C退出,cancel_futures参数也支持正常终止Windows平台已启动的工作进程,升级版本是成本最低的解决方案。
方案2:适配Python 3.9的代码修改
如果必须使用Python 3.9版本,调整代码逻辑避免重复调用shutdown,同时手动处理子进程终止即可解决问题:
- 不要在
with上下文内部手动调用executor.shutdown(),上下文退出时会自动执行关闭逻辑 - 捕获
KeyboardInterrupt后直接终止所有子进程,跳过默认的等待逻辑
修改后参考代码如下:
import os import signal import concurrent.futures # 你的业务逻辑 def Worker(x): pass # 你的业务逻辑 def DoSomething(result): pass if __name__ == "__main__": iterable = [] # 替换为你的迭代参数 executor = concurrent.futures.ProcessPoolExecutor() try: results = executor.map(Worker, iterable, timeout=5) for result in results: DoSomething(result) except KeyboardInterrupt: # 手动杀死所有工作进程避免挂起 for pid in executor._processes.keys(): os.kill(pid, signal.SIGTERM) executor.shutdown(wait=False) except Exception as exc: print(exc) executor.shutdown(wait=True, cancel_futures=True)
方案3:替换进程池实现
如果上述方案都不适用,可以直接用multiprocessing.Pool替代ProcessPoolExecutor,前者在Windows平台的中断信号处理逻辑更成熟,相同场景下不会出现无限挂起问题。
内容的提问来源于stack exchange,提问作者Martin Bammer
相关产品推荐
相关产品推荐

