Python 优雅地处理子进程关闭

Python 优雅地处理子进程关闭,python,multiprocessing,pipe,Python,Multiprocessing,Pipe,我在一个项目上工作,我有一批工人。我没有使用内置的多处理.Pool,而是创建了自己的进程池 它的工作方式是我创建了两个多处理.Queue实例——一个用于向工作人员发送工作任务,另一个用于接收结果 每个工人只是坐在一个永久运行的循环中,如下所示: while True: try: request = self.request_queue.get(True, 5) except Queue.Empty: continue else:

我在一个项目上工作,我有一批工人。我没有使用内置的
多处理.Pool
,而是创建了自己的进程池

它的工作方式是我创建了两个
多处理.Queue
实例——一个用于向工作人员发送工作任务,另一个用于接收结果

每个工人只是坐在一个永久运行的循环中,如下所示:

while True:
    try:
        request = self.request_queue.get(True, 5)
    except Queue.Empty:
        continue
    else:
        result = request.callable(*request.args, **request.kwargs)
        self.results_queue.put((request, result))
还有一些错误处理代码,但我把它留给了brewity。每个工作进程都将
守护进程设置为
1

我希望正确关闭主进程和所有子工作进程。我迄今为止的经验(使用Ctrl+C):

  • 在没有特殊实现的情况下,每个子进程都会通过键盘中断回溯停止/崩溃,但主进程不存在,必须终止(
    sudo kill-9
  • 如果我为子进程实现一个信号处理程序,设置为忽略SIGINT,主线程将显示键盘中断跟踪框,但两种方式都不会发生
  • 如果我为子进程和主进程实现一个信号处理程序,我可以看到信号处理程序在主进程中被调用,但是调用
    sys.exit()
    似乎没有任何效果
我正在寻找一种“最佳实践”的处理方法。我还读到,关闭与
Queue
s和
Pipe
s交互的进程可能会导致它们与其他进程死锁(由于内部使用了信号量和其他东西)

我目前的做法如下: -找到一种方法向每个进程发送一个内部信号(使用一个单独的命令队列或类似命令队列),以终止它们的主循环。 -为发送关机命令的主回路实现一个信号处理程序。子进程将有一个子处理程序,将它们设置为忽略信号


这是正确的方法吗?

您需要注意的是,在您想要关闭的时候,队列中可能存在消息,因此您需要一种方法让您的进程干净地排空其输入队列。假设您的主进程将认识到是时候关闭了,您可以这样做

  • 向每个工作进程发送哨兵。这是一条特殊的消息(通常为
    None
    ),永远不会看起来像普通消息。在sentinel之后,刷新并关闭每个工作进程的队列
  • 在工作进程中,使用与以下伪代码类似的代码:

    while True:  # Your main processing loop
        msg = inqueue.dequeue()  # A blocking wait
        if msg is None:
            break
        do_something()
    outqueue.flush()
    outqueue.close()
    
  • 如果可能有多个进程在
    inqueue
    上发送消息,则需要更复杂的方法。此示例取自Python 3.2或更高版本中的
    logging.handlers.QueueListener
    中的
    monitor
    方法的源代码,显示了一种可能性

                """
                Monitor the queue for records, and ask the handler
                to deal with them.
    
                This method runs on a separate, internal thread.
                The thread will terminate if it sees a sentinel object in the queue.
                """
                q = self.queue
                has_task_done = hasattr(q, 'task_done')
                # self._stop is a multiprocessing.Event object that has been set by the
                # main process as part of the shutdown processing, before sending
                # the sentinel           
                while not self._stop.isSet():
                    try:
                        record = self.dequeue(True)
                        if record is self._sentinel:
                            break
                        self.handle(record)
                        if has_task_done:
                            q.task_done()
                    except queue.Empty:
                        pass
                # There might still be records in the queue.
                while True:
                    try:
                        record = self.dequeue(False)
                        if record is self._sentinel:
                            break
                        self.handle(record)
                        if has_task_done:
                            q.task_done()
                    except queue.Empty:
                        break
    

    您需要注意的是,在您希望关闭时,队列中可能存在消息,因此您需要一种方法让您的进程干净地排空其输入队列。假设您的主进程将认识到是时候关闭了,您可以这样做

  • 向每个工作进程发送哨兵。这是一条特殊的消息(通常为
    None
    ),永远不会看起来像普通消息。在sentinel之后,刷新并关闭每个工作进程的队列
  • 在工作进程中,使用与以下伪代码类似的代码:

    while True:  # Your main processing loop
        msg = inqueue.dequeue()  # A blocking wait
        if msg is None:
            break
        do_something()
    outqueue.flush()
    outqueue.close()
    
  • 如果可能有多个进程在
    inqueue
    上发送消息,则需要更复杂的方法。此示例取自Python 3.2或更高版本中的
    logging.handlers.QueueListener
    中的
    monitor
    方法的源代码,显示了一种可能性

                """
                Monitor the queue for records, and ask the handler
                to deal with them.
    
                This method runs on a separate, internal thread.
                The thread will terminate if it sees a sentinel object in the queue.
                """
                q = self.queue
                has_task_done = hasattr(q, 'task_done')
                # self._stop is a multiprocessing.Event object that has been set by the
                # main process as part of the shutdown processing, before sending
                # the sentinel           
                while not self._stop.isSet():
                    try:
                        record = self.dequeue(True)
                        if record is self._sentinel:
                            break
                        self.handle(record)
                        if has_task_done:
                            q.task_done()
                    except queue.Empty:
                        pass
                # There might still be records in the queue.
                while True:
                    try:
                        record = self.dequeue(False)
                        if record is self._sentinel:
                            break
                        self.handle(record)
                        if has_task_done:
                            q.task_done()
                    except queue.Empty:
                        break
    

    我发现有一个基于进程的threading.Event克隆,它使向每个进程发送“stop”信号变得容易得多,我设置了一些类似于您编写的东西。我发现有一个基于进程的threading.Event克隆,它使向每个进程发送“stop”信号变得容易得多,我建立了一些类似于你所写的东西。