如何在Python中创建守护进程?

282

在谷歌上搜索可以找到两个代码片段。第一个结果是这个代码示例,其中有很多文档和解释,并且还有一些有用的讨论。

然而,另一个代码示例虽然没有那么多文档,但包括了传递命令(如启动、停止和重启)的示例代码。它还创建了一个PID文件,可以方便地检查守护进程是否已经在运行等。

这些示例都说明了如何创建守护进程。还有其他需要考虑的事情吗?其中一个示例比另一个更好,为什么?


1
我一直认为守护进程的代码是不必要的。为什么不让 shell 来做呢? - emil.p.stanchev
19
因为它没有执行setsid或setpgrp。 - bmargulies
4
请使用http://supervisord.org/。这样你就不需要fork()或重定向stdin/stderr,只需编写普通程序即可。 - guettli
16个回答

205

75
自从我最初发布这个回复以来,PEP 3143 的参考实现现在可用了:http://pypi.python.org/pypi/python-daemon/。Translated: Since I originally posted this reply, a reference implementation of PEP 3143 is now available: http://pypi.python.org/pypi/python-daemon/. - Jeff Bauer
@JeffBauer 原始链接已失效,我记得它很有用,你不会知道一个有效的链接吗? - CrazyCasta
1
@CrazyCasta:Sander Marechal的版本仍然可以在Wayback Machine上找到。 - Jeff Bauer
1
@JeffBauer:Sander的代码仍然比http://pypi.python.org/pypi/python-daemon更好。更可靠。只需举一个例子:尝试使用python-daemon启动两次相同的守护进程:会出现一个大而丑陋的错误。而使用Sander的代码:会有一个友好的提示“守护进程已在运行中”。 - Basj
2
由于“python-daemon”模块文档仍然缺失(请参见许多其他SO问题),并且相当晦涩(如何使用此模块从命令行正确启动/停止守护程序?),因此我修改了Sander Marechal的代码示例以添加在停止守护程序之前执行的quit()方法。这是它的链接。 - Basj
显示剩余2条评论

173

当成为一个表现良好的守护进程时,有许多琐碎的事情需要注意:

  • 防止核心转储(许多守护程序以root身份运行,核心转储可能包含敏感信息)

  • chroot监狱中正确运行

  • 适当设置UID、GID、工作目录、umask和其他进程参数以满足使用情况

  • 放弃提升的suidsgid权限

  • 关闭所有打开的文件描述符,具体排除哪些依赖于使用情况

  • 在已分离的上下文中启动时正确运行,例如initinetd

  • 为合理的守护程序行为设置信号处理程序,但也根据使用情况确定特定的处理程序

  • 重定向标准流stdinstdoutstderr,因为守护进程不再具有控制终端

  • 将PID文件处理为协作的咨询锁定,这本身就是一个很麻烦的问题,有许多相互矛盾但有效的处理方式

  • 在进程终止时进行适当的清理

  • 确实成为守护进程而不导致僵尸进程

其中一些是标准的,如经典的Unix文献(《UNIX环境高级编程》作者W. Richard Stevens已故,Addison-Wesley出版,1992年)所述。其他行为,例如流重定向和PID文件处理,是大多数守护程序用户所期望的惯例行为,但不太标准化。

所有这些都包含在PEP 3143“标准守护进程库”规范中。python-daemon参考实现适用于Python 2.7或更高版本以及Python 3.2或更高版本。


26
"gaol" 的拼写是正确的,因为这就是 W. Richard Stevens 拼写它的方式 :-) - bignose
7
监狱是一个“英国的东西”。因为这个海报来自澳大利亚,所以有道理。 - devin
1
有没有计划制作一个适用于Py3k的友好版本? - Tim Tisdall

111

这是我用来开发新守护进程应用程序时使用的基本“Howdy World” Python 守护进程。

#!/usr/bin/python
import time
from daemon import runner

class App():
    def __init__(self):
        self.stdin_path = '/dev/null'
        self.stdout_path = '/dev/tty'
        self.stderr_path = '/dev/tty'
        self.pidfile_path =  '/tmp/foo.pid'
        self.pidfile_timeout = 5
    def run(self):
        while True:
            print("Howdy!  Gig'em!  Whoop!")
            time.sleep(10)

app = App()
daemon_runner = runner.DaemonRunner(app)
daemon_runner.do_action()

请注意,您需要安装python-daemon库。您可以通过以下方式进行安装:

pip install python-daemon

只需使用./howdy.py start启动它,使用./howdy.py stop停止它。


