【问题标题】:Python: Non-Blocking + Non defunct processPython:非阻塞+非失效进程
【发布时间】:2013-05-24 08:13:03
【问题描述】:

我想创建一个父进程,它会创建许多子进程。由于父进程负责创建子进程,因此父进程不会关心子进程的状态。

由于 subprocess.call 是阻塞的,它不起作用。因此我使用 subprocess.Popen 来替换调用。然而,一旦孩子终止(Link),Popen 将生成僵尸(已失效)进程。

有没有办法解决这个问题?

提前致谢

【问题讨论】:

  • 两件事,在我看来,你在混淆父母和孩子。还有一个问题,你为什么要创造僵尸?
  • 我不想要僵尸。这只是我想避免的 Popen 的副作用。
  • 所以你想生成 N 个进程,让它们并行执行它们的东西,然后阻塞父进程直到它们全部完成?
  • 实际上父母只是创建孩子。孩子随时会死,父母不在乎。所以它没有阻塞。
  • 但这还是太不具体了。什么是父进程?孩子们跑的时候它会继续跑吗?或者它只是一个产生 N 个进程,然后退出并让子进程运行的实用程序?如果是后者,请查看stackoverflow.com/questions/5772873/…

标签: python subprocess


【解决方案1】:

请看http://docs.python.org/2/library/multiprocessing.html

它提供了一个与线程非常相似的 API。如果需要,您可以等待子进程退出。

【讨论】:

  • 我有明显的印象,他不想想等孩子们离开。
  • 看来他想启动一堆可以用Process.start()来完成的进程。此调用是非阻塞的。亲子关系仍然保持,因此他可以对孩子采取任何他想采取的行动。父级将并行运行。
  • 感谢您的回复。实际上子进程是一个shell命令,不能被多进程执行。有没有其他选择?
  • 你可以这样做:1。创建 N 个进程对象 2。对所有对象 3 调用 start()。然后对象调用 subprocess.call ,这样就可以使用多进程执行shell命令了。
【解决方案2】:

有很多方法可以解决这个问题。关键是存在僵尸/“失效”进程,以便父进程可以收集它们的状态。

  1. 作为流程的创建者,您可以宣布您忽略该状态的意图。 POSIX 方法是设置标志SA_NOCLDWAIT(使用sigaction)。这在 Python 中有点痛苦。但大多数类 Unix 系统允许您简单地忽略 SIGCHLD / SIGCLD(拼写因类 Unix 系统而异),这在 Python 中很容易做到:

    import signal

    signal.signal(signal.SIGCHLD, signal.SIG_IGN)

  2. 或者,如果由于某种原因它不可用或在您的系统上不起作用,您可以使用一个旧的备用技巧:不要只分叉一次,分叉两次。在第一个孩子中,叉出第二个孩子;在第二个孩子中,使用execve(或类似的)运行所需的程序;然后在第一个孩子中,退出(使用_exit)。在原始父级中,使用waitwaidpid 或操作系统提供的任何内容,并收集第一个子级的状态。

    这样做的原因是第二个孩子现在已经成为“孤儿”(它的父母,第一个孩子,死了,并被您的原始进程收集)。作为孤儿,它被移交给始终为wait-ing 的代理父代(特别是“init”),因此会立即收集所有僵尸。

  3. 1234563 (这有点混乱,并且依赖于操作系统;我之前已经编写过代码,但是对于一些我现在无法访问的公司代码。)
  4. 最后,您可以简单地定期收集这些进程。如果您正在使用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。]

【讨论】:

  • 谢谢我尝试了 signal.signal 但当我调用 subprocess.call 时它会导致 OSError: [Errno 10] No child processes
  • 这意味着您没有解决方法(上面的 bugs.python.org 链接)。 subprocesscallPopen 后跟wait 组成,并且您已告诉操作系统丢弃子状态-es,因此底层os.wait 失败并显示errno.ECHILD。 (解决方法只是将其视为“子进程退出代码 = 0”。)
  • signal.signal(signal.SIGCHLD, signal.SIG_IGN) 技术对我有用
  • 谢谢,signal.signal(signal.SIGCHLD, signal.SIG_IGN) 在 docker 容器中运行的 python 2.7.10 上为我工作
【解决方案3】:

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 是非阻塞的,可以按需调用。

【讨论】:

【解决方案4】:

在终止或杀死一个进程后,操作系统等待父进程收集子进程状态。你可以使用进程的communicate()方法来收集状态:

p = subprocess.Popen( ... )
p.terminate()
p.communicate()

请注意,终止进程允许进程拦截终止信号并对其进行任何操作。这是至关重要的,因为 p.communicate() 是一个阻塞调用。

如果您不希望出现这种行为,请使用 p.kill() 而不是 p.terminate(),这会让进程不会拦截信号。

如果您想使用 p.terminate() 并确保进程自行结束,您可以使用 psutil 模块检查进程状态。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-23
    • 2011-07-15
    • 1970-01-01
    • 2012-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多