Go scanner.Scan()在Linux(而不是Windows)上阻止直到退出

Go scanner.Scan()在Linux(而不是Windows)上阻止直到退出,go,io,stdout,child-process,spawn,Go,Io,Stdout,Child Process,Spawn,我正在开发一个devtool,这个工具的一个特性是生成一个子进程并读取该进程的标准输出流。我需要将每一行输出读入内存,以便以某种方式对其进行处理(该工具的未来功能之一将涉及处理日志并将其发送到外部位置,如日志管理器和仪表板等),因此我不简单地执行cmd.Stdout=os.Stdout) 它运行良好,已经运行了一段时间,但显然只在Windows上运行。最近我遇到了一个相当令人困惑的问题,一个用户报告输出不是“实时的”,在Linux上测试时,我发现这是真的,并且只有当进程退出时,输出才会转储到控制

我正在开发一个devtool,这个工具的一个特性是生成一个子进程并读取该进程的标准输出流。我需要将每一行输出读入内存,以便以某种方式对其进行处理(该工具的未来功能之一将涉及处理日志并将其发送到外部位置,如日志管理器和仪表板等),因此我不简单地执行
cmd.Stdout=os.Stdout

它运行良好,已经运行了一段时间,但显然只在Windows上运行。最近我遇到了一个相当令人困惑的问题,一个用户报告输出不是“实时的”,在Linux上测试时,我发现这是真的,并且只有当进程退出时,输出才会转储到控制台

下面是扫描读卡器的代码,在Windows上按预期工作,但在Linux上不工作,或者在Windows/MacOS上的Linux容器中也不工作(两者都经过测试)

如果仔细查看代码,您会发现读卡器是在哪里用io.Pipe()创建的,并绑定到cmd的Stdout/Stderr输出

第134行是程序阻塞的地方,直到下面goroutine中的
cmd
停止运行,在第161行

我假设这与缓冲区和刷新有关,但我对Go的内部结构了解不够,无法真正找出问题所在。Windows和Linux上的
scanner.Scan()
到底有什么不同?为什么它在一个平台上阻塞,而不在另一个平台上阻塞?它是否与不同调度的线程/goroutine有关?(两台测试机都有多个核,即使Docker容器也有4个VCPU)

以下是供参考的问题:

我真的被这件事难住了,希望能有人帮我弄明白

编辑:

所以我又搞砸了一些,仍然没有解决办法。我尝试使用Python脚本并得到了相同的结果,stdout在定向到tty时可以正常工作,但当进程读取它时,它只是挂起:

from subprocess import Popen, PIPE
from time import sleep

p = Popen(
    ['/root/.samp/runtime/0.3.7/samp03svr'],
    stdin=PIPE,
    stdout=PIPE,
    stderr=PIPE,
    shell=False,
    cwd="/root/.samp/runtime/0.3.7/")

while True:
    print "attempting to read a line"
    output = p.stdout.read()
    print "read a line"
    if not output:
        print '[No more data]'
        break
    print output

尝试读取一行时
是挂起的地方。

默认情况下,Linux缓冲区在不处于交互模式(即不在终端中)时输出,因此仅当缓冲区已满时才刷新输出(例如,每4096字节,但这是实现定义的);当程序显式调用
flush
时(这里显然不发生这种情况);或者当过程结束时(如您所见)

您可以通过调整缓冲区大小来更改此默认行为。例如,通过
stdbuf
启动程序:

stdbuf -oO /root/.samp/runtime/0.3.7/samp03svr
-o
用于
stdout
(也可以是
-e
-i
),
o
用于“关闭”(也可以是
L
用于“行缓冲”或用于显式缓冲大小的大小)

或者有一个
unbuffer
命令或
script
命令:


根据Y_Less的回答,一般的解决方案是使用伪终端。我希望避免使用stdbuf或unbuffer,因为这需要依赖于存在的外部命令

所以我的最终解决方案是一个伪终端的Go实现


我只是想自己回答,帮助其他围棋用户通过搜索找到这个问题。

bufio
没有特定于目标的代码。您是否尝试使用
io.MultiWriter
测试输出是否被cmd阻止。我有一个粗略的猜测,那就是你的外部程序在Linux上的行为不同,而不是你的go代码。只是一个粗略的猜测——这可能与新行需要不同对待的方式有关。你能不能写一个小程序,只给扫描器提供与当前输入完全相同的输入,然后在Windows和Linux上运行它,看看会发生什么。@leadbebebop我没想到,我会调查的。不过,在linux上,子进程在输出方面似乎是相同的,即:输出通常通过stdout流式传输到控制台。@Ravi根据
bufio.ScanLines
的文档,这不太可能。但是是的,一些更具控制性的测试会有所帮助。这里仍然有太多的迷雾。出于测试目的,如果您将当前的实现替换为
cmd.Stdout=os.Stdout
,它在Linux上是否正常工作/如预期的那样?