5
你导入的 daemon 模块不是 Python 的标准部分(至少目前还不是)。你需要使用 pip install python-daemon 或其等效命令来安装它。 - Nate
6
我按照你的描述安装了python-daemon,但当我尝试运行我的应用程序(与你最后三行相同)时,出现了ImportError: cannot import name runner的错误。 - Nostradamnit
4
这个建议似乎过时了——至少从2013年9月起,http://www.python.org/dev/peps/pep-3143/没有提到可以导入的“runner”。这当然可以解释@Nostradamnit的观察结果。 - offby1
2
这对我来说仍然很好,在2013年9月,在Ubuntu 13.04上,使用原始的Python软件包,安装了python2.7和python-daemon。然而,使用python3时,我看到错误:“from daemon import runner ImportError: No module named 'daemon'”。 - Dustin Kirkland
1
我遇到了以下错误 io.UnsupportedOperation: File or stream is not seekable. - alper
显示剩余6条评论

48

另一种方法是创建一个普通的、非守护程序化的Python程序,然后使用supervisord外部进行守护。这可以避免很多麻烦,并且具有*nix和语言可移植性。


1
我认为这是最好的方法。特别是如果您想在一个操作系统上运行多个守护进程。不要编写新代码,重复利用已有的代码。 - guettli
它简化了很多问题。我写过真正的守护进程——它们并不容易。 - Chris Johnson
1
最佳答案就藏在这里 :) - kawing-chiu
1
这真是太棒了。在花费数小时尝试运行python-daemon后,这是一个对我有效的开箱即用解决方案。出色的文档和示例使我的守护进程在几分钟内启动并运行。 - Nikhil Sahu
或者,如果您正在使用现代Linux,则将程序作为systemd服务运行... - Mikhail T.

44
请注意python-daemon 包,它可以轻松解决很多与守护进程相关的问题。
除了其他功能外,它还可以实现以下功能(来自Debian软件包描述):
  • 将进程分离到自己的进程组中。
  • 设置适合在chroot内部运行的进程环境。
  • 放弃suid和sgid权限。
  • 关闭所有打开的文件描述符。
  • 更改工作目录、uid、gid和umask。
  • 设置适当的信号处理程序。
  • 为标准输入、标准输出和标准错误打开新的文件描述符。
  • 管理指定的PID锁文件。
  • 注册清理函数以进行退出处理。

28

也许不是对问题的直接回答,但systemd可以用来将您的应用程序作为守护进程运行。以下是一个示例:

[Unit]
Description=Python daemon
After=syslog.target
After=network.target

[Service]
Type=simple
User=<run as user>
Group=<run as group group>
ExecStart=/usr/bin/python <python script home>/script.py

# Give the script some time to startup
TimeoutSec=300

[Install]
WantedBy=multi-user.target

我喜欢这种方法,因为其中的大部分工作已经为你完成,然后你的守护进程脚本就可以与系统的其余部分类似地运行。


3
这是正确和合理的方法。1) 需要保存到 /etc/systemd/system/control.service 2) 使用sudo管理,执行命令 systemctl start control.service 即可。 - jimper

8

这个函数将把一个应用程序转化为守护进程:

import sys
import os

def daemonize():
    try:
        pid = os.fork()
        if pid > 0:
            # exit first parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #1 failed: {0}\n'.format(err))
        sys.exit(1)
    # decouple from parent environment
    os.chdir('/')
    os.setsid()
    os.umask(0)
    # do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # exit from second parent
            sys.exit(0)
    except OSError as err:
        sys.stderr.write('_Fork #2 failed: {0}\n'.format(err))
        sys.exit(1)
    # redirect standard file descriptors
    sys.stdout.flush()
    sys.stderr.flush()
    si = open(os.devnull, 'r')
    so = open(os.devnull, 'w')
    se = open(os.devnull, 'w')
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

8

YapDi 是一个 Python 包。它可以在脚本内部将 Python 脚本转换为守护进程模式。


7

由于python-daemon尚未支持python 3.x,从邮件列表中可以读到,它可能永远不会支持。因此,我编写了一个新的PEP 3143实现:pep3143daemon

pep3143daemon应该支持至少python 2.6、2.7和3.x

它还包含一个PidFile类。

该库仅依赖于标准库和six模块。

它可以用作python-daemon的替代品。

这里是文档


6

我担心 @Dustin 提到的守护进程模块对我不起作用。相反,我安装了python-daemon并使用以下代码:

# filename myDaemon.py
import sys
import daemon
sys.path.append('/home/ubuntu/samplemodule') # till __init__.py
from samplemodule import moduleclass 

with daemon.DaemonContext():
    moduleclass.do_running() # I have do_running() function and whatever I was doing in __main__() in module.py I copied in it.

跑步很容易。
> python myDaemon.py

为了完整起见,这里是samplemodule目录的内容:

>ls samplemodule
__init__.py __init__.pyc moduleclass.py

moduleclass.py的内容可以是:

class moduleclass():
    ...

def do_running():
    m = moduleclass()
    # do whatever daemon is required to do.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接