我想创建一个父进程,它将创建许多子进程。由于父进程负责创建子进程,父进程不会关心子进程的状态。
由于subprocess.call是阻塞的,它不起作用。因此,我使用subprocess.Popen替代call。然而,一旦子进程终止,Popen将生成僵尸(defunct)进程(链接)。
有没有办法解决这个问题?
我想创建一个父进程,它将创建许多子进程。由于父进程负责创建子进程,父进程不会关心子进程的状态。
由于subprocess.call是阻塞的,它不起作用。因此,我使用subprocess.Popen替代call。然而,一旦子进程终止,Popen将生成僵尸(defunct)进程(链接)。
有没有办法解决这个问题?
有很多方法可以处理这个问题。关键点在于僵尸/"defunct"进程存在是为了让父进程收集它们的状态。
作为进程的创建者,你可以宣布忽略该状态。 POSIX方法是设置标志SA_NOCLDWAIT
(使用sigaction
)。在Python中执行此操作有些麻烦;但是大多数类Unix系统允许简单地忽略SIGCHLD
/ SIGCLD
(不同的类Unix系统拼写有所不同),在Python中很容易实现:
import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
或者,如果出于某些原因无法使用此方法或在您的系统上无法正常工作,则可以使用一个老套路:不要只fork一次,而是fork两次。在第一个子进程中,fork第二个子进程;在第二个子进程中,使用execve
(或类似)运行所需的程序;然后在第一个子进程中,退出(使用_exit
)。在原始父进程中,使用wait
或waidpid
或任何操作系统提供的方法,并收集第一个子进程的状态。
之所以会起作用,是因为第二个子进程现在已成为“孤儿”(其父进程,第一个子进程已经死亡并被你的原始进程收集)。作为孤儿,它被移交给一个代理父进程(具体而言是“init”),后者总是wait
-ing,并立即收集所有僵尸进程。
除了双重fork之外,您还可以使子进程生活在自己单独的会话中和/或放弃控制终端访问权限(在Unix-y术语中称为“daemonize”)。 (这有些混乱且依赖于操作系统;我以前编写过这样的代码,但对于我现在无法访问的某些公司代码。)
最后,你可以定期收集这些进程。如果你使用的是subprocess
模块,只需在方便的时候调用.poll
函数。如果进程仍在运行,则返回None
,如果它已经完成,则返回退出状态(已收集)。如果有一些进程仍在运行,你的主程序仍然可以退出,而它们继续运行。此时,它们就成为孤儿进程,如上述方法#2。
"忽略SIGCHLD"方法简单易行,但缺点是会干扰创建和等待子进程的库例程。在Python 2.7及更高版本中有一个解决方法(http://bugs.python.org/issue15756),但这意味着库例程无法看到这些子进程的任何失败。
[编辑:http://bugs.python.org/issue1731717是关于p.wait()
的,其中p
是从subprocess.Popen
获得的进程;15756是特别针对p.poll()
的;但无论如何,如果你没有修复程序,你必须采取方法2、3或4。]
OSError: [Errno 10] No child processes
。 - Winstonsubprocess
的call
由Popen
和wait
组成,您已告诉操作系统丢弃子状态,因此底层的os.wait
失败并显示errno.ECHILD
。(解决方法只是将其视为“子进程退出代码= 0”) - toreksignal.signal(signal.SIGCHLD, signal.SIG_IGN)
可以工作,但在使用 multiprocessing
等模块时可能不安全。它的使用并不特定于某个子进程。 - Asclepius在终止或杀死进程后,操作系统会等待父进程收集子进程的状态。您可以使用进程的communicate()方法来收集状态:
p = subprocess.Popen( ... )
p.terminate()
p.communicate()
torek的方法可以!
我发现另一种处理僵尸进程的方式;
我们可以使用waitpid根据需要回收僵尸进程:
import os, subprocess, time
def recycle_pid():
while True:
try:
pid, status, _ = os.wait3(os.WNOHANG)
if pid == 0:
break
print("----- child %d terminated with status: %d" %(pid, status))
except OSError,e:
break
print("+++++ start pid:", subprocess.Popen("ls").pid)
recycle_pid()
print("+++++ start pid:", subprocess.Popen("ls").pid)
recycle_pid()
time.sleep(1)
recycle_pid()
recycle_pid是非阻塞的,可以根据需要随时调用。