终止Python多进程池
我正在运行一个Python程序,这个程序使用了多进程模块来启动一些工作线程。通过使用Pool.map
,这些线程可以处理一个文件列表。
在某个时刻,我想要停止所有的操作,让这个脚本结束。
通常情况下,按Ctrl+C
可以在命令行中实现这个目的。但是在这种情况下,我觉得这只是中断了其中一个工作线程,然后又会启动一个新的线程。
所以,我最后不得不运行ps aux | grep -i python
来查看正在运行的Python进程,然后用kill -9
命令来强制结束那些进程。
有没有更好的方法可以让中断信号让所有的操作都停止下来呢?
4 个回答
我发现使用Python的信号库在这种情况下效果很好。当你初始化线程池的时候,可以给每个线程传递一个信号处理器,这样当主线程收到键盘中断时,就能设置一个默认的行为。
如果你真的只是想让所有的东西都停止运行,可以在主线程中捕获键盘中断的异常,然后调用pool.terminate()来结束线程池。
有几种方法可以做到这一点。第一种方法是将线程标记为守护线程,使用以下代码:
在线程处理中,
myThread.setDaemon(true)
在多进程处理中,
myThread.daemon = True
所有标记为守护线程的线程会随着主线程一起结束。这种方法并不太好,因为它不允许线程进行清理工作。
接下来一种方法是监听 KeyboardInterrupt
,也就是当你按下键盘中断时,使用 try-catch 结构,然后像这样调用 .join() 来等待线程结束。
try:
myThread = MyThread()
except KeyboardInterrupt:
myThread.join()
如果你的线程在一个循环中运行,你可以使用一个条件,比如一个布尔值,将它设置为 false,当这个条件为 false 时,就进行清理工作。
class MyThread(Threading.thread):
def __init__(self):
self.alive=True
def run(self):
while self.alive:
#do stuff
#cleanup goes here, outside the loop
try:
myThread = MyThread()
except KeyboardInterrupt:
myThread.alive = False
myThread.join()
很遗憾,在Python 2.x中,这个问题没有很好的解决办法。我知道的最佳变通方法是使用 pool.map_async(...).get(timeout=<大数字>)
,而不是 pool.map
。问题在于 pool.map
会调用 threading.Condition.wait()
,而这个方法在Python 2.x中无法被Ctrl+C中断(在Python 3中是可以的)。使用 map_async()
时,它会调用 threading.Condition.wait(timeout=<大数字>)
,这会导致一个忙等待循环,这个循环是可以被Ctrl+C中断的。
你可以自己试试:
c = threading.Condition()
try:
c.acquire()
c.wait() # You won't be able to interrupt this
except KeyboardInterrupt:
print("Caught it")
c = threading.Condition()
try:
c.acquire()
c.wait(timeout=100) # You CAN interrupt this
except KeyboardInterrupt:
print("Caught it")
所以,要让你的 map
调用可以被中断,可以这样做:
if __name__ == "__main__":
p = multiprocessing.Pool()
try:
p.map_async(func, iterable).get(timeout=10000000)
except KeyboardInterrupt:
print("Caught it")
# Optionally try to gracefully shut down the worker processes here.
p.close()
# DON'T join the pool. You'll end up hanging.
另外,正如phihag指出的,这个问题在Python 3.4中已经修复(可能在3.x的早期版本中也修复了)。
SIGQUIT(按下 Ctrl + \)会终止所有进程,即使是在 Python 2.x 版本下也一样。
你也可以升级到 Python 3.x,在这个版本中,这种情况(只有子进程会收到信号)似乎已经修复了。