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

在 Google 上搜索可以找到 x2代码片段。第一个结果是 这个代码配方,它有很多文档和解释,以及一些有用的讨论。

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

这些示例都解释了如何创建守护进程。是否还有其他需要考虑的事情?一个样本比另一个好吗? 为什么?

343540 次浏览

当前的解决方案

标准守护进程库的参考实现现在作为python-daemon可用。

历史的答案

Sander Marechal的代码示例比2004年最初发布的原版更好。我曾经为Pyro贡献了一个daemonizer,但如果我必须重新做的话,可能会使用Sander的代码。

80%的情况下,当人们说“守护进程”时,他们只想要一个服务器。因为这个问题在这一点上是完全不清楚的,所以很难说答案的可能范围是什么。既然有一个服务器就足够了,那就从那里开始吧。如果实际需要一个“守护进程”(这种情况很少见),请阅读nohup作为对服务器进行守护的一种方式。

在真正需要守护进程之前,只需编写一个简单的服务器。

还要看看WSGI参考实现。

再看看简单HTTP服务器

“还有其他需要考虑的事情吗?”是的。大概有一百万件事。什么协议?有多少请求?为每个请求服务多长时间?他们多久来一次?您会使用专用的流程吗?线程?子流程?编写一个守护进程是一项大工作。

用Python创建守护进程最简单的方法是使用扭曲的事件驱动框架。它为您处理守护进程所需的所有东西。它使用反应器模式来处理并发请求。

当成为运行良好的守护进程时,有很多精细的东西需要注意:

  • 阻止核心转储(许多守护进程以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或更高版本。

注意python-daemon包,它解决了很多后台守护进程开箱即用的问题。

它支持的其他特性(来自Debian包描述)包括:

  • 将进程分离到自己的进程组中。
  • 设置适合在chroot中运行的进程环境。
  • 放弃suid和sgid权限。
  • 关闭所有打开的文件描述符。
  • 修改工作目录、uid、gid和umask。
  • 设置适当的信号处理程序。
  • 打开stdin、stdout和stderr的新文件描述符。
  • 管理指定PID锁文件。
  • 注册用于出口处理的清理函数。

YapDi是一个python包。它可用于从脚本内部将python脚本转换为守护进程模式。

这是我的基本'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结束它。

另一种方法——创建一个正常的、非守护的Python程序,然后使用supervisord从外部对其进行守护。这可以省去很多麻烦,并且是*nix和语言可移植的。

在python中进行daemonization时,还有一件需要考虑的事情:

如果你正在使用python 日志记录,并且你想在守护后继续使用它,请确保在处理程序(特别是文件处理程序)上调用close()

如果您不这样做,处理程序仍然可以认为它有文件打开,并且您的消息将简单地消失-换句话说,确保记录器知道它的文件是关闭的!

这假设当你守护进程时,你不加区别地关闭所有打开的文件描述符——相反,你可以尝试关闭除了日志文件以外的所有文件(但是关闭所有文件然后重新打开你想要的文件通常更简单)。

因为python-daemon还不支持python3。从邮件列表中可以读取的内容(可能永远不会读取),我已经编写了PEP 3143: pep3143daemon的新实现

pep3143守护进程至少支持python 2.6、2.7和3.x

它还包含一个PidFile类。

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

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

这里是文档

可能这不是对问题的直接回答,但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

我更喜欢这种方法,因为许多工作都是为您完成的,并且您的守护进程脚本的行为与系统的其他部分类似。

恐怕@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

py的内容可以是

class moduleclass():
...


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

这个函数将应用程序转换为守护进程:

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())

我修改了Sander Marechal的代码示例(由@JeffBauer在公认的答案中提到)中的几行,以添加一个quit()方法,该方法在守护进程停止之前执行。这有时是非常有用的。

在这里

注意:我没有使用“python-daemon”模块,因为文档仍然缺失(参见许多其他SO问题),而且相当模糊(如何使用这个模块从命令行正确地启动/停止一个守护进程?)

经过几年的努力和多次尝试(我尝试了这里给出的所有答案,但最后都有一些小缺点),现在我意识到有一种比直接从Python启动、停止和重新启动守护进程更好的方法:使用OS工具。

例如,对于Linux,我不做python myapp startpython myapp stop,而是这样启动应用程序:

screen -S myapp python myapp.py
# CTRL+A, D to detach

screen -dmS myapp python myapp.py在一个命令中启动和卸载它

然后:

screen -r myapp

重新连接到这个终端。进入终端后,可以使用CTRL+C来停止它。

虽然你可能更喜欢Python -daemon模块提供的纯Python解决方案,但在libc中有一个daemon(3)函数——至少在BSDLinux上——它将做正确的事情。

从python调用它很简单:

import ctypes


ctypes.CDLL(None).daemon(0, 0) # Read the man-page for the arguments' meanings

剩下要做的唯一一件事就是创建(和锁定)pid文件。但你能处理好自己…