让多处理 Python 应用程序干净地退出

81 阅读2分钟

在多处理(multiprocessing)的 Python 应用程序中,为了干净地退出并释放资源,通常需要采取以下几种策略。

1、问题背景

当使用多处理的Python脚本时,若是收到 Ctrl-C 信号,通常难以干净地停止该脚本。需要多次按下 Ctrl-C 才能停止,并且屏幕上会出现各种错误消息。

我们如何编写一个在收到 Ctrl-C 信号后能够干净地退出的 Python 脚本?

下面是一个使用多处理的脚本示例:

import numpy as np, time
from multiprocessing import Pool
​
def countconvolve(N):
    np.random.seed() # ensure seed is random
​
    count = 0
    iters = 1000000 # 1million
​
    l=12
    k=12
    l0=l+k-1
​
    for n in range(N):
        t = np.random.choice(np.array([-1,1], dtype=np.int8), size=l0 * iters)
        v = np.random.choice(np.array([-1,1], dtype=np.int8), size = l * iters)
        for i in xrange(iters):
            if (not np.convolve(v[(l*i):(l*(i+1))],
t[(l0*i):(l0*(i+1))], 'valid').any()):
                count += 1
    return count
​
if __name__ == '__main__':
    start = time.clock()
​
    num_processes = 8
    N = 13
​
    pool = Pool(processes=num_processes)
    res = pool.map(countconvolve, [N] * num_processes)
    print res, sum(res)
​
    print (time.clock() - start)

2、解决方案

封装 Pool 对象

一种解决方案是将 Pool 对象封装在一个类中,该类可以处理 Ctrl-C 信号并干净地关闭池。

import numpy as np, time
from multiprocessing import Pool
​
class WorkerPool(Pool):
    def __init__(self, processes=8):
        super().__init__(processes=processes)
        self._closed = False
​
    def close(self):
        if not self._closed:
            super().close()
            self._closed = True
​
    def terminate(self):
        if not self._closed:
            super().terminate()
            self._closed = True
​
    def join(self):
        if not self._closed:
            super().join()
            self._closed = Trueif __name__ == '__main__':
    start = time.clock()
​
    num_processes = 8
    N = 13
​
    pool = WorkerPool(processes=num_processes)
    try:
        res = pool.map(countconvolve, [N] * num_processes)
        print res, sum(res)
        print (time.clock() - start)
    except KeyboardInterrupt as e:
        print 'Stopping..'
        pool.close()
        pool.join()
​

使用 WorkerPool 类,就可以在收到 Ctrl-C 信号时,使用 close()join() 方法来干净地关闭池。

使用信号处理程序

另一种解决方案是使用信号处理程序来处理 Ctrl-C 信号。可以将信号处理程序注册到 SIGINT 信号,当收到该信号时,就会调用信号处理程序。

import numpy as np, time
from multiprocessing import Pool
import signal
​
# 定义一个全局的池变量
pool = None
​
def term_signal_handler(signum, frame):
    global pool
​
    print 'CTRL-C pressed'
    try:
        pool.close()
        pool.join()
    except AttributeError:
        print 'Pool has been already closed'
​
def countconvolve(N):
    np.random.seed() # ensure seed is random
​
    count = 0
    iters = 1000000 # 1million
​
    l=12
    k=12
    l0=l+k-1
​
    for n in range(N):
        t = np.random.choice(np.array([-1,1], dtype=np.int8), size=l0 * iters)
        v = np.random.choice(np.array([-1,1], dtype=np.int8), size = l * iters)
        for i in xrange(iters):
            if (not np.convolve(v[(l*i):(l*(i+1))],t[(l0*i):(l0*(i+1))], 'valid').any()):
                count += 1
    return count
​
​
if __name__ == '__main__':
    # 注册信号处理程序
    signal.signal(signal.SIGINT, term_signal_handler)
​
    start = time.clock()
​
    num_processes = 8
    N = 13
​
    pool = Pool(processes=num_processes)
    try:
        res = pool.map(countconvolve, [N] * num_processes)
        print res, sum(res)
        print (time.clock() - start)
    except KeyboardInterrupt as e:
        print 'Stopping..'
​

在该脚本中,使用 signal.signal() 函数将 term_signal_handler() 函数注册到 SIGINT 信号。当收到 SIGINT 信号时,就会调用 term_signal_handler() 函数,该函数会关闭并加入池,从而干净地退出脚本。

根据具体需求选择适合的退出方式,可以让多处理程序更加稳定和优雅。