使用 python 的多处理池进行键盘中断
如何使用 python 的多处理池处理 KeyboardInterrupt 事件?这是一个简单的示例:
from multiprocessing import Pool
from time import sleep
from sys import exit
def slowly_square(i):
sleep(1)
return i*i
def go():
pool = Pool(8)
try:
results = pool.map(slowly_square, range(40))
except KeyboardInterrupt:
# **** THIS PART NEVER EXECUTES. ****
pool.terminate()
print "You cancelled the program!"
sys.exit(1)
print "\nFinally, here are the results: ", results
if __name__ == "__main__":
go()
运行上面的代码时,当我按 ^C
时,KeyboardInterrupt
会引发,但进程只是在此时挂起,我必须终止它外部。
我希望能够随时按 ^C
并使所有进程正常退出。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
这是一个 Python 错误。当在 threading.Condition.wait() 中等待条件时,永远不会发送 KeyboardInterrupt。重现:
在 wait() 返回之前不会传递 KeyboardInterrupt 异常,并且它永远不会返回,因此中断永远不会发生。 KeyboardInterrupt 几乎肯定应该中断条件等待。
请注意,如果指定了超时,则不会发生这种情况; cond.wait(1) 将立即接收中断。因此,解决方法是指定超时。为此,请替换
为
或类似的。
This is a Python bug. When waiting for a condition in threading.Condition.wait(), KeyboardInterrupt is never sent. Repro:
The KeyboardInterrupt exception won't be delivered until wait() returns, and it never returns, so the interrupt never happens. KeyboardInterrupt should almost certainly interrupt a condition wait.
Note that this doesn't happen if a timeout is specified; cond.wait(1) will receive the interrupt immediately. So, a workaround is to specify a timeout. To do that, replace
with
or similar.
根据我最近的发现,最好的解决方案是将工作进程设置为完全忽略 SIGINT,并将所有清理代码限制在父进程中。这解决了空闲和繁忙工作进程的问题,并且不需要在子进程中添加错误处理代码。
说明和完整的示例代码可以在 http://noswap.com/blog/python-multiprocessing 找到-keyboardinterrupt/ 和 http://github.com/jreese/multiprocessing-keyboardinterrupt分别。
From what I have recently found, the best solution is to set up the worker processes to ignore SIGINT altogether, and confine all the cleanup code to the parent process. This fixes the problem for both idle and busy worker processes, and requires no error handling code in your child processes.
Explanation and full example code can be found at http://noswap.com/blog/python-multiprocessing-keyboardinterrupt/ and http://github.com/jreese/multiprocessing-keyboardinterrupt respectively.
由于某些原因,只有从
Exception
基类继承的异常才能正常处理。作为解决方法,您可以将KeyboardInterrupt
重新引发为Exception
实例:通常您会得到以下输出:
因此,如果您点击
^C
>,您将得到:For some reasons, only exceptions inherited from the base
Exception
class are handled normally. As a workaround, you may re-raise yourKeyboardInterrupt
as anException
instance:Normally you would get the following output:
So if you hit
^C
, you will get:投票的答案并没有解决核心问题,而是解决了类似的副作用。
Jesse Noller,multiprocessing 库的作者,解释了在旧的 multiprocessing.Pool 时如何正确处理 CTRL+C /01/08/multiprocessingpool-and-keyboardinterrupt" rel="noreferrer">博客文章。
The voted answer does not tackle the core issue but a similar side effect.
Jesse Noller, the author of the multiprocessing library, explains how to correctly deal with CTRL+C when using
multiprocessing.Pool
in a old blog post.其中许多答案都是旧的,并且/或者如果您正在执行诸如
Pool.map
更高版本的 Python(我正在运行 3.8.5) >,它会阻塞,直到所有提交的任务完成。以下是我的解决方案。signal.signal(signal.SIGINT, signal.SIG_IGN)
以完全忽略 Ctrl-C。KyboardInterrupt
异常。handle_ctrl_c
可用于装饰多处理函数和方法,这些函数和方法应在输入 Ctrl-C 时立即退出。该装饰器将测试是否设置了全局 ctrl_c_entered 标志,如果是,则甚至不运行该函数/方法,而是返回一个 KeyboardInterrupt 异常实例。否则,将建立 KeyboardInterrupt 的 try/catch 处理程序,并调用修饰的函数/方法。如果输入 Ctrl-C,则全局ctrl_c_entered
将设置为True
并返回KeyboardInterrupt
异常实例。无论如何,在返回之前装饰器都会重新建立 SIG_IGN 处理程序。本质上,所有提交的任务都将被允许启动,但一旦输入 Ctrl-C,将立即终止并返回
KeyBoardInterrupt
异常。主进程可以测试返回值是否存在这样的返回值,以检测是否输入了Ctrl-C。印刷:
Many of these answers are old and/or they do not seem to work with later versions of Python (I am running 3.8.5) on Windows if you are executing a method such as
Pool.map
, which blocks until all the submitted tasks have completed. The following is my solution.signal.signal(signal.SIGINT, signal.SIG_IGN)
in the main process to ignore Ctrl-C altogether.ctrl_c_entered
will be set toFalse
and a a call tosignal.signal(signal.SIGINT, signal.SIG_IGN)
will be issued to initially ignore Ctrl-C. The return value from this call will be saved; this is the original, default handler that when re-established allows handing ofKyboardInterrupt
exceptions.handle_ctrl_c
, can be used to decorate multiprocessing functions and methods that should exit immediately on Ctrl-C being entered. This decorator will test to see if the globalctrl_c_entered
flag is set and if so ,not even bother to run the function/method and instead will return aKeyboardInterrupt
exception instance. Otherwise a try/catch handler for aKeyboardInterrupt
will be established and the decorated function/method will be invoked. If Ctrl-C is entered, globalctrl_c_entered
will be set toTrue
and aKeyboardInterrupt
exception instance will be returned. In any event, before returning the decorator will re-establish the SIG_IGN handler.In essence all submitted tasks will be allowed to start but will immediately terminate with a return value of a
KeyBoardInterrupt
exception once a Ctrl-C has been entered. The main process can test the return values for the presence of such a return value to detect whether a Ctrl-C was entered.Prints:
通常,这个简单的结构适用于 Pool 上的 Ctrl-C :
正如一些类似帖子中所述:
在 Python 中捕获键盘中断而不使用 try- except
Usually this simple structure works for Ctrl-C on Pool :
As was stated in few similar posts:
Capture keyboardinterrupt in Python without try-except
似乎有两个问题在多处理时造成异常,令人烦恼。第一个(Glenn 指出)是您需要使用带有超时的
map_async
而不是map
才能获得立即响应(即,不要完成处理整个列表)。第二个(Andrey 指出)是多处理不会捕获不是从Exception
继承的异常(例如,SystemExit
)。所以这是我处理这两个问题的解决方案:It seems there are two issues that make exceptions while multiprocessing annoying. The first (noted by Glenn) is that you need to use
map_async
with a timeout instead ofmap
in order to get an immediate response (i.e., don't finish processing the entire list). The second (noted by Andrey) is that multiprocessing doesn't catch exceptions that don't inherit fromException
(e.g.,SystemExit
). So here's my solution that deals with both of these:我是Python新手。我到处寻找答案,偶然发现了这个以及其他一些博客和 YouTube 视频。我尝试复制粘贴上面作者的代码,并在 Windows 7 64 位中的 python 2.7.13 上重现它。这很接近我想要实现的目标。
我让我的子进程忽略 ControlC 并使父进程终止。看起来绕过子进程确实可以避免这个问题。
从 pool.terminate() 开始的部分似乎永远不会执行。
I'm a newbie in Python. I was looking everywhere for answer and stumble upon this and a few other blogs and youtube videos. I have tried to copy paste the author's code above and reproduce it on my python 2.7.13 in windows 7 64- bit. It's close to what I wanna achieve.
I made my child processes to ignore the ControlC and make the parent process terminate. Looks like bypassing the child process does avoid this problem for me.
The part starting at
pool.terminate()
never seems to execute.我发现,目前最好的解决方案是不使用 multiprocessing.pool 功能,而是推出自己的池功能。我提供了一个示例来演示 apply_async 的错误,以及一个示例来展示如何完全避免使用池功能。
http://www.bryceboe.com/2010/08 /26/python-multiprocessing-and-keyboardinterrupt/
I found, for the time being, the best solution is to not use the multiprocessing.pool feature but rather roll your own pool functionality. I provided an example demonstrating the error with apply_async as well as an example showing how to avoid using the pool functionality altogether.
http://www.bryceboe.com/2010/08/26/python-multiprocessing-and-keyboardinterrupt/
您可以尝试使用 Pool 对象的 apply_async 方法,如下所示:
输出:
此方法的优点是中断前处理的结果将在结果字典中返回:
You can try using the apply_async method of a Pool object, like this:
Output:
An advantage of this method is that results processed before interruption will be returned in the results dictionary:
奇怪的是,看起来您还必须处理子级中的
KeyboardInterrupt
。我本希望这能按书面方式工作...尝试将slowly_square
更改为:这应该按您的预期工作。
Strangely enough it looks like you have to handle the
KeyboardInterrupt
in the children as well. I would have expected this to work as written... try changingslowly_square
to:That should work as you expected.