起因

同事想要写一个代码,主进程中监听SIGINT、SIGTERM信号退出,并关闭启动的子进程,代码类似这样

import signal
import sys
import time
from multiprocessing import Process
from multiprocessing import Manager


class Test:

    def __init__(self):
        self.is_running = True
        if sys.platform != 'win32':
            signal.signal(signal.SIGHUP, self._signal_handler)
        signal.signal(signal.SIGINT, self._signal_handler)
        signal.signal(signal.SIGTERM, self._signal_handler)

    def _signal_handler(self, signum, frame):
        """
        Terminate scenario ticking when receiving a signal interrupt
        """
        self.is_running = False
        print('关闭场景调度')

    def listen_manage(self, running, kill_task_lst):
        """监控取消任务"""
        while running.value:
            kill_task_lst.append(1)
            time.sleep(1)
            print("child is running")

    def run(self):
        """启动场景调度"""
        kill_task_lst = Manager().list()
        running = Manager().Value(bool, True)
        print('启动监控进程')
        p = Process(target=self.listen_manage, args=(running, kill_task_lst, ))
        p.start()
        while self.is_running:
            time.sleep(0.5)
            print("main is running")

但是发现在ctrl + c 时候,子进程也同时接收到了信号,退出了

启动监控进程
main is running
main is running
child is running
main is running
关闭场景调度
Process Process-3:
Traceback (most recent call last):
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\process.py", line 315, in _bootstrap
    self.run()
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "D:\python_projects\pythonProject\test_egg\test.py", line 35, in listen_manage
    time.sleep(1)
KeyboardInterrupt
main is running

这时猜想到可能是子进程也监听了SIGINT,退出了程序,所以想在子任务中设置忽略信号,改造后的 listen_manage:

    def listen_manage(self, running, kill_task_lst):
        """监控取消任务"""
        if sys.platform != 'win32':
            signal.signal(signal.SIGHUP, signal.SIG_IGN)
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        signal.signal(signal.SIGTERM, signal.SIG_IGN)
        while running.value::
            kill_task_lst.append(1)
            time.sleep(1)
            print("child is running")

运行后会报 BrokenPipeError: [WinError 232] 管道正在被关闭。

启动监控进程
main is running
main is running
child is running
main is running
关闭场景调度
main is running
child is running
Process Process-4:
Traceback (most recent call last):
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\process.py", line 315, in _bootstrap
    self.run()
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "D:\python_projects\pythonProject\test_egg\test.py", line 37, in listen_manage
    kill_task_lst.append(1)
  File "<string>", line 2, in append
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\managers.py", line 834, in _callmethod
    conn.send((self._id, methodname, args, kwds))
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\connection.py", line 206, in send
    self._send_bytes(_ForkingPickler.dumps(obj))
  File "D:\software\anaconda\envs\test_egg1\lib\multiprocessing\connection.py", line 280, in _send_bytes
    ov, err = _winapi.WriteFile(self._handle, buf, overlapped=True)
BrokenPipeError: [WinError 232] 管道正在被关闭。

这里想到的原因是父进程直接退出了,导致共享变量kill_task_lst被关闭导致的,所以想在关闭管道前等待子进程退出
可执行的完整代码:

import signal
import sys
import time
from multiprocessing import Process
from multiprocessing import Manager


class Test:

    def __init__(self):
        self.is_running = True
        if sys.platform != 'win32':
            signal.signal(signal.SIGHUP, self._signal_handler)
        signal.signal(signal.SIGINT, self._signal_handler)
        signal.signal(signal.SIGTERM, self._signal_handler)

    def _signal_handler(self, signum, frame):
        """
        Terminate scenario ticking when receiving a signal interrupt
        """
        self.is_running = False
        print('关闭场景调度')

    def listen_manage(self, running, kill_task_lst):
        """监控取消任务"""
        if sys.platform != 'win32':
            signal.signal(signal.SIGHUP, signal.SIG_IGN)
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        signal.signal(signal.SIGTERM, signal.SIG_IGN)
        while running.value:
            kill_task_lst.append(1)
            time.sleep(1)
            print("child is running")

    def run(self):
        """启动场景调度"""
        kill_task_lst = Manager().list()
        running = Manager().Value(bool, True)
        print('启动监控进程')
        p = Process(target=self.listen_manage, args=(running, kill_task_lst, ))
        p.start()
        while self.is_running:
            time.sleep(0.5)
            print("main is running")
        running.set(False)
        while p.is_alive():
            time.sleep(0.01)
        print("finish.....")

if __name__ == '__main__':
    t = Test()
    t.run()

执行结果:

启动监控进程
main is running
main is running
child is running
main is running
关闭场景调度
main is running
child is running
finish.....
09-05 16:17