从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()
将子进程放入自己的进程组中。 Popen
的 preexec_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
" 似乎有一个粗略的等效项,但我从未尝试过。)
【讨论】:
感谢您的回答;这个对我有用!但是,我很好奇,如果我省略了stdout
和 stderr
参数,为什么我的命令会在某个时间点停止(进程终止)。
可能 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运行程序,并在脚本被杀死后继续运行的主要内容,如果未能解决你的问题,请参考以下文章