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
。
锁在多进程上共享,所以你的死锁发生是因为你的主进程在导入你的模块时加载并等待一个子进程试图导入你的模块,但无法获取锁来导入它,因为它目前正在由您的主进程导入。
步骤形式:
- 主进程获取锁导入
myfile.py
- 主进程开始导入
myfile.py
(它必须导入 myfile.py
因为那是定义 job()
函数的地方,这就是它没有导入的原因print()
). 的死锁
- 主进程启动 并阻塞 子进程。
- 子进程尝试获取锁以导入
myfile.py
=> 死锁。
此代码 运行 在常规 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
。
锁在多进程上共享,所以你的死锁发生是因为你的主进程在导入你的模块时加载并等待一个子进程试图导入你的模块,但无法获取锁来导入它,因为它目前正在由您的主进程导入。
步骤形式:
- 主进程获取锁导入
myfile.py
- 主进程开始导入
myfile.py
(它必须导入myfile.py
因为那是定义job()
函数的地方,这就是它没有导入的原因print()
). 的死锁
- 主进程启动 并阻塞 子进程。
- 子进程尝试获取锁以导入
myfile.py
=> 死锁。