如何在子进程中避免死锁而不使用 communicate()
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 个回答
output = proc.stdout.read() # Deadlocked here
这一行会导致死锁,因为 read()
不会返回,直到它读取到文件结束符(EOF),这个EOF是在另一端关闭它的标准输出(stdout)时发送的,比如当子进程结束时。相反,你应该读取以行作为单位的输入:
output = proc.stdout.readline()
readline()
会在读取到换行符(或EOF)后返回,也就是说,readline()
会在读取到一整行后返回。
接下来你可能会遇到的死锁原因有:
没有在你发送给子进程的输出中添加
newline
(换行符)——当子进程试图读取以行为单位的输入时,它会在从标准输入(stdin)读取时寻找换行符。没有
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)
假设你想用另一个程序来驱动这个程序?
让
prog.py
可执行:$ chmod a+x prog.py
注意 shebang 行。
注意 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。