如何在子进程中避免死锁而不使用 communicate()

13 投票
1 回答
10108 浏览
提问于 2025-05-10 23:35
proc = subprocess.Popen(['start'],stdin=subprocess.PIPE,stdout=subprocess.PIPE)
proc.stdin.write('issue commands')
proc.stdin.write('issue more commands')
output = proc.stdout.read()   # Deadlocked here

# Actually I have more commands to issue here 

我知道使用 communicate() 可以解决这个问题,但我想之后还要发更多的命令。不过,使用 communicate() 的话,会把子进程给关闭掉。

有没有什么办法可以解决这个问题呢?我正在用 Python 的一个封装库和路由器进行交互。接下来我还有更多的命令要发,这些命令也可能会产生一些输出。在这种情况下,我该怎么做才能在不结束子进程的情况下读取输出呢?

相关文章:

  • 暂无相关问题
暂无标签

1 个回答

16
output = proc.stdout.read()   # Deadlocked here

这一行会导致死锁,因为 read() 不会返回,直到它读取到文件结束符(EOF),这个EOF是在另一端关闭它的标准输出(stdout)时发送的,比如当子进程结束时。相反,你应该读取以行作为单位的输入:

output = proc.stdout.readline()

readline() 会在读取到换行符(或EOF)后返回,也就是说,readline() 会在读取到一整行后返回。

接下来你可能会遇到的死锁原因有:

  1. 没有在你发送给子进程的输出中添加 newline(换行符)——当子进程试图读取以行为单位的输入时,它会在从标准输入(stdin)读取时寻找换行符。

  2. 没有 flushing 输出,这意味着另一端根本看不到任何数据可读,因此它会一直等待数据。

为了提高效率,当你写入文件(包括标准输出和标准输入)时,输出是 缓冲 的,这意味着 Python 不会立即将输出写入文件,而是把输出存储在一个列表中(称为 缓冲区)。当这个列表的大小达到一定程度时,Python 会一次性将所有输出写入文件,这比每次写一行要高效得多。

没有在你发送给子进程的所有输出中添加换行符是比较容易解决的;然而,找出所有需要刷新缓冲区的地方就比较困难了。这里有个例子:

prog.py:

#!/usr/bin/env python3.4 -u

import sys

print('What is your name?') 
name = input()
print(name)

print("What is your number?")
number = input()
print(number)

假设你想用另一个程序来驱动这个程序?

  1. prog.py 可执行:$ chmod a+x prog.py

  2. 注意 shebang 行。

  3. 注意 shebang 行中的 -u flag,这意味着该程序的所有输出将是无缓冲的,也就是说,它会直接写入 stdout——而不是先存储在缓冲区中。


import subprocess as sp

child = sp.Popen(
    ['./prog.py'],
    stdin = sp.PIPE,
    stdout = sp.PIPE
)

print_question = child.stdout.readline().decode('utf-8')  #PIPE's send a bytes type, 
                                                          #so convert to str type
name = input(print_question)
name = "{}\n".format(name)
child.stdin.write(
    name.encode('utf-8') #convert to bytes type
)
child.stdin.flush()

print_name = child.stdout.readline().decode('utf-8')
print("From client: {}".format(name))

print_question = child.stdout.readline().decode('utf-8')

number = input(print_question)
number = "{}\n".format(number)
child.stdin.write(
    number.encode('utf-8')
)
child.stdin.flush()

print_number = child.stdout.readline().decode('utf-8')
print("From client: {}".format(print_number))

对评论的回应:

你可能正在 遭受缓冲 的困扰。大多数程序为了提高效率都会缓冲输出。此外,一些程序会尝试判断它们的标准输出是否连接到终端——如果连接了,程序就会使用 行缓冲,这意味着每当程序输出换行符时,缓冲区中的输出就会被取出并实际写入标准输出。这使得使用连接的终端的人能够与程序进行交互。

另一方面,如果程序的标准输出没有连接到终端,程序就会使用 块缓冲,这意味着只有在缓冲区的大小达到特定值,比如4K数据后,输出才会被取出并实际写入标准输出。如果程序使用块缓冲并且输出少于4K,那么实际上什么都不会写入标准输出。块缓冲可以让其他计算机程序更高效地获取程序的输出。

如果路由程序使用块缓冲,并且它的输出少于块大小,那么实际上什么都不会写入标准输出,因此你的 Python 程序就没有东西可读。

你的 Python 程序不是终端,所以路由程序可能在使用 块缓冲。正如 J.F. Sebastian 在评论中指出的,有一些方法可以让路由程序认为你的 Python 程序是一个终端,这样路由程序就会使用 行缓冲,因此你就可以通过 readline() 从它的标准输出中读取数据。可以看看 pexpect。

撰写回答