正常处理子进程关闭

时间:2013-08-05 15:50:04

标签: python multiprocessing pipe

我正在开展一个项目,我有一群工人。我没有使用内置的multiprocessing.Pool,但创建了自己的进程池。

它的工作方式是我创建了两个multiprocessing.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))

还有一些错误处理代码,但我把它留给了酿酒。每个工作进程都将daemon设置为1

我希望正确关闭主进程和所有子进程进程。到目前为止我的经历(做Ctrl + C):

  • 没有特殊实现,每个子进程都会使用KeyboardInterrupt回溯停止/崩溃,但主进程不存在且必须被终止(sudo kill -9)。
  • 如果我为子进程实现信号处理程序,设置为忽略SIGINT,主线程显示KeyboardInterrupt tracebok,但无论如何都没有发生。
  • 如果我为子进程和主进程实现信号处理程序,我可以看到在主进程中调用信号处理程序,但调用sys.exit()似乎没有任何影响。

我正在寻找一种处理此问题的“最佳实践”方式。我还在某处读到,关闭与QueuePipe进行交互的进程可能会导致它们与其他进程发生死锁(由于信号量和内部使用的其他内容)。

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

这是正确的方法吗?

1 个答案:

答案 0 :(得分:1)

您需要注意的是处理您想要关闭时队列中有消息的可能性,因此您需要一种方法让您的进程干净地排空其输入队列。假设您的主进程是能够识别出是时候关闭的进程,那么您可以这样做。

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

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

                """
                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