如何在Python中启动进程并将其放到后台?

6 投票
1 回答
2320 浏览
提问于 2025-05-10 15:09

我现在正在写我的第一个Python程序(使用的是Python 2.6.6)。这个程序的功能是帮助启动和停止在服务器上运行的不同应用程序,提供给用户一些常用的命令(就像在Linux服务器上启动和停止系统服务一样)。

我通过以下方式启动应用程序的启动脚本:

p = subprocess.Popen(startCommand, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate()
print(output)

问题是,有一个应用程序的启动脚本会一直在前台运行,因此p.communicate()会一直等待,永远不会结束。我已经尝试在startCommand前面加上"nohup startCommand &",但效果并没有我预期的那么好。

作为解决办法,我现在使用以下的bash脚本来调用应用程序的启动脚本:

#!/bin/bash

LOGFILE="/opt/scripts/bin/logs/SomeServerApplicationStart.log"

nohup /opt/someDir/startSomeServerApplication.sh >${LOGFILE} 2>&1 &

STARTUPOK=$(tail -1 ${LOGFILE} | grep "Server started in RUNNING mode" | wc -l)
COUNTER=0

while [ $STARTUPOK -ne 1 ] && [ $COUNTER -lt 100 ]; do
   STARTUPOK=$(tail -1 logs/SomeServerApplicationStart.log | grep "Server started in RUNNING mode" | wc -l)
   if (( STARTUPOK )); then
      echo "STARTUP OK"
      exit 0
   fi
   sleep 1
   COUNTER=$(( $COUNTER + 1 ))
done

echo "STARTUP FAILED"

这个bash脚本是从我的Python代码中调用的。这个解决办法效果很好,但我更希望能全部用Python来完成……

使用subprocess.Popen是不是不对的做法?我该如何仅用Python来完成我的任务呢?

相关文章:

  • 暂无相关问题
暂无标签

1 个回答

2

首先,如果你不想让Python脚本在执行命令时被阻塞,其实很简单,就是不要调用`communicate`这个方法!你可以直接从命令的输出或错误输出中读取信息,直到找到你需要的消息,然后就可以不再关注这个命令了。

# to avoid waiting for an EOF on a pipe ...
def getlines(fd):
    line = bytearray()
    c = None
    while True:
        c = fd.read(1)
        if c is None:
            return
        line += c
        if c == '\n':
            yield str(line)
            del line[:]

p = subprocess.Popen(startCommand, shell=True, stdout=subprocess.PIPE,
               stderr=subprocess.STDOUT) # send stderr to stdout, same as 2>&1 for bash
for line in getlines(p.stdout):
    if "Server started in RUNNING mode" in line:
        print("STARTUP OK")
        break
else:    # end of input without getting startup message
     print("STARTUP FAILED")
     p.poll()    # get status from child to avoid a zombie
     # other error processing

不过,上面这种做法有个问题,就是服务器仍然是Python进程的一个子进程,可能会收到一些不想要的信号,比如SIGHUP。如果你想把它变成一个守护进程(也就是在后台运行的进程),你需要先启动一个子进程,然后再在这个子进程中启动你的服务器。这样,当第一个子进程结束时,调用者可以等待它,而服务器的父进程ID会变成1(被init进程接管)。你可以使用`multiprocessing`模块来简化这个过程。

代码可能是这样的:

import multiprocessing
import subprocess

# to avoid waiting for an EOF on a pipe ...
def getlines(fd):
    line = bytearray()
    c = None
    while True:
        c = fd.read(1)
        if c is None:
            return
        line += c
        if c == '\n':
            yield str(line)
            del line[:]

def start_child(cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                         shell=True)
    for line in getlines(p.stdout):
        print line
        if "Server started in RUNNING mode" in line:
            print "STARTUP OK"
            break
    else:
        print "STARTUP FAILED"

def main():
    # other stuff in program
    p = multiprocessing.Process(target = start_child, args = (server_program,))
    p.start()
    p.join()
    print "DONE"
    # other stuff in program

# protect program startup for multiprocessing module
if __name__ == '__main__':
    main()

有人可能会问,既然文件对象本身就是一个可以逐行返回内容的迭代器,那为什么还需要`getlines`这个生成器呢?问题在于,`getlines`内部调用了`read`方法,而这个方法会一直读取到文件结束(EOF),特别是当文件没有连接到终端时。而现在它连接到了一个管道(PIPE),所以在服务器结束之前,你是不会得到任何输出的……这并不是我们所期待的结果。

撰写回答