如何实现一个简单的跨平台Python守护进程?

24

我希望我的Python程序可以作为守护进程在Windows或Unix上后台运行。我看到python-daemon包只适用于Unix系统;是否有跨平台的替代品?如果可能,我希望保持代码尽可能简单。


1
我不知道答案,但是我对这个非常感兴趣。 - Bite code
是的,如果有一种跨平台的方式可以使用相同/类似的代码在Unix守护进程或Windows服务上启动程序,那将会非常方便。也许并不实用,我不确定... - Nick Bolton
你能详细介绍一下你的程序是做什么的吗? - Adam Matan
@Adam,这可能太冗长和离题了,在这里描述不太合适 ;) - Nick Bolton
由于“守护进程”只是一个概念,而实现方式高度依赖于平台,所以这个问题并没有意义。您是在询问关于平台特定的包装器,以便您的应用程序不会发生太多变化吗? - S.Lott
5个回答

12

在Windows中,它被称为"服务",您可以使用win32serviceutil模块实现它,该模块是pywin32的一部分,这可以很容易地实现。不幸的是,“服务”与“守护进程”的两种“思维模式”在细节上非常不同,尽管它们具有类似的用途,但我不知道任何Python外观尝试将它们统一成一个单一的框架。


啊,所以你实际上建议在Windows中采用服务路线;我认为那可能有些过度了...不过,我想Windows不支持后台进程(只有隐藏窗口)--对吗? - Nick Bolton
1
@Nick,确实——对于在Windows上使用Solidity,服务是最好的选择。这并不是过度设计——实际上,制作Windows服务非常简单,只是与制作Unix守护程序不同(除了框架不同之外,您的代码主体在这两种用例之间通常可以基本保持不变——Unix守护程序和Windows服务)。 - Alex Martelli

6
这个问题已经有6年了,但我遇到了同样的问题,现有的答案不足以跨平台使用。尽管Windows服务通常与Unix守护进程类似,但归根结底它们存在实质性差异,“细节决定成败”。长话短说,我开始寻找一些东西,可以在Unix和Windows上运行完全相同的应用程序代码,并在两个平台上尽可能地满足良好的Unix守护进程的期望(其他地方更好地解释)。请注意保留HTML标签。
  1. 关闭打开的文件描述符(通常是全部,但某些应用程序可能需要保护某些描述符以防止关闭)
  2. 将进程的工作目录更改为适当的位置,以防止“目录忙”错误
  3. 更改文件访问创建掩码(在Python世界中为os.umask
  4. 将应用程序移到后台并使其与启动进程分离
  5. 完全脱离终端,包括将STDINSTDOUTSTDERR重定向到不同的流(通常为DEVNULL),并防止重新获取控制终端
  6. 处理信号,特别是SIGTERM
跨平台守护进程的根本问题在于Windows作为操作系统并不真正支持守护进程的概念:从终端启动(或任何其他交互式上下文中启动,包括从资源管理器中启动等)的应用程序将继续在可见窗口中运行,除非控制应用程序(在此示例中为Python)已包含无窗口GUI。此外,Windows信号处理能力极低,并且尝试向独立的Python进程(而不是子进程,后者不会在终端关闭后继续存在)发送信号几乎总是会导致该Python进程立即退出而没有任何清理(没有finally:、没有atexit、没有__del__等)。

对我来说,Windows服务虽然在许多情况下是可行的替代方案,但基本上不是一个问题:它们不跨平台,并且需要进行代码修改。附带所有最近的Windows Python二进制文件的 无窗口版本Python pythonw.exe 更接近解决问题,但仍然不够完美:特别是在信号处理方面,它无法改善情况,而且您仍然不能轻松地从终端启动pythonw.exe应用程序并在启动期间与其交互(例如,为了向脚本传递动态启动参数,比如密码、文件路径等),“守护”之前。

最终,我选择使用subprocess.Popencreationflags=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
)

然而,这仍然让我面临着启动通信和信号处理的额外挑战。不详细展开前者,我的策略是:
  1. 在启动过程的名称空间中使用pickle保存重要的部分
  2. 将其存储在一个tempfile
  3. 在启动之前将该文件的路径添加到子进程的环境变量中
  4. 从"daemonization"函数中提取并返回名称空间
对于信号处理,我必须更有创意。在"守护进程"内部:
  1. 忽略守护进程中的信号,因为它们会立即终止进程且没有任何清理
  2. 创建一个新线程来管理信号处理
  3. 该线程启动子信号处理进程并等待它们完成
  4. 外部应用程序向子信号处理进程发送信号,导致它终止和完成
  5. 这些进程然后使用信号编号作为其返回代码
  6. 信号处理线程读取返回代码,然后调用用户定义的信号处理程序,或使用cytpes API在Python主线程中引发适当的异常
  7. 为新信号重复上述步骤

话虽如此,对于将来遇到这个问题的任何人,我已经编写了一个名为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()

4

我能想到的有两个选项:

  1. 将你的程序移植成Windows服务。你可以在两个实现之间共享大部分代码。

  2. 你的程序是否真正需要任何守护进程功能?如果不需要,那么将其重写为一个简单的后台运行服务器,通过套接字管理通信并执行任务。这可能会消耗比守护进程更多的系统资源,但是相对来说具有平台无关性。


2
一般来说,"daemon" 这个概念是Unix特有的,尤其是在与文件创建掩码、进程层次结构和信号处理方面的预期行为方面。
您可能会发现 PEP 3143 很有用,其中考虑了针对 Python 3.2 的 python-daemon 的提议继续,并讨论了许多 相关的守护进程模块和实现

0
它只支持Unix的原因是 daemons是一个Unix特有的概念,即由操作系统启动的后台进程,通常作为根PID的子进程运行。
Windows没有直接相当于Unix守护进程的东西,我能想到的最接近的是Windows服务。 有一个名为pythonservice.exe的程序适用于Windows。不确定它是否在所有版本的Python上都受支持。

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