【问题标题】:Python doctest hangs using ProcessPoolExecutorPython doctest 使用 ProcessPoolExecutor 挂起
【发布时间】:2018-06-21 11:33:03
【问题描述】:

此代码在常规 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下运行会挂起?

【问题讨论】:

  • 对我发布的答案有任何更新/反馈吗?

标签: python python-multiprocessing doctest concurrent.futures process-pool


【解决方案1】:

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

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

步骤形式:

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

=> 死锁。

【讨论】:

    【解决方案2】:

    所以我认为问题在于您的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 模块(如果需要)

    【讨论】:

    • 这个答案有点误导,因为问题不在于with 语句。您可以通过执行 pool = ...ProcessPoolExecutor() pool.submit(...) pool.shutdown() 在不使用 with 语句的情况下重现此行为。正如我在回答中指出的那样,问题在于导入锁。
    • @daphtdazz,我同意你的看法。我不知道https://docs.python.org/3/library/imp.html#imp.lock_held 在我的回答中引用了这一点,我只知道这是一个导入死锁。当我说with 语句是问题时,我的意思是ProcessPoolExecutor__exit__ 将执行shutdown 方法并导致导入死锁。您的回答解释了我的下一层。两者在各自的上下文中都是正确的。你解释了为什么它不起作用,我解释了如何使它起作用。
    【解决方案3】:

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

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

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

    【讨论】:

    • 我明白你在说什么。不过,问题是我希望能够在 doctest 中启动 ProcessPoolExecutor。那是我无法上班的地方。简单地将所有代码隐藏在if name == "main" 下是行不通的,因为这会阻止代码运行(在 doctest 下)。
    • 为什么不把 ProcessPoolExecutor 的代码放在 doctest 字符串中,让它作为测试运行呢?还是有其他用例?
    【解决方案4】:

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

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

    【讨论】:

    • 通过阻止代码一起运行来回避问题。但是我不想阻止代码运行,我想阻止它挂起。
    • 代码应该在模块加载时运行(例如通过 doctest,或常规导入),或作为独立脚本运行。
    猜你喜欢
    • 2022-12-01
    • 1970-01-01
    • 2020-08-09
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多