我正在开发一个devtool,这个工具的一个功能是生成一个子进程并读取该进程的stdout流。我需要将每行输出读入内存,以便以某种方式处理它(该工具的未来功能之一将涉及处理日志并将其发送到外部位置,如日志管理器和仪表板等),这就是我不喜欢的原因不要只做cmd.Stdout = os.Stdout
)
它工作正常,已经做了一段时间,但显然只在Windows上。最近我得到了一个相当令人困惑的bug report,用户报告输出不是“实时”,在Linux上进行测试时,我发现它是真的,输出只在进程退出时转储到控制台。 / p>
以下是扫描阅读器的代码,在Windows上按预期工作,但在Linux / MacOS上的Linux容器中无法正常工作(同时测试)
如果你在代码周围找到你会发现使用io.Pipe()创建阅读器的位置并绑定到cmd的Stdout / Stderr输出。
第134行是程序刚刚阻塞,直到下面的goroutine中的cmd
停止运行,在第161行。
我认为这与缓冲区和刷新有关,但我不太了解Go的内部结构以确定问题。在Windows和Linux上,scanner.Scan()
究竟有什么不同?为什么它在一个平台上阻止而在另一个平台上阻止?是否与线程/ goroutine的调度方式不同? (两台测试机都有多个内核,甚至Docker容器都有4个vCPU)
以下是供参考的问题:https://github.com/Southclaws/sampctl/issues/100
我真的很难过这个,会喜欢一些帮助搞清楚!
编辑:
所以我搞砸了一些,仍然没有解决方案。我试图使用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
attempting to read a line
就是它挂起的地方。
答案 0 :(得分:2)
默认情况下,Linux在不处于交互模式时(即不在终端中)缓冲输出,因此仅在缓冲区满时刷新输出(例如,每4096个字节,但这是定义的实现);当程序显式调用flush
时(显然这里没有发生);或者当过程结束时(如你所见)。
您可以通过调整缓冲区大小来更改此默认行为。例如,通过stdbuf
启动程序:
stdbuf -oO /root/.samp/runtime/0.3.7/samp03svr
-o
代表stdout
(-e
和-i
),O
代表“关闭”(L
代表“行缓存”或显式缓冲区大小的大小。)
或者,有一个unbuffer
命令或script
命令:
https://unix.stackexchange.com/questions/25372/turn-off-buffering-in-pipe/61833#61833
答案 1 :(得分:2)
从Y_Less的答案继续,一般的解决方案是使用伪终端。我想避免使用stdbuf或unbuffer,因为这需要依赖于外部命令。
所以我的最终解决方案是https://github.com/kr/pty,它是伪终端的Go实现。
我想我会自我回答,帮助其他Go用户通过搜索找到这个问题。