代码之家  ›  专栏  ›  技术社区  ›  Southclaws

扫描仪。在Linux(而非Windows)上,扫描()阻塞直到退出

  •  3
  • Southclaws  · 技术社区  · 7 年前

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

    它工作得很好,已经有一段时间了,但显然只是在Windows上。我有点困惑 bug report 最近,一位用户报告输出不是“实时”的,在Linux上进行测试后,我发现这是真的,只有当进程退出时,输出才会转储到控制台。

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

    https://github.com/Southclaws/sampctl/blob/f639c941dd8f9ca7c7c819a152909044ad63be08/runtime/run.go#L133-L137

    如果你浏览一下代码,你会发现读卡器是在哪里用io创建的。并绑定到cmd的Stdout/Stderr输出。

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

    我假设这与缓冲区和刷新有关,但我对Go的内部结构了解不够,无法真正找出问题所在。到底有什么不同 scanner.Scan() 在Windows和Linux上?为什么它在一个平台上阻塞,而在另一个平台上阻塞?它是否与不同调度的线程/goroutines相关?(两台测试机都有多核,甚至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 就是它悬挂的地方。

    2 回复  |  直到 7 年前
        1
  •  2
  •   Y_Less    7 年前

    默认情况下,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

        2
  •  2
  •   Southclaws    7 年前

    从Y\u Less的答案来看,一般的解决方案是使用伪终端。我希望避免使用stdbuf或unbuffer,因为这需要依赖于存在的外部命令。

    所以我的最终解决方案是 https://github.com/kr/pty 这是一个伪终端的Go实现。

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