为什么python.subprocess在proc.communicate()后挂起?

23 投票
4 回答
48419 浏览
提问于 2025-04-15 20:12

我有一个互动程序,叫做 my_own_exe。首先,它会打印出 alive,然后你输入 S\n,接着它又会打印出 alive。最后你输入 L\n,它会进行一些处理然后退出。

但是,当我从下面的Python脚本调用这个程序时,它在打印出第一个'alive'后似乎就卡住了。

这里有没有人能告诉我为什么会这样?

// 在阅读了后续的回复后(谢谢大家),我把代码修改成了这样:

import subprocess
import time

base_command = "./AO_FelixStrategy_UnitTest --bats 31441 --chix 12467 --enxutp 31884 --turq 26372 --symbol SOGN --target_date " + '2009-Oct-16'
print base_command

proc2 = subprocess.Popen(base_command, shell=True , stdin=subprocess.PIPE,)

time.sleep(2);
print "aliv"
proc2.communicate('S\n')

print "alive"
time.sleep(6)

print "alive"
print proc2.communicate('L\n')
time.sleep(6)

现在程序在接收到第一个输入'S\n'后运行得很好,但接下来又停住了,第二个输入'L\n'似乎被忽略了。

有没有人能告诉我为什么会这样?

4 个回答

1

虽然上面的回答提到在进程结束后使用 process.communicate() 是安全的,但这里有个小提醒。

  • 如果这个进程启动了其他子进程,那么在你用 kill() 杀掉父进程后,只要子进程还活着,process.communicate() 就会被阻塞。虽然 process.wait() 可能会返回一个代码,表示进程已经退出,但如果你调用 process.communicate(),它还是会卡住。

  • 这是因为 UNIX 系统(比如 Linux 和 macOS)的工作方式。

  • 如果你使用了标准库中的 multiprocessing.Manager 类,可能会悄悄地出现这种情况。Manager 会在后台启动一个子进程,这对开发者来说是看不见的,所以可能会让人感到意外。

  • 因为 Manager 启动的子进程没有设置 Process.daemon 标志,这个子进程会阻止整个进程组的 terminate()kill(),导致即使进程看似已经结束,process.communicate() 也无法正常工作。

  • 在这种情况下,进程处于一种“僵尸”状态,既不活着也不完全死去。

作为参考,我没有找到一个简单的方法来安全地使用 Manager,如果你想用 process.communicate()

4

communicate() 这个函数会从标准输出(stdout)和标准错误(stderr)中读取数据,直到文件的末尾被到达,也就是说,它会一直等到你的程序结束。

38

来自 关于 communicate 的文档

与进程进行交互:向标准输入(stdin)发送数据。从标准输出(stdout)和标准错误(stderr)读取数据,直到文件结束。等待进程终止。

所以在 communicate() 执行完后,进程就已经结束了。

如果你想在进程没有停止的情况下进行读写:

  • 绝对不要使用 shell=True - 这样会多此一举地调用一个shell来执行你的程序,这样在你和程序之间就会多出一个进程。这会带来很多不好的副作用。默认情况下是 shell=False,所以你应该保持这个设置。把你的 Popen 行改成:

    p = subprocess.Popen(["./AO_FelixStrategy_UnitTest",
                          "--bats", "31441", "--chix", "12467",
                          "--enxutp", "31884", "--turq", "26372",
                          "--symbol", "SOGN", "--target_date", '2009-Oct-16'],
                         stdin=subprocess.PIPE, 
                         stdout=subprocess.PIPE)
    
  • 使用 p.stdin.write 向进程写入数据。使用 p.stdout.read 从进程读取数据。

  • 如果调用 p.stdout.read 时没有数据可读,它会阻塞。如果调用 p.stdin.write 时写入缓冲区已满,它也会阻塞。所以你必须确保有东西可以读或写 - 在Unix系统上,你可以使用 select 来做到这一点。在Windows上,你不幸地需要使用线程。至少这就是 Popen.communicate 在内部所做的。
  • 如果你没有写 AO_FelixStrategy_UnitTest,那么你可能会遇到其他问题:
    • 它可能是从其他地方读取,而不是标准输入。有些程序直接从终端读取,其他程序使用某些操作系统API来读取。这意味着写入到stdin的数据不会传递给程序。这在密码提示时经常发生。
    • 记得要考虑 AO_FelixStrategy_UnitTest 的缓冲区。默认情况下,标准C的管道通信是有缓冲的,所以你可能在关闭输入端(通过 p.stdin.close())之前看不到任何输出。除非 AO_FelixStrategy_UnitTest 定期刷新输出。

这里有一些示例代码,基于你描述的内容。它可能会根据 AO_FelixStrategy_UnitTest 的开发方式而工作:

p = subprocess.Popen(["./AO_FelixStrategy_UnitTest",
                      "--bats", "31441", "--chix", "12467",
                      "--enxutp", "31884", "--turq", "26372",
                      "--symbol", "SOGN", "--target_date", '2009-Oct-16'],
                     stdin=subprocess.PIPE, 
                     stdout=subprocess.PIPE)
output = p.communicate('S\nL\n')[0]
print output

撰写回答