退出守护进程时的问题

4 投票
2 回答
2582 浏览
提问于 2025-04-15 12:23

我正在写一个守护进程程序,它会启动几个子进程。在我运行 stop 脚本后,主进程本该退出却一直在运行,这让我很困惑。

import daemon, signal
from multiprocessing import Process, cpu_count, JoinableQueue
from http import httpserv
from worker import work

class Manager:
    """
    This manager starts the http server processes and worker
    processes, creates the input/output queues that keep the processes
    work together nicely.
    """
    def __init__(self):
        self.NUMBER_OF_PROCESSES = cpu_count()

    def start(self):
        self.i_queue = JoinableQueue()
        self.o_queue = JoinableQueue()

        # Create worker processes
        self.workers = [Process(target=work,
                                args=(self.i_queue, self.o_queue))
                        for i in range(self.NUMBER_OF_PROCESSES)]
        for w in self.workers:
            w.daemon = True
            w.start()

        # Create the http server process
        self.http = Process(target=httpserv, args=(self.i_queue, self.o_queue))
        self.http.daemon = True
        self.http.start()

        # Keep the current process from returning
        self.running = True
        while self.running:
            time.sleep(1)

    def stop(self):
        print "quiting ..."

        # Stop accepting new requests from users
        os.kill(self.http.pid, signal.SIGINT)

        # Waiting for all requests in output queue to be delivered
        self.o_queue.join()

        # Put sentinel None to input queue to signal worker processes
        # to terminate
        self.i_queue.put(None)
        for w in self.workers:
            w.join()
        self.i_queue.join()

        # Let main process return
        self.running = False


import daemon

manager = Manager()
context = daemon.DaemonContext()
context.signal_map = {
        signal.SIGHUP: lambda signum, frame: manager.stop(),
        }

context.open()
manager.start()

这个 stop 脚本其实就一行代码 os.kill(pid, signal.SIGHUP),但是在执行后,子进程(工作进程和HTTP服务器进程)都正常结束了,唯独主进程还在那儿,我不知道是什么原因让它没有退出。

2 个回答

1

你创建了一个http服务器进程,但没有用join()来等待它完成。如果你不是用os.kill()来停止这个http服务器进程,而是像给工作进程发送停止信号一样,给它发送一个停止处理的信号(None),然后再调用self.http.join(),会发生什么呢?

更新: 你还需要给输入队列发送一次None信号,次数要和工作进程的数量一样。你可以这样尝试:

    for w in self.workers:
        self.i_queue.put(None)
    for w in self.workers:
        w.join()

注意:你需要两个循环的原因是,如果你在同一个循环中把None放进队列,并且同时调用join(),那么None可能会被除了w以外的其他工作进程拿走,这样在w上调用join()就会导致调用者被阻塞。

你没有展示工作进程或http服务器的代码,所以我假设它们在调用task_done等方面表现良好,并且每个工作进程在看到None后会立刻退出,而不会再从输入队列中get()更多的东西。

另外,请注意,JoinableQueue.task_done()有一个至少存在的难以重现的已知问题,这可能会影响到你。

1

我尝试了另一种方法,这似乎有效(注意我把代码中与守护进程相关的部分去掉了,因为我没有安装那个模块)。

import signal

class Manager:
    """
    This manager starts the http server processes and worker
    processes, creates the input/output queues that keep the processes
    work together nicely.
    """
    def __init__(self):
        self.NUMBER_OF_PROCESSES = cpu_count()

    def start(self):

       # all your code minus the loop

       print "waiting to die"

       signal.pause()

    def stop(self):
        print "quitting ..."

        # all your code minus self.running


manager = Manager()

signal.signal(signal.SIGHUP, lambda signum, frame: manager.stop())

manager.start()

有一个警告,就是 signal.pause() 会因为任何信号而解除暂停,所以你可能需要相应地修改你的代码。

编辑:

以下代码对我来说运行得很好:

import daemon
import signal
import time

class Manager:
    """
    This manager starts the http server processes and worker
    processes, creates the input/output queues that keep the processes
    work together nicely.
    """
    def __init__(self):
        self.NUMBER_OF_PROCESSES = 5

    def start(self):

       # all your code minus the loop

       print "waiting to die"
       self.running = 1
       while self.running:
           time.sleep(1)

       print "quit"



    def stop(self):
        print "quitting ..."

        # all your code minus self.running

        self.running = 0


manager = Manager()

context = daemon.DaemonContext()
context.signal_map = {signal.SIGHUP : lambda signum, frame: manager.stop()}

context.open()
manager.start()

你使用的是什么版本的 Python?

撰写回答