Python doctest 使用 ProcessPoolExecutor 挂起

Joh*_*nck 4 python doctest concurrent.futures python-multiprocessing process-pool

此代码在常规 CPython 3.5 下运行良好:

import concurrent.futures

def job(text):
    print(text)

with concurrent.futures.ProcessPoolExecutor(1) as pool:
    pool.submit(job, "hello")
Run Code Online (Sandbox Code Playgroud)

但是如果你把它作为 运行python -m doctest myfile.py,它就会挂起。更改submit(jobsubmit(print使其不会挂起,使用ThreadPoolExecutor代替ProcessPoolExecutor.

为什么在 doctest 下运行时会挂起?

dap*_*azz 7

问题是,导入模块获取的锁(锁这取决于你的Python版本),看到的文档imp.lock_held

锁在多处理上共享,因此您的死锁发生是因为您的主进程在导入您的模块时加载并等待子进程尝试导入您的模块,但无法获取锁以导入它,因为它当前正在导入通过您的主要流程。

阶梯形式:

  1. 主进程获取锁导入 myfile.py
  2. 主进程开始导入myfile.py (它必须导入,myfile.py因为那是您job()定义函数的地方,这就是它没有死锁的原因print()
  3. 主进程子进程上启动和阻塞
  4. 子进程尝试获取锁以进行导入 myfile.py

=> 死锁。


Tar*_*ani 6

所以我认为问题出在你的with陈述上。当你有以下

with concurrent.futures.ProcessPoolExecutor(1) as pool:
    pool.submit(job, "hello")
Run Code Online (Sandbox Code Playgroud)

它强制执行线程并关闭它本身。当您将此作为主进程运行时,它会工作并为线程提供时间来执行作业。但是当你import把它作为一个模块时,它不会给后台线程一个机会,shutdown池上等待工作被执行,因此deadlock

因此,您可以使用的解决方法如下

import concurrent.futures

def job(text):
    print(text)

pool = concurrent.futures.ProcessPoolExecutor(1)
pool.submit(job, "hello")

if __name__ == "__main__":
    pool.shutdown(True)
Run Code Online (Sandbox Code Playgroud)

如果您愿意,这将阻止deadlock并且可以让您doctestimport模块一样运行

  • 这个答案有点误导,因为问题不在于“with”语句。您可以通过执行 `pool = ...ProcessPoolExecutor()` `pool.submit(...)` `pool.shutdown()` 来重现此行为,而无需使用 `with` 语句。正如我在回答中指出的那样,问题是导入锁。 (2认同)