终止Python多进程池

23 投票
4 回答
11616 浏览
提问于 2025-04-18 17:59

我正在运行一个Python程序,这个程序使用了多进程模块来启动一些工作线程。通过使用Pool.map,这些线程可以处理一个文件列表。

在某个时刻,我想要停止所有的操作,让这个脚本结束。

通常情况下,按Ctrl+C可以在命令行中实现这个目的。但是在这种情况下,我觉得这只是中断了其中一个工作线程,然后又会启动一个新的线程。

所以,我最后不得不运行ps aux | grep -i python来查看正在运行的Python进程,然后用kill -9命令来强制结束那些进程。

有没有更好的方法可以让中断信号让所有的操作都停止下来呢?

4 个回答

0

我发现使用Python的信号库在这种情况下效果很好。当你初始化线程池的时候,可以给每个线程传递一个信号处理器,这样当主线程收到键盘中断时,就能设置一个默认的行为。

如果你真的只是想让所有的东西都停止运行,可以在主线程中捕获键盘中断的异常,然后调用pool.terminate()来结束线程池。

3

有几种方法可以做到这一点。第一种方法是将线程标记为守护线程,使用以下代码:

在线程处理中,

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()
4

很遗憾,在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的早期版本中也修复了)。

26

SIGQUIT(按下 Ctrl + \)会终止所有进程,即使是在 Python 2.x 版本下也一样。

你也可以升级到 Python 3.x,在这个版本中,这种情况(只有子进程会收到信号)似乎已经修复了。

撰写回答