如何在Python中启动后台进程?

我正在尝试将shell脚本移植到可读性更强的python版本。原来的shell脚本使用“&”在后台启动几个进程(实用程序、监视器等)。如何在python中实现相同的效果?我希望这些进程在python脚本完成时不死亡。我确信这与守护进程的概念有关,但我不知道如何轻松地做到这一点。

584290 次浏览

你可能想要如何在Python中调用外部命令的答案。

最简单的方法是使用os.system函数,例如:

import os
os.system("some_command &")

基本上,无论你传递给system函数的是什么,都会像你在脚本中传递给shell一样执行。

请注意:这个答案没有2009年发布的时候那么流行。使用其他答案中显示的subprocess模块现在建议使用在文档中

(注意,subprocess模块为生成新进程和检索其结果提供了更强大的功能;使用该模块比使用这些函数更可取。)


如果你想让你的进程在后台启动,你可以使用system()并像你的shell脚本一样调用它,或者你可以spawn它:

import os
os.spawnl(os.P_DETACH, 'some_long_running_command')

(或者,您可以尝试较难移植的os.P_NOWAIT标志)。

参见文件在这里

您可能想要开始研究os模块以派生不同的线程(通过打开交互式会话并发出帮助)。相关的函数是fork和任何一个exec函数。为了给你一个如何开始的想法,在一个执行fork的函数中放入这样的东西(函数需要接受一个列表或元组'args'作为参数,其中包含程序的名称和参数;你可能还想为新线程定义stdin, out和err):

try:
pid = os.fork()
except OSError, e:
## some debug output
sys.exit(1)
if pid == 0:
## eventually use os.putenv(..) to set environment variables
## os.execv strips of args[0] for the arguments
os.execv(args[0], args)

虽然jkp的解决方案是可行的,但较新的做事方式(以及文档推荐的方式)是使用subprocess模块。对于简单的命令,它是等效的,但是如果您想执行一些复杂的操作,它提供了更多的选项。

举个例子:

import subprocess
subprocess.Popen(["rm","-r","some.file"])

这将在后台运行rm -r some.file。注意,在Popen返回的对象上调用.communicate()将阻塞直到它完成,所以如果你想让它在后台运行,就不要这样做:

import subprocess
ls_output=subprocess.Popen(["sleep", "30"])
ls_output.communicate()  # Will block for 30 seconds

参见文档在这里

另外,需要澄清的一点是:你在这里使用的“Background”纯粹是一个shell概念;从技术上讲,您的意思是希望在等待进程完成时不阻塞地生成进程。但是,我在这里使用的“background”指的是类似shell-background的行为。

我发现这个在这里:

在windows (winxp)上,父进程直到longtask.py完成它的工作才会结束。这不是你在cgi脚本中想要的。这个问题并不局限于Python,在PHP社区也存在同样的问题。

解决方案是将DETACHED_PROCESS 进程创建标志传递给win API中的底层CreateProcess函数。如果你碰巧安装了pywin32,你可以从win32process模块导入这个标志,否则你应该自己定义它:

DETACHED_PROCESS = 0x00000008


pid = subprocess.Popen([sys.executable, "longtask.py"],
creationflags=DETACHED_PROCESS).pid

使用subprocess.Popen()close_fds=True参数,这将允许派生的子进程与Python进程本身分离,并在Python退出后继续运行。

https://gist.github.com/yinjimmy/d6ad0742d03d54518e9f

import os, time, sys, subprocess


if len(sys.argv) == 2:
time.sleep(5)
print 'track end'
if sys.platform == 'darwin':
subprocess.Popen(['say', 'hello'])
else:
print 'main begin'
subprocess.Popen(['python', os.path.realpath(__file__), '0'], close_fds=True)
print 'main end'

两者都捕获输出并在后台使用threading运行

正如前面提到的关于这个答案,如果你用stdout=捕获输出,然后尝试read(),那么进程会阻塞。

然而,在某些情况下您需要这样做。例如,我想启动两个进程之间通过端口进行通信,并将他们的标准输出保存到日志文件和标准输出。

threading模块允许我们这样做。

首先,看看如何单独执行这个问题中的输出重定向部分:Python Popen:同时写入标准输出和日志文件

然后:

main.py

#!/usr/bin/env python3


import os
import subprocess
import sys
import threading


def output_reader(proc, file):
while True:
byte = proc.stdout.read(1)
if byte:
sys.stdout.buffer.write(byte)
sys.stdout.flush()
file.buffer.write(byte)
else:
break


with subprocess.Popen(['./sleep.py', '0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc1, \
subprocess.Popen(['./sleep.py', '10'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc2, \
open('log1.log', 'w') as file1, \
open('log2.log', 'w') as file2:
t1 = threading.Thread(target=output_reader, args=(proc1, file1))
t2 = threading.Thread(target=output_reader, args=(proc2, file2))
t1.start()
t2.start()
t1.join()
t2.join()

sleep.py

#!/usr/bin/env python3


import sys
import time


for i in range(4):
print(i + int(sys.argv[1]))
sys.stdout.flush()
time.sleep(0.5)

在运行:

./main.py

Stdout每0.5秒更新一次,每两行包含:

0
10
1
11
2
12
3
13

每个日志文件都包含给定进程的日志。

灵感来源:https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/

在Ubuntu 18.04, Python 3.6.7上测试。

你可以使用

import os
pid = os.fork()
if pid == 0:
Continue to other code ...

这将使python进程在后台运行。

我还没有尝试过这个,但使用.pyw文件而不是.py文件应该有帮助。Pyw文件没有控制台,所以理论上它不应该像后台进程一样出现和工作。