Python创建子进程,分离并退出
我在想,这样执行一个系统进程并让它和父进程分开,是否是正确的做法。这样做可以让父进程退出时,不会产生僵尸进程,也不会杀掉子进程。我现在使用的是subprocess模块,代码是这样的...
os.setsid()
os.umask(0)
p = subprocess.Popen(['nc', '-l', '8888'],
cwd=self.home,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
os.setsid()这个函数可以改变进程组,我认为这就是让进程在父进程退出后还能继续运行的原因,因为它不再属于同一个进程组。
这样做对吗?这也是一种可靠的方法吗?
基本上,我有一个远程控制工具,它通过套接字进行通信,可以远程启动进程。但我必须确保,如果远程控制工具崩溃了,它启动的进程仍然能继续运行,不受影响。
我在阅读关于双重分叉的内容,但不确定这是否必要,或者subprocess.POpen的close_fds是否能处理这个问题,是否只需要改变进程组就可以了?
谢谢。
Ilya
3 个回答
在Windows上的解决方法:
proc_exe = subprocess.Popen(<Your executable path>, shell=True)
proc_exe.send_signal(subprocess.signal.SIGTERM)
当你使用subprocess.Popen()时,默认情况下,子进程的父进程就是调用它的那个进程。通常这是个好事,但在某些情况下,父进程的资源会被锁住,直到子进程结束。这个时候,可以用subprocess.send_signal()方法来解决问题。运行这个命令后,子进程就会和父进程断开连接,独立运行。
确保在调用子进程时使用“shell=True”选项。
因为send_signal()方法只会结束“CMD.exe”(父进程),而不是实际的调用者。
popen
在 Unix 系统上是通过 fork
来实现的。这意味着你可以安全地做以下两件事:
- 在你的父进程中运行
Popen
- 然后立即退出父进程
当父进程退出后,子进程会被 init
进程(在 OSX 上是 launchd
)接管,并且会继续在后台运行。
你程序的前两行其实是可以省略的,下面的代码就可以正常工作:
import subprocess
p = subprocess.Popen(['nc', '-l', '8888'],
cwd="/",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
我在阅读关于双重 fork 的内容,但不确定这是否必要。
如果你的父进程还在运行,而你又想保护子进程不跟着父进程一起结束,那就需要双重 fork。这个回答展示了如何实现。
双重 fork 的工作原理:
- 通过
os.fork()
创建一个子进程 - 在这个子进程中调用
Popen()
启动一个长时间运行的进程 - 子进程退出:
Popen
进程会被init
接管并在后台运行
为什么父进程必须立即退出?如果不立即退出会发生什么?
如果你让父进程继续运行,而用户通过 ctrl-C
(SIGINT
)或 ctrl-\
(SIGQUIT
)停止进程,那么父进程和 Popen
进程都会被杀掉。
如果它在 fork 后一秒才退出呢?
那么在这1秒内,你的 Popen
进程就会受到 ctrl-C
等信号的影响。如果你想确保万无一失,就需要使用双重 fork。
对于Python 3.8.x,处理方式稍微有点不同。可以使用从Python 3.2开始就有的start_new_session
参数:
import shlex
import subprocess
cmd = "<full filepath plus arguments of child process>"
cmds = shlex.split(cmd)
p = subprocess.Popen(cmds, start_new_session=True)
这样做可以让父进程退出,而子进程继续运行。不过关于僵尸进程的情况就不太确定了。
start_new_session
参数在所有POSIX系统上都支持,也就是说在Linux、MacOS等系统上都可以用。
这个是在macOS 10.15.5上测试的Python 3.8.1版本。