Python doctest 使用 ProcessPoolExecutor 挂起

Python doctest hangs using ProcessPoolExecutor

此代码 运行 在常规 CPython 3.5 下没问题:

import concurrent.futures

def job(text):
    print(text)

with concurrent.futures.ProcessPoolExecutor(1) as pool:
    pool.submit(job, "hello")

但是,如果您 运行 将其设置为 python -m doctest myfile.py,它就会挂起。将 submit(job 更改为 submit(print 使其不会挂起,就像使用 ThreadPoolExecutor 而不是 ProcessPoolExecutor 一样。

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

doctest 导入您的模块以便对其进行处理。尝试添加此内容以防止在导入时执行:

if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor(1) as pool: 
        pool.submit(job, "hello")

这实际上应该是一条评论,但它太长了。

如果您的代码也作为模块导入,则会失败,并出现与 doctest 相同的错误。我得到 _pickle.PicklingError: Can't pickle <function job at 0x7f28cb0d2378>: import of module 'a' failed(我将文件命名为 a.py)。

您缺少 if __name__ == "__main__": 违反了多处理的编程准则: https://docs.python.org/3.6/library/multiprocessing.html#the-spawn-and-forkserver-start-methods

我猜子进程也会尝试导入模块,然后尝试启动另一个子进程(因为池无条件执行)。但我对此不是 100% 确定。 我也不确定为什么你得到的错误是 can't pickle <function>.

这里的问题似乎是您希望模块在导入时自动启动进程。我不确定这是否可行。

所以我认为问题出在您的 with 声明上。当你有以下

with concurrent.futures.ProcessPoolExecutor(1) as pool:
    pool.submit(job, "hello")

它强制执行并关闭线程,然后再关闭线程本身。当您 运行 作为主要进程时,它会工作并为线程提供时间来执行作业。但是当你 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)

这将阻止 deadlock 并让你 运行 doctest 以及 import 如果你想要模块

问题是导入一个模块需要一个锁(哪个锁取决于你的python版本),见docs for imp.lock_held

锁在多进程上共享,所以你的死锁发生是因为你的主进程在导入你的模块时加载并等待一个子进程试图导入你的模块,但无法获取锁来导入它,因为它目前正在由您的主进程导入。

步骤形式:

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

=> 死锁。