在Python守护进程中使用multiprocessing模块时出错

7 投票
6 回答
4908 浏览
提问于 2025-04-15 13:59

我在使用multiprocessing模块时遇到了一个错误,这个模块是在一个python守护进程中使用的(通过python-daemon)。

Traceback (most recent call last):
  File "/usr/local/lib/python2.6/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "/usr/local/lib/python2.6/multiprocessing/util.py", line 262, in _exit_function
    for p in active_children():
  File "/usr/local/lib/python2.6/multiprocessing/process.py", line 43, in active_children
    _cleanup()
  File "/usr/local/lib/python2.6/multiprocessing/process.py", line 53, in _cleanup
    if p._popen.poll() is not None:
  File "/usr/local/lib/python2.6/multiprocessing/forking.py", line 106, in poll
    pid, sts = os.waitpid(self.pid, flag)
OSError: [Errno 10] No child processes

这个守护进程(父进程)会创建几个子进程,然后定期检查这些子进程是否完成。如果父进程发现某个子进程已经完成,它就会尝试重启这个子进程。就在这个时候,上面的错误就出现了。看起来一旦某个子进程完成,任何与multiprocessing模块相关的操作都会引发这个错误。如果我在一个非守护的python脚本中运行相同的代码,就不会出现任何错误。

补充:

示例脚本

from daemon import runner

class DaemonApp(object):
    def __init__(self, pidfile_path, run):
        self.pidfile_path = pidfile_path
        self.run = run

        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'

def run():
    import multiprocessing as processing
    import time
    import os
    import sys
    import signal

    def func():
        print 'pid: ', os.getpid()
        for i in range(5):
            print i
            time.sleep(1)

    process = processing.Process(target=func)
    process.start()

    while True:
        print 'checking process'
        if not process.is_alive():
            print 'process dead'
            process = processing.Process(target=func)
            process.start()
        time.sleep(1)

# uncomment to run as daemon
app = DaemonApp('/root/bugtest.pid', run)
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

#uncomment to run as regular script
#run()

6 个回答

0

我记得之前在主干和2.6的维护版本里做了一些修复,这应该能帮到你。你能试着在python-trunk或者最新的2.6-maint svn里运行你的脚本吗?我这边没法查看到错误信息。

7

你的问题是守护进程和多进程模块之间的冲突,特别是在处理SIGCLD(子进程终止)信号时。守护进程在启动时将SIGCLD设置为SIG_IGN,这样在Linux系统上,终止的子进程会立即被回收,而不是变成僵尸进程,直到父进程调用wait()。但是,多进程模块的is_alive测试会调用wait()来检查进程是否还活着,如果进程已经被回收,这个检查就会失败。

最简单的解决办法就是把SIGCLD重新设置为SIG_DFL(默认行为——忽略这个信号,让父进程通过wait()来等待终止的子进程):

def run():
    # ...

    signal.signal(signal.SIGCLD, signal.SIG_DFL)

    process = processing.Process(target=func)
    process.start()

    while True:
        # ...
5

忽略 SIGCLD 也会导致 subprocess 模块出现问题,这是因为这个模块有一个bug(详细信息可以查看issue 1731717,这个问题在2011年9月21日时仍然没有解决)。

python-daemon 库的 1.4.8版本 中,解决了这个问题;现在它不再默认处理 SIGCLD,因此不再与其他标准库模块产生这种不愉快的冲突。

撰写回答