从Python守护进程中正确启动线程的方法

1 投票
2 回答
1128 浏览
提问于 2025-04-18 11:08

我需要写一个简单的守护进程,并且要有网页界面。

我的想法是使用python-daemon这个包,然后在一个线程里运行wsgiref.simple_server

守护进程用下面的代码运行得很好:

import daemon
import logging
import time
import signal
import threading

logfilename = '/var/log/testdaemon.log'
logger = logging.getLogger("DaemonLog")
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
    '%(asctime)s:%(levelname)s:%(message)s',
    '%Y-%m-%d %H:%M:%S')
handler = logging.FileHandler(logfilename)
handler.setFormatter(formatter)
logger.addHandler(handler)

def initial_program_setup():
    logger.info('daemon started')

def do_main_program():
    while True:
        time.sleep(1)
        logger.info('another second passed')

def program_cleanup(signum, frame):
    logger.info('daemon stops')
    context.terminate(signum, frame)

def reload_program_config(signum, frame):
    logger.info('reloading config')

context = daemon.DaemonContext()

context.signal_map = {
    signal.SIGTERM: program_cleanup,
    signal.SIGHUP: 'terminate',
    signal.SIGUSR1: reload_program_config,
    }

context.files_preserve = [handler.stream]

initial_program_setup()

with context:
    do_main_program()

但是如果我在initial_program_setup()里像这样启动一个线程:

def web_gui():
    logger.info('weg gui started')

web = threading.Thread(target=web_gui)
web.setDaemon(True)

def initial_program_setup():
    logger.info('daemon started')
    web.start()

看起来守护进程在线程完成后就退出了。加上类似于

while True:
    time.sleep(1)

的代码到web_gui()(让线程一直运行,就像网页服务器应该那样)反而更糟:甚至连web gui started这行日志都没有显示出来。

我有几个问题:

  1. 为什么这样不行?在守护进程里正确启动线程的方法是什么?
  2. 也许通过网页界面控制守护进程有更好的方法?按照这样的架构,我觉得我应该为每个界面页面启动一个新线程,这样很难扩展。

谢谢。

2 个回答

0

对我来说,这个方法有效。你可以把设置守护进程的环境想象成一种前额叶切除手术 :-)

这是我觉得比较简单易懂的步骤。我的主函数只负责解析参数,如果被标记为在后台运行,就会创建守护进程的环境。之后,它会执行一个通用的主循环函数_main_common(),在这里会完成所有的工作,包括创建所有的线程:

args = parse_arguments(args)
if args.foreground:
   # we run in the foreground, so just run the main loop
   _main_common()
else:
   # we run in the background, so now create the daemon context and run the main loop
   context = create_daemon_context(context, detach_process=True)
   with context:
      _main_common()
1

这是一个关于守护进程库的限制(讨论线程从这里开始)。

简单来说,你有两个选择:

  • 不要使用守护进程,因为在这方面它是无法修复的。
  • 在“with daemoncontext”块内启动线程。

详细说明:

当守护进程库切换到守护进程上下文时,它会进行双重分叉。这意味着它首先会创建一个子进程,然后杀掉父进程。新的子进程没有任何线程,所以一旦父进程退出,就相当于杀掉了你的网页界面线程。最终,解决这个问题的任何方法都必须在新创建的子进程中启动任何永久线程。在守护进程上下文中这样做的缺点是,你将无法将潜在的错误反馈给用户。理想情况下,你应该进行双重分叉,但不退出父进程,然后设置你的守护进程,在进入主循环之前让父进程退出。但是,使用守护进程库或任何实现PEP3143草案的库都无法做到这一点。

撰写回答