重用和关闭子进程对象的正确方法

时间:2012-03-06 14:44:32

标签: python subprocess

我在循环中有以下代码:

while true:
    # Define shell_command
    p1 = Popen(shell_command, shell=shell_type, stdout=PIPE, stderr=PIPE, preexec_fn=os.setsid)
    result = p1.stdout.read(); 
    # Define condition
    if condition:
       break;

其中shell_command类似于ls (它只是打印内容)。

我已经在不同的地方阅读过我可以通过各种方式关闭/终止/退出Popen对象,例如:

p1.stdout.close()
p1.stdin.close()
p1.terminate
p1.kill

我的问题是:

  1. 完成使用后,关闭subprocess对象的正确方法是什么?
  2. 考虑到我的脚本的性质,有没有办法只打开一个subprocess对象并使用不同的shell命令重用它?除了每次打开新的subprocess对象之外,这会更有效吗?
  3. 更新

    我仍然对要遵循的步骤顺序感到困惑,具体取决于我是使用p1.communicate()还是p1.stdout.read()来与我的流程进行互动。

    根据我在答案和评论中的理解:

    如果我使用p1.communicate(),我不必担心释放资源,因为communicate()会等到进程完成,抓取输出并正确关闭subprocess对象< / p>

    如果我遵循p1.stdout.read()路线(我认为适合我的情况,因为shell命令只是打印东西)我应该按顺序调用东西:

    1. p1.wait()
    2. p1.stdout.read()
    3. p1.terminate()
    4. 是吗?

4 个答案:

答案 0 :(得分:9)

  

完成使用后,关闭子进程对象的正确方法是什么?

stdout.close()stdin.close()不会终止进程,除非它在输入结束时或写入错误时退出。

.terminate().kill()都做了这项工作,kill在POSIX系统上更加“激烈”,因为SIGKILL被发送,这是不可忽视的应用程序。例如,解释具体差异in this blog post。在Windows上,没有区别。

此外,请记住.wait()并在杀死进程后关闭管道以避免僵尸并强制释放资源。

经常遇到的特殊情况是从STDIN读取并将结果写入STDOUT的进程,在遇到EOF时自行关闭。使用这些类型的程序,使用subprocess.communicate

通常是明智的
>>> p = Popen(["sort"], stdin=PIPE, stdout=PIPE)
>>> p.communicate("4\n3\n1")
('1\n3\n4\n', None)
>>> p.returncode
0

这也可以用于打印某些内容的程序,然后立即退出:

>>> p = Popen(["ls", "/home/niklas/test"], stdin=PIPE, stdout=PIPE)
>>> p.communicate()
('file1\nfile2\n', None)
>>> p.returncode
0
  

考虑到我的脚本的性质,有没有办法只打开一个子进程对象并使用不同的shell命令重用它?除了每次打开新的子进程对象之外,它会更有效吗?

我不认为subprocess模块支持这个,我不知道这里可以分享哪些资源,所以我认为它不会给你带来显着的优势。

答案 1 :(得分:2)

“正确”的顺序是:

  1. 创建一个线程来读取stdout(第二个读取stderr,除非你将它们合并为一个)。

  2. 将要执行的命令写入stdin。如果您没有同时读取stdout,写入stdin可能会阻止。

  3. 关闭stdin(这是孩子的信号,它现在可以自动终止)

  4. 当stdout返回EOF时,孩子已经终止。请注意,您需要同步stdout阅读器线程和主线程。

  5. 致电wait()以查看是否存在问题并清理子流程

  6. 如果您因任何原因需要停止子进程(可能是用户想要退出),那么您可以:

    1. 如果孩子在读取EOF时终止,则关闭stdin。

    2. terminate()杀死。这是忽略stdin的子进程的正确解决方案。

    3. 如果孩子没有回应,请尝试kill()

    4. 在所有这三种情况下,您都必须致电wait()来清理死亡的子进程。

答案 2 :(得分:2)

  

考虑到我的脚本的性质,有没有办法只打开一个子进程对象并使用不同的shell命令重用它?

#!/usr/bin/env python
from __future__ import print_function
import uuid
import random
from subprocess import Popen, PIPE, STDOUT

MARKER = str(uuid.uuid4())

shell_command = 'echo a'
p = Popen('sh', stdin=PIPE, stdout=PIPE, stderr=STDOUT,
          universal_newlines=True) # decode output as utf-8, newline is '\n'
while True:
    # write next command
    print(shell_command, file=p.stdin)
    # insert MARKER into stdout to separate output from different shell_command
    print("echo '%s'" % MARKER, file=p.stdin)
    # read command output
    for line in iter(p.stdout.readline, MARKER+'\n'):
        if line.endswith(MARKER+'\n'):
            print(line[:-len(MARKER)-1])
            break # command output ended without a newline
        print(line, end='')
    # exit on condition
    if random.random() < 0.1:
        break
# cleanup
p.stdout.close()
if p.stderr:
   p.stderr.close()
p.stdin.close()
p.wait()

while True置于try: ... finally:内以在发生异常时执行清理。在Python 3.2+上,您可以使用with Popen(...):代替。

  

除了每次打开新的子进程对象之外,它会更有效吗?

你的情况有关系吗?不要猜。测量它。

答案 3 :(得分:1)

  1. 取决于您对该过程的期望;你应该总是打电话给p1.wait()以避免僵尸。其他步骤取决于子进程的行为;如果它产生任何输出,你应该消耗输出(例如p1.read() ...但这会占用大量内存)然后才调用p1.wait();或者你可以等待一段时间超时,如果你认为它没有按预期工作,可以调用p1.terminate()来终止进程,并可以调用p1.wait()清除僵尸。
  2. 或者,如果io等待你({而不是杀戮),p1.communicate(...)会进行处理。

    1. 不应重用子进程对象。