Python 如何在不使用communicate()的情况下避免子流程中的死锁

Python 如何在不使用communicate()的情况下避免子流程中的死锁,python,subprocess,stdout,deadlock,popen,Python,Subprocess,Stdout,Deadlock,Popen,我知道communicate()可以给我一个解决这个问题的方法,但我想以后再发出更多的命令。但是communicate()关闭了子流程 这附近有专门的工作吗。我正在尝试使用python包装器与路由器交互。因此,我有更多的命令,也可以产生一些输出。在这种情况下,如何在不终止子进程的情况下进行读取 proc = subprocess.Popen(['start'],stdin=subprocess.PIPE,stdout=subprocess.PIPE) proc.stdin.write('issu

我知道communicate()可以给我一个解决这个问题的方法,但我想以后再发出更多的命令。但是communicate()关闭了子流程

这附近有专门的工作吗。我正在尝试使用python包装器与路由器交互。因此,我有更多的命令,也可以产生一些输出。在这种情况下,如何在不终止子进程的情况下进行读取

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 
该行导致死锁,因为
read()
在读取EOF之前不会返回,EOF是在另一方关闭其标准输出时发送的(例如,当子进程终止时)。相反,您希望读取面向行的输入:

output = proc.stdout.read()   # Deadlocked here
readline()
将在读取换行符(或EOF)后返回,即
readline()
读取一行后返回

您的下一个死锁将由以下任一原因导致:

  • 不向发送给子流程的输出中添加换行符——当子流程尝试读取面向行的输入时,即子流程在从stdin读取时查找换行符

  • 不刷新输出,这意味着另一端从未看到任何要读取的数据,因此另一端挂起等待数据

  • 为了提高效率,当您写入一个文件(包括stdout、stdin)时,输出会被缓冲,这意味着python不会将输出实际写入一个文件,而是会欺骗您并将输出存储在一个列表(称为缓冲区)中。然后,当列表增长到一定大小时,python会将所有输出一次写入文件,这比一次写入一行更有效

    不向发送到子流程的所有输出添加换行符可以很容易地更正;但是,发现需要刷新缓冲区的所有位置更加困难。下面是一个例子:

    prog.py:

    output = proc.stdout.readline()
    
    假设您想用另一个程序驱动该程序

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

  • 请注意shebang行

  • 请注意,shebang行中python解释器的
    -u标志
    ,这意味着该程序的所有输出都将是无缓冲的,即它将直接写入
    stdout
    ——而不是累积在缓冲区中


  • 评论回复:

    你可能正遭受缓冲的痛苦。大多数程序缓冲输出以提高效率。此外,一些程序将尝试确定其标准输出是否连接到终端——如果连接到终端,则程序采用行缓冲,这意味着每次程序输出换行时,从缓冲区检索输出并实际写入标准输出。这允许人类使用连接的终端与程序进行交互

    另一方面,如果程序的stdout未连接到终端,则程序将阻塞缓冲区,这意味着只有在缓冲区增长到特定大小(例如4K数据)后,才会从缓冲区检索输出并实际写入stdout。如果程序是块缓冲的,并且输出小于4K,那么实际上不会向标准输出写入任何内容。块缓冲允许其他计算机程序以更高的效率检索程序的输出

    如果路由器程序是块缓冲的,并且它的输出小于块大小,那么实际上没有任何内容写入标准输出,因此python程序没有任何内容可读取


    python程序不是终端,因此路由器程序可能是块缓冲。正如J.F.Sebastian在评论中指出的,有一些方法可以欺骗路由器程序,使其认为您的python程序是一个终端,这将导致路由器程序使用行缓冲区,因此您可以使用
    readline()
    从其标准输出读取。查看pexpect。

    代码非常简单。请提供更多关于输入/输出的信息以及问题的具体内容。请阅读使用线程从stdout和stderr读取行,然后将它们放在主线程可以查看数据的队列中。只需在阅读之前添加
    proc.stdin.flush()
    ,一切都会好起来的。感谢所有人的帮助,它工作得很好。你知道如何避免被阻止吗?您使用了什么解决方案?它非常脆弱(您必须知道要准确读取多少数据——输入/输出可能很容易失去同步,从而导致死锁),并且它无法处理发生死锁时的情况。您可以异步使用或/写入管道。我也尝试过使用
    .readline()
    ,但没有任何运气。它也在等待一些东西,我已经在路由器中发出了打印命令,它在无休止的等待中。“请帮我解决这个问题。”JAugust,我的回答在我答案的底部。难道这不是一个非阻塞阅读吗?就像我读的一样,没有什么可读的,就让脚本继续吧,或者什么的?非常好的答案!非常感谢你!
    #!/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)
    
    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))