从python运行程序,并在脚本被杀死后继续运行

Posted

技术标签:

【中文标题】从python运行程序,并在脚本被杀死后继续运行【英文标题】:Run a program from python, and have it continue to run after the script is killed 【发布时间】:2011-08-26 01:21:46 【问题描述】:

我试过这样运行:

subprocess.Popen(['nohup', 'my_command'],
                 stdout=open('/dev/null', 'w'),
                 stderr=open('logfile.log', 'a'))

如果父脚本正常退出,则此方法有效,但如果我终止脚本 (Ctrl-C),我的所有子进程也会被终止。有没有办法避免这种情况?

我关心的平台是 OS X 和 Linux,使用 Python 2.6 Python 2.7。

【问题讨论】:

【参考方案1】:

在 Unix 系统上执行此操作的通常方法是,如果您是父级,则分叉并退出。看看os.fork()

这是一个完成这项工作的函数:

def spawnDaemon(func):
    # do the UNIX double-fork magic, see Stevens' "Advanced 
    # Programming in the UNIX Environment" for details (ISBN 0201563177)
    try: 
        pid = os.fork() 
        if pid > 0:
            # parent process, return and keep running
            return
    except OSError, e:
        print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) 
        sys.exit(1)

    os.setsid()

    # do second fork
    try: 
        pid = os.fork() 
        if pid > 0:
            # exit from second parent
            sys.exit(0) 
    except OSError, e: 
        print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) 
        sys.exit(1)

    # do stuff
    func()

    # all done
    os._exit(os.EX_OK)

【讨论】:

如果我分叉,然后我杀死一半分叉(而不是让它退出),那会杀死新进程吗? 好的,进一步阅读:这需要分叉两次以避免接收信号?我非常希望父进程保持交互 --- 它的工作是监视它产生的进程 --- 如果它必须放弃 shell,这是不可能的。 谢谢!我已将我的实现添加到您的答案中。 这很棒,因为它将守护进程 父进程 ID 设置为 1,以便它与父进程完全断开连接。我从另一个答案运行的子进程命令被我的 Torque 作业调度程序杀死,即使在更改其进程组时也是如此,因为父进程 ID 仍然与垂死的进程匹配。 在这个实现中,中间的孩子被留下作为僵尸,直到父母存在。您需要在父进程中收集其返回代码以避免这种情况,例如通过调用os.waitid(os.P_PID, pid, os.WEXITED)(在主进程中返回之前)【参考方案2】:

子进程收到与父进程相同的SIGINT,因为它在同一个进程组中。您可以通过在子进程中调用os.setpgrp() 将子进程放入自己的进程组中。 Popenpreexec_fn 参数在这里很有用:

subprocess.Popen(['nohup', 'my_command'],
                 stdout=open('/dev/null', 'w'),
                 stderr=open('logfile.log', 'a'),
                 preexec_fn=os.setpgrp
                 )

preexec_fn 仅适用于 un*x-oids。Windows "creationflags=CREATE_NEW_PROCESS_GROUP" 似乎有一个粗略的等效项,但我从未尝试过。)

【讨论】:

感谢您的回答;这个对我有用!但是,我很好奇,如果我省略了 stdoutstderr 参数,为什么我的命令会在某个时间点停止(进程终止)。 可能 stdout 和 stderr 缓冲区已满,进程陷入死锁? 如果您使用的是shell=True,那么creationflags=subprocess.CREATE_NEW_CONSOLE 可能就是您想要的 如果您致电setpgrp,是否需要nohup?后者不会阻止孩子从父母那里获得SIGHUP,因为它不再属于同一个进程组吗? 我不清楚这些open 何时关闭——如果有的话。对我来说,这至少是隐含的行为,我会将它们与 with 捆绑在一起,就像写成 below 一样。【参考方案3】:
with open('/dev/null', 'w') as stdout, open('logfile.log', 'a') as stderr:
    subprocess.Popen(['my', 'command'], stdout=stdout, stderr=stderr)

类 subprocess.Popen(...)

在新进程中执行子程序。 在 POSIX 上,该类使用类似 os.execvp() 的行为来执行 儿童节目。在 Windows 上,该类使用 Windows CreateProcess() 功能。

os.execvpe(file, args, env)

这些函数都执行一个新程序,替换当前的 过程;他们不回来。在 Unix 上,新的可执行文件被加载 进入当前进程,并且将具有与当前进程相同的进程 ID 呼叫者。错误将被报告为 OSError 异常。

【讨论】:

【参考方案4】:

经过一个小时的各种尝试,这对我有用:

process = subprocess.Popen(["someprocess"], creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP)

这是windows的解决方案。

【讨论】:

这适用于 2021 年的 Windows。谢谢!【参考方案5】:

从 3.2 开始,您还可以使用 start_new_session 标志(仅限 POSIX)。

import subprocess

p = subprocess.Popen(["sleep", "60"], start_new_session=True)
ret = p.wait()

见start_new_session in Popen constructor

【讨论】:

可以,但是注意p的父进程还是调用进程。当然,OP 不想p.wait()。如果 p 失败了,它仍然有调用进程作为它的父进程,那么它将成为一个僵尸进程。

以上是关于从python运行程序,并在脚本被杀死后继续运行的主要内容,如果未能解决你的问题,请参考以下文章

我杀死应用程序并快速重新运行后,徽章不计入

从另一个环境调用 Perl 在后台

python 脚本被意外打断之后(比如开网页但是断网了)如何从当前工作现场继续运行?

如果 Python 程序被杀死,则自动重新启动它

在终端窗口中停止 logcat 的脚本

在 Python 中,如何执行 .exe 文件并在 n 秒后停止它? [复制]