4

此代码在常规 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(jobsubmit(print使其不会挂起,就像使用ThreadPoolExecutor而不是ProcessPoolExecutor.

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

4

4 回答 4

7

所以我认为这个问题是因为你的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并让您doctestimport模块一样运行

于 2018-04-12T06:38:10.280 回答
7

问题是导入模块会获得一个锁(哪个锁取决于您的 python 版本),请参阅imp.lock_held.

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

以步进形式:

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

=> 死锁。

于 2018-04-17T14:28:27.627 回答
0

doctest 导入您的模块以进行处理。尝试添加它以防止在导入时执行:

if __name__ == "__main__":
    with concurrent.futures.ProcessPoolExecutor(1) as pool: 
        pool.submit(job, "hello")
于 2018-01-12T06:50:45.680 回答
0

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

如果您的代码也作为模块导入,则您的代码也会失败,并出现与 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>.

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

于 2018-04-11T17:53:12.927 回答