从Python运行程序,并在脚本被终止后继续运行

48

我尝试运行以下代码:

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。

5个回答

65

子进程接收相同的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 只适用于类 Unix 操作系统。 对于 Windows,似乎有一个类似的选项 "creationflags=CREATE_NEW_PROCESS_GROUP",但我从未尝试过。)


谢谢你的回答,它对我有用!然而,如果我省略stdoutstderr参数,为什么我的命令会在某个时候停止(进程死亡),我很好奇。 - danuker
1
也许标准输出和标准错误缓冲区已满,导致进程死锁? - Tom
1
如果您正在使用 shell=True,那么 creationflags=subprocess.CREATE_NEW_CONSOLE 可能是您想要的。 - Pro Q
2
如果调用setpgrp,是否需要使用nohup?后者不会使子进程无法从父进程接收到SIGHUP信号吗,因为它已经不再是同一进程组的一部分了? - rcorre
我不清楚这些“open”何时被关闭,如果有的话。对我来说,这至少是隐式行为,我会像下面写的那样将它们与“with”捆绑在一起。 - Suuuehgi

32

在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)

如果我进行分叉,然后杀死其中一半的分叉(而不是让它退出),那么这会杀死新进程吗? - James
1
好的,经过进一步阅读:这需要两次分叉以避免接收信号?我希望父进程保持交互性 --- 它的工作是监视它生成的进程 --- 如果它必须放弃 shell,则不可能实现。 - James
谢谢!我已经将我的实现添加到你的答案中。 - James
1
这很棒,因为它将守护进程的父进程ID设置为1,使其与父进程完全断开连接。我从另一个答案运行的子进程命令被我的Torque作业调度程序杀死了,即使更改了其进程组,因为父进程ID仍然匹配正在关闭的进程。 - storm_m2138
1
在这个实现中,一个中间子进程会一直保持为僵尸状态,直到父进程退出。你需要在父进程中收集它的返回码,以避免这种情况,例如通过调用 os.waitid(os.P_PID, pid, os.WEXITED)(在主进程返回之前)。 - nirvana-msu
有没有办法将子进程的PID传递给父线程?即使通过锁定/延迟的方式?当然,我可以使用os.getpid()打印PID,但这并不意味着生成它的父进程能够知道该PID。 - undefined

16

尝试了一个小时后,以下方法对我有效:

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

这是一个Windows的解决方案。


1
这在2021年的Windows上可以运行。谢谢! - Bumsik Kim

4
自3.2版本起,您还可以使用start_new_session标志(仅限POSIX)。
import subprocess

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

请参见Popen构造函数中的start_new_session


1
是的,但请注意,p 的父进程仍然是调用进程。当然,OP 不想 p.wait()。如果 p 失败并且它仍然将调用进程作为其父进程,则它将成为僵尸进程。 - Wolfgang Kuehn

-1
with open('/dev/null', 'w') as stdout, open('logfile.log', 'a') as stderr:
    subprocess.Popen(['my', 'command'], stdout=stdout, stderr=stderr)

在新进程中执行子程序。 在 POSIX 上,该类使用 os.execvp() 类似的行为来执行子程序。在 Windows 上,该类使用 Windows CreateProcess() 函数。

os.execvpe(file, args, env)

这些函数都执行一个新程序,替换当前进程;它们不返回。在 Unix 上,新可执行文件将加载到当前进程中,并具有与调用者相同的进程 ID。错误将报告为 OSError 异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接