如何实现一个简单的跨平台Python守护进程?
我想让我的Python程序在后台运行,像个守护进程一样,适用于Windows或Unix系统。我发现python-daemon这个包只适用于Unix系统;有没有什么可以跨平台使用的替代方案?如果可以的话,我希望代码尽量简单。
5 个回答
这个问题虽然已经有6年了,但我也遇到了同样的问题,之前的回答对我来说跨平台的支持不够。虽然Windows服务和Unix守护进程的用法有些相似,但它们之间的差异很大,细节决定成败。简单来说,我想找到一种方法,让同一段应用代码能在Unix和Windows上都能运行,同时尽量满足Unix守护进程的要求(这个要求在其他地方解释得更清楚)。具体来说,我希望做到以下几点:
- 关闭打开的文件描述符(通常是所有的,但有些应用可能需要保留某些描述符)
- 将进程的工作目录更改为合适的位置,以防止出现“目录忙”的错误
- 更改文件访问创建掩码(在Python中是
os.umask
) - 将应用程序移到后台,并使其与启动进程脱离关系
- 完全与终端断开,包括将
STDIN
、STDOUT
和STDERR
重定向到不同的流(通常是DEVNULL
),并防止重新获得控制终端 - 处理信号,特别是
SIGTERM
。
跨平台守护进程化的根本问题在于,Windows作为一个操作系统,实际上并不支持守护进程的概念:从终端启动的应用程序(或在任何其他交互上下文中启动,包括从资源管理器启动等)将继续以可见窗口运行,除非控制应用程序(在这个例子中是Python)包含一个无窗口的图形用户界面。此外,Windows的信号处理非常不足,尝试向一个独立的Python进程发送信号(与子进程不同,子进程在终端关闭时不会存活)几乎总是会导致该Python进程立即退出而没有任何清理(没有finally:
、没有atexit
、没有__del__
等)。
Windows服务(虽然在很多情况下是可行的替代方案)对我来说基本上不在考虑范围内:它们不支持跨平台,并且需要修改代码。pythonw.exe
(一个无窗口的Python版本,与所有最新的Windows Python二进制文件一起提供)更接近,但仍然不够好:特别是,它没有改善信号处理的情况,而且你仍然无法轻松地从终端启动pythonw.exe
应用程序并在启动过程中与之交互(例如,传递动态启动参数给你的脚本,比如密码、文件路径等),在“守护进程化”之前。
最后,我决定使用subprocess.Popen
和creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
关键字来创建一个独立的、无窗口的进程:
import subprocess
independent_process = subprocess.Popen(
'/path/to/pythonw.exe /path/to/file.py',
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)
不过,这仍然给我带来了启动通信和信号处理的额外挑战。简单来说,对于启动通信,我的策略是:
- 使用
pickle
序列化启动进程的重要部分 - 将其存储在一个
tempfile
中 - 在启动子进程之前,将该文件的路径添加到子进程的环境中
- 从“守护进程化”函数中提取并返回命名空间
至于信号处理,我需要更有创意。在“守护进程化”的进程中:
- 在守护进程中忽略信号,因为如前所述,它们都会立即终止进程而没有清理
- 创建一个新线程来管理信号处理
- 该线程启动子信号处理进程并等待它们完成
- 外部应用程序向子信号处理进程发送信号,导致其终止并完成
- 这些进程使用信号号作为返回代码
- 信号处理线程读取返回代码,然后调用用户定义的信号处理程序,或者使用cytypes API在Python主线程中引发适当的异常
- 对新信号重复以上步骤
说到这里,对于未来遇到这个问题的任何人,我开发了一个名为daemoniker的库,它将正确的Unix守护进程化和上述Windows策略封装成一个统一的接口。这个跨平台API看起来是这样的:
from daemoniker import Daemonizer
with Daemonizer() as (is_setup, daemonizer):
if is_setup:
# This code is run before daemonization.
do_things_here()
# We need to explicitly pass resources to the daemon; other variables
# may not be correct
is_parent, my_arg1, my_arg2 = daemonizer(
path_to_pid_file,
my_arg1,
my_arg2
)
if is_parent:
# Run code in the parent after daemonization
parent_only_code()
# We are now daemonized, and the parent just exited.
code_continues_here()
在Windows系统中,这种东西叫做“服务”,你可以很简单地实现它,比如使用win32serviceutil这个模块,它是pywin32的一部分。不过,服务和守护进程这两个概念在细节上差别很大,尽管它们的目的差不多。我知道没有哪个Python的工具可以把它们统一成一个框架。
这里有两个选择:
把你的程序移植到Windows服务上。你可能可以在这两个版本之间共享很多代码。
你的程序真的需要使用守护进程的功能吗?如果不需要,你可以把它重写成一个简单的后台服务器,通过套接字来管理通信,并执行它的任务。这样做可能会消耗比守护进程更多的系统资源,但它会比较不依赖于平台。