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


295

我正在尝试将Shell脚本移植到可读性更高的python版本。原始的shell脚本在后台使用“&”启动多个进程(实用程序,监视器等)。如何在python中达到相同的效果?我希望这些过程在Python脚本完成后不会消失。我敢肯定它与守护程序的概念有关,但是我找不到如何轻松实现此目的。


1
真正重复的问题是如何在后台启动和运行外部脚本?。干杯;)
olibre

1
嗨Artem。请接受Dan的回答,因为(1)更多投票,(2)subprocess.Popen()是自2010年以来的新推荐方式(我们现在是2015年),并且(3)此处重复的重复问题也已经接受了有关的答案subprocess.Popen()。干杯:-)
olibre 2015年

1
@olibre实际上答案应该是subprocess.Popen("<command>")由适当的shebang领导的<command>文件。使用bash和python脚本对我(Debian)非常适合,隐式地shells并在其父进程中幸存下来。stdout与父母去同一个终端。因此,这很像&在OP要求的外壳中工作。但是,地狱,所有问题都变得非常复杂,而一点点测试却很快表明了这一点;)
flaschbier 2015年

对于背景,也许也请参见stackoverflow.com/a/51950538/874188
Tripleee

Answers:


84

注意:此答案的最新版本比2009年发布时要少。subprocess现在建议在文档中使用其他答案中显示的模块

(请注意,子流程模块提供了更强大的工具来生成新流程并检索其结果;使用该模块比使用这些功能更可取。)


如果您希望您的进程在后台启动,则可以使用system()与您的Shell脚本相同的方式来使用和调用它,也可以spawn

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

(或者,您也可以尝试使用便携性较差的os.P_NOWAIT标志)。

请参阅此处文档


8
备注:您必须指定可执行文件的完整路径。此函数将不使用PATH变量,并且使用该变量的变体在Windows下不可用。
sorin

36
直线为我崩溃了python。
pablo

29
os.P_DETACH已被os.P_NOWAIT取代。
自己2012年

7
建议使用此方法的人员可以subprocess给我们提示如何与之分离流程subprocess吗?
rakslice 2014年

3
如何使用Python脚本(例如attach.py​​)查找后台进程并重定向其IO,以便attach.py​​可以在后台读取/写入some_long_running_prog?
raof01

376

尽管jkp的解决方案有效,但是更新的方式(以及文档建议的方式)是使用subprocess模块。对于简单的命令,它等效,但是如果您要执行复杂的操作,它提供了更多选项。

您的案例示例:

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

这将rm -r somefile在后台运行。请注意,调用.communicate()从返回的对象Popen将一直阻塞,直到完成为止,因此,如果要使其在后台运行,请不要这样做:

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

请参阅此处的文档。

另外,有一点需要澄清:这里使用的“背景”纯粹是一个外壳概念;从技术上讲,您的意思是希望在等待进程完成时生成一个没有阻塞的进程。但是,我在这里使用“背景”来指代类似外壳背景的行为。


7
@丹:一旦进程在后台运行,我该如何终止进程?我想运行它一段时间(这是一个与我交互的守护进程),并在完成后将其杀死。该文档也没有什么帮助...
胡安

1
@Dan,但我不需要知道它的PID吗?活动监视器/任务管理器不是一个选项(需要以编程方式进行)。
2014年

5
好的,当需要Popen()的结果写入标准输入时,如何将进程强制为后台?
迈克尔

2
@JFSebastian:我将其解释为“如何创建一个不会停止当前程序执行的独立进程”。您如何建议我对其进行编辑以使其更明确?
2014年

1
@Dan:正确的答案是:用于Popen()避免阻塞主线程,如果需要守护程序,请查看python-daemon软件包以了解定义良好的守护程序的行为。如果删除所有以“但要警惕”开头的内容,除了指向子流程文档的链接之外,您的答案都是可以的。
jfs

47

您可能需要答案“如何在Python中调用外部命令”

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

import os
os.system("some_command &")

基本上,传递给system函数的所有内容都将与将其传递给脚本中的shell一样执行。


9
恕我直言,python脚本通常被编写为跨平台的,如果存在简单的跨平台解决方案,则最好坚持使用。永远不知道将来是否需要使用另一个平台:)或者是否有人希望将您的脚本迁移到他的平台。
d9k'5

7
该命令是同步的(即,它总是等待启动进程的终止)。
tav

@ d9k是os.system无法移植吗?
lucid_dreamer

1
@ d9k不是在已经将您定位在posix-land的背景中运行某些内容的选择吗?您将在Windows上做什么?作为服务运行?
lucid_dreamer

如果需要从特定文件夹运行命令,该如何使用?
mrRobot

29

我在这里找到这个:

在Windows(win xp)上,父进程longtask.py只有在完成工作后才能完成。这不是您想要的CGI脚本。问题并非特定于Python,在PHP社区中,问题是相同的。

解决方案是将DETACHED_PROCESS 过程创建标志传递给CreateProcesswin API中的基础函数。如果碰巧安装了pywin32,则可以从win32process模块​​中导入该标志,否则,您应该自己定义它:

DETACHED_PROCESS = 0x00000008

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

6
+1用于显示如何保留进程ID。如果有人想以后用进程ID
终止

1
这似乎仅
适用

24

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'

1
在Windows中,它不会分离,但是使用creationflags参数有效
SmartManoj

3
该解决方案在Linux上将子进程保留为Zombie。
TitanFighter

:@TitanFighter这可以切忌集SIGCHLD SIG_IGN stackoverflow.com/questions/16807603/...
sailfish009

谢谢@吉米,您的答案是唯一的解决方案适合我。
sailfish009

12

您可能想开始研究os模块以派生不同的线程(通过打开交互式会话并发出help(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)

2
os.fork()确实很有用,但是它的缺点是只能在* nix上使用。
Evan Fosmark 09年

os.fork的唯一问题是它特定于Win32。
jkp

有关此方法的更多详细信息:以Python方式创建守护程序
Amir Ali Akbari 2014年

您也可以通过以下方法达到类似的效果threadingstackoverflow.com/a/53751896/895245我认为这可能在Windows上有效。
西罗Santilli郝海东冠状病六四事件法轮功

9

捕获输出并在后台运行 threading

本答案所述,如果您使用捕获输出,stdout=然后尝试进行read(),则该过程将阻塞。

但是,在某些情况下您需要这样做。例如,我想启动两个进程,它们通过它们之间的端口进行通信,并将它们的stdout保存到日志文件和stdout中。

threading模块使我们能够做到这一点。

首先,看看如何在此问题中单独完成输出重定向:Python Popen:同时写入stdout和日志文件

然后:

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

标准输出每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上测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.