Python multiprocessing - 捕获信号以重新启动子进程或关闭父进程

标签 python linux signals python-multiprocessing

我正在使用多处理库生成两个子进程。我想确保只要父进程还活着,如果子进程死了(收到 SIGKILL 或 SIGTERM),它们就会自动重启。另一方面,如果父进程收到 SIGTERM/SIGINT,我希望它终止所有子进程然后退出。

我是这样解决问题的:

import sys
import time
from signal import signal, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_IGN
from functools import partial
import multiprocessing
import setproctitle

class HelloWorld(multiprocessing.Process):
    def __init__(self):
        super(HelloWorld, self).__init__()

        # ignore, let parent handle it
        signal(SIGTERM, SIG_IGN)

    def run(self):

        setproctitle.setproctitle("helloProcess")

        while True:
            print "Hello World"
            time.sleep(1)

class Counter(multiprocessing.Process):
    def __init__(self):
        super(Counter, self).__init__()

        self.counter = 1

        # ignore, let parent handle it
        signal(SIGTERM, SIG_IGN)

    def run(self):

        setproctitle.setproctitle("counterProcess")

        while True:
            print self.counter
            time.sleep(1)
            self.counter += 1


def signal_handler(helloProcess, counterProcess, signum, frame):

    print multiprocessing.active_children()
    print "helloProcess: ", helloProcess
    print "counterProcess: ", counterProcess

    if signum == 17:

        print "helloProcess: ", helloProcess.is_alive()

        if not helloProcess.is_alive():
            print "Restarting helloProcess"

            helloProcess = HelloWorld()
            helloProcess.start()

        print "counterProcess: ", counterProcess.is_alive()

        if not counterProcess.is_alive():
            print "Restarting counterProcess"

            counterProcess = Counter()
            counterProcess.start()

    else:

        if helloProcess.is_alive():
            print "Stopping helloProcess"
            helloProcess.terminate()

        if counterProcess.is_alive():
            print "Stopping counterProcess"
            counterProcess.terminate()

        sys.exit(0)



if __name__ == '__main__':

    helloProcess = HelloWorld()
    helloProcess.start()

    counterProcess = Counter()
    counterProcess.start()

    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
        signal(signame, partial(signal_handler, helloProcess, counterProcess))

    multiprocessing.active_children()

如果我向 counterProcess 发送 SIGKILL,它将正确重启。但是,向 helloProcess 发送 SIGKILL 也会重新启动 counterProcess 而不是 helloProcess?

如果我向父进程发送 SIGTERM,父进程将退出,但子进程将成为孤儿并继续运行。我该如何纠正这种行为?

最佳答案

代码有几个问题,所以我将逐一讨论。

If I send a SIGKILL to the counterProcess, it will restart correctly. However, sending a SIGKILL to the helloProcess also restarts the counterProcess instead of the helloProcess?

这种奇怪的行为很可能是由于您的主进程中缺少阻塞调用,因为 multiprocessing.active_children() 并不真正充当一个进程。我无法真正解释程序如此行为的确切原因,但是在 __main__ 函数中添加阻塞调用,例如。

while True:
    time.sleep(1)

解决问题。

另一个非常严重的问题是将对象传递给处理程序的方式:

helloProcess = HelloWorld()
...
partial(signal_handler, helloProcess, counterProcess)

这是过时的,考虑到你在里面创建新的对象:

if not helloProcess.is_alive():
    print "Restarting helloProcess"

    helloProcess = HelloWorld()
    helloProcess.start()

请注意,这两个对象对 HelloWorld() 对象使用不同的别名。部分对象绑定(bind)到 __main__ 函数中的别名,而回调中的对象绑定(bind)到其局部范围别名。因此,通过将新对象分配给本地范围别名,您实际上并不会影响回调绑定(bind)到的对象(它仍然绑定(bind)到在 __main__ 范围内创建的对象)。

您可以通过在回调范围内以相同的方式将信号回调与新对象重新绑定(bind)来修复它:

