【问题标题】:How to Clean Up subprocess.Popen Instances Upon Process Termination如何在进程终止时清理 subprocess.Popen 实例
【发布时间】:2013-05-02 14:45:14
【问题描述】:

我有一个在 Python / PyQt / QtWebKit 基础上运行的 JavaScript 应用程序,它创建 subprocess.Popen 对象来运行外部进程。

Popen 对象保存在字典中并由内部标识符引用,以便 JS 应用程序可以通过 pyqtSlot 调用 Popen 的方法,例如 poll() 以确定进程是否仍在运行或kill() 杀死流氓进程。

如果某个进程不再运行,我想从字典中删除其Popen 对象以进行垃圾回收。

自动清理字典以防止内存泄漏的推荐方法是什么?

到目前为止我的想法:

  • 在每个衍生进程的线程中调用 Popen.wait() 以在终止时立即执行自动清理。
    专业版:立即清理,线程可能不会消耗太多 CPU 功率,因为​​它们应该处于休眠状态,对吗?
    CON:许多线程取决于生成活动。
  • 使用线程在所有现有进程上调用 Popen.poll(),并检查 returncode 是否已终止并在这种情况下进行清理。
    专业版:所有进程只需一个工作线程, 降低内存使用率。
    CON:需要定期轮询,如果有许多长时间运行的进程或产生大量已处理的进程,则 CPU 使用率会更高。

你会选择哪一个,为什么?还是有更好的解决方案?

【问题讨论】:

  • 这需要在什么操作系统上运行?
  • 主要是 Windows,如果可能的话,Mac OS X,Linux 会很高兴。最好是与平台无关的解决方案。
  • 好吧,我给出的答案适用于 Linux 和 OSX。我得考虑一下 Windows 解决方案。
  • 好的,谢谢,我忘了说这是 Python 3.3。
  • 更新了答案。代码示例适用于 Python 2.x,但对于 3.x-compat,您只需将 print foo 更改为 print(foo)

标签: python python-3.x pyqt subprocess qtwebkit


【解决方案1】:

对于与平台无关的解决方案,我会选择选项 #2,因为高 CPU 使用率的“CON”可以通过类似...

import time

# Assuming the Popen objects are in the dictionary values
PROCESS_DICT = { ... }

def my_thread_main():
    while 1:
        dead_keys = []
        for k, v in PROCESS_DICT.iteritems():
            v.poll()
            if v.returncode is not None:
                dead_keys.append(k)
        if not dead_keys:
            time.sleep(1)  # Adjust sleep time to taste
            continue
        for k in dead_keys:
            del PROCESS_DICT[k]

...因此,如果没有进程在迭代中死亡,您只需睡一会儿。

因此,实际上,您的线程大部分时间仍处于休眠状态,尽管在子进程死亡与其随后的“清理”之间存在潜在的延迟,但这真的没什么大不了的,这应该比使用每个进程一个线程。

不过,有更好的平台相关解决方案。

对于 Windows,您应该能够通过 ctypesWaitForMultipleObjects 函数用作 ctypes.windll.kernel32.WaitForMultipleObjects,尽管您必须研究其可行性。

对于 OSX 和 Linux,使用 signal 模块异步处理 SIGCHLD 可能是最简单的方法。

一个简单的脏例子......

import os
import time
import signal
import subprocess

# Map child PID to Popen object
SUBPROCESSES = {}

# Define handler
def handle_sigchld(signum, frame):
    pid = os.wait()[0]
    print 'Subprocess PID=%d ended' % pid
    del SUBPROCESSES[pid]

# Handle SIGCHLD
signal.signal(signal.SIGCHLD, handle_sigchld)

# Spawn a couple of subprocesses
p1 = subprocess.Popen(['sleep', '1'])
SUBPROCESSES[p1.pid] = p1
p2 = subprocess.Popen(['sleep', '2'])
SUBPROCESSES[p2.pid] = p2

# Wait for all subprocesses to die
while SUBPROCESSES:
    print 'tick'
    time.sleep(1)

# Done
print 'All subprocesses died'

【讨论】:

  • 是的,我一直在考虑使用WaitForMultipleObjects(),但是我猜这个解决方案会有点复杂......你可能必须在每次有新进程时更新等待进程添加,这可能不值得付出努力,例如在一个循环中并使用几秒钟的等待超时或其他东西。此外,由于MAXIMUM_WAIT_OBJECTS 的限制,您可能需要将等待拆分为多个线程。
  • @Archimedix 是的。这与在多个文件描述符上使用 select() 非常相似——通常的习惯用法是包含 FD(通常是侦听套接字),这可能会更改您正在监视的 FD 集。因此,在您的情况下,您需要在集合中包含一些可用于检测何时创建新进程的对象,那么等待超时可能会很长。但是,唯一的好处是从您的选项 #2 中消除延迟。 (在下一条评论中继续)
  • @Archimedix(接上一条评论)SIGCHLD 解决方案似乎是最优雅的,它是异步的(即不需要阻塞调用),并且可以在主线程中使用。它也可以在 Windows 上使用,只要您的代码将在为 cygwin 编译的 Python 版本下运行,但如果您使用多个第三方 Python 扩展模块,这可能会更加复杂。我建议现在使用选项 #2,因为它不需要太多线程管理,如果有必要,稍后再考虑优化。
  • @Archimedix 我刚刚遇到another question,它可能会使用 WMI 提供异步 Windows 选项 - 不过不确定适应 Python 是否容易。
  • 谢谢,我认为 WMI 太麻烦了。
猜你喜欢
  • 2010-09-23
  • 2013-11-09
  • 1970-01-01
  • 1970-01-01
  • 2020-11-04
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多