def signal_handler(...):
    ...
    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
        signal(signame, partial(signal_handler, helloProcess, counterProcess))
    ...

然而,这会导致另一个陷阱,因为现在每个子进程都会从父进程继承回调,并在每次收到信号时访问它。要修复它,您可以在创建子进程之前临时将信号处理程序设置为默认值:

for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
    signal(signame, SIG_DFL)

最后,您可能希望在终止它们之前压制来自您的子进程的任何信号,否则它们会再次触发回调:

signal(SIGCHLD, SIG_IGN)

请注意,您可能想重新设计应用程序的架构并利用 multiprocessing 提供的一些功能。

最终代码:

import sys
import time
from signal import signal, SIGINT, SIGTERM, SIGQUIT, SIGCHLD, SIG_IGN, SIG_DFL
from functools import partial
import multiprocessing
#import setproctitle

class HelloWorld(multiprocessing.Process):
    def __init__(self):
        super(HelloWorld, self).__init__()

        # ignore, let parent handle it
        #signal(SIGTERM, SIG_IGN)

    def run(self):

        #setproctitle.setproctitle("helloProcess")

        while True:
            print "Hello World"
            time.sleep(1)

class Counter(multiprocessing.Process):
    def __init__(self):
        super(Counter, self).__init__()

        self.counter = 1

        # ignore, let parent handle it
        #signal(SIGTERM, SIG_IGN)

    def run(self):

        #setproctitle.setproctitle("counterProcess")

        while True:
            print self.counter
            time.sleep(1)
            self.counter += 1


def signal_handler(helloProcess, counterProcess, signum, frame):

    print multiprocessing.active_children()
    print "helloProcess: ", helloProcess
    print "counterProcess: ", counterProcess

    print "current_process: ", multiprocessing.current_process()

    if signum == 17:

        # Since each new child inherits current signal handler,
        # temporarily set it to default before spawning new child.
        for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
            signal(signame, SIG_DFL)

        print "helloProcess: ", helloProcess.is_alive()

        if not helloProcess.is_alive():
            print "Restarting helloProcess"

            helloProcess = HelloWorld()
            helloProcess.start()

        print "counterProcess: ", counterProcess.is_alive()

        if not counterProcess.is_alive():
            print "Restarting counterProcess"

            counterProcess = Counter()
            counterProcess.start()

        # After new children are spawned, revert to old signal handling policy.
        for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
            signal(signame, partial(signal_handler, helloProcess, counterProcess))


    else:

        # Ignore any signal that child communicates before quit   
        signal(SIGCHLD, SIG_IGN) 

        if helloProcess.is_alive():
            print "Stopping helloProcess"
            helloProcess.terminate()

        if counterProcess.is_alive():
            print "Stopping counterProcess"
            counterProcess.terminate()

        sys.exit(0)



if __name__ == '__main__':

    helloProcess = HelloWorld()
    helloProcess.start()

    counterProcess = Counter()
    counterProcess.start()

    for signame in [SIGINT, SIGTERM, SIGQUIT, SIGCHLD]:
        signal(signame, partial(signal_handler, helloProcess, counterProcess))

    while True:
        print multiprocessing.active_children()
        time.sleep(1)

关于Python multiprocessing - 捕获信号以重新启动子进程或关闭父进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40453496/

相关文章:

linux - 优雅地发送本地 tarball 并在远程端解压

ruby - Thread#terminate 和处理 SIGTERM

windows - 为什么在 asyncio 事件循环运行时我无法捕获 SIGINT?

python - 如何在 Python 中读取 excel 单元格并保留或检测其格式

python - 如何用装饰器绕过python函数定义?

python - 在 pandas DataFrame 中重新分配索引

Linux 环境变量行为

linux - 在 Linux 上检查连接的蓝牙设备的电池电量

java - 使用 "TERM"的信号处理

python - 列表理解 Append Odds Twice Evens Once