我需要快速连续地调用许多短期(偶尔也有一些长期存在)的外部进程,stdout并stderr实时处理。我已经找到了许多解决方案,StdoutPipe并将StderrPipe其bufio.Scanner打包到 goroutines 中。这在大多数情况下都有效,但它偶尔会吞下外部命令的输出,我不知道为什么。
这是在 MacOS X (Mojave) 和 Linux 上显示该行为的最小示例:
package main
import (
"bufio"
"log"
"os/exec"
"sync"
)
func main() {
for i := 0; i < 50000; i++ {
log.Println("Loop")
var wg sync.WaitGroup
cmd := exec.Command("echo", "1")
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
cmd.Start()
stdoutScanner := bufio.NewScanner(stdout)
stdoutScanner.Split(bufio.ScanLines)
wg.Add(1)
go func() {
for stdoutScanner.Scan() {
line := stdoutScanner.Text()
log.Printf("[stdout] %s\n", line)
}
wg.Done()
}()
cmd.Wait()
wg.Wait()
}
}
Run Code Online (Sandbox Code Playgroud)
我已经忽略了这个stderr处理。运行它时,我只得到大约 49,900[stdout] 1行(实际数量因每次运行而异),但应该有 50,000。我看到了 50,000loop行,所以它似乎不会过早死亡。这闻起来像是某个地方的竞赛条件,但我不知道在哪里。
如果我不将扫描循环放在 goroutine 中,它工作得很好,但是我失去了同时读取stderr我需要的能力。
我试过用 运行它-race,Go 报告没有数据竞争。
我没有想法,我出了什么问题?
您没有在多个地方检查错误。
在某些情况下,这实际上并不会导致问题,但检查以下内容仍然是一个好主意:
cmd.Start()
Run Code Online (Sandbox Code Playgroud)
可能会返回错误,在这种情况下,该命令从未运行过。(这不是实际问题。)
当stdoutScanner.Scan()返回 false 时,stdoutScanner.Err()可能会显示错误。如果你开始检查这个,你会发现一些错误:
2020/02/19 15:38:17 [stdout err] read |0: file already closed
Run Code Online (Sandbox Code Playgroud)
这不是实际问题,但是——啊哈——这与您看到的症状相符:并非所有输出都被看到。现在,为什么阅读会stdout声称文件已关闭?嗯,stdout从哪里来的?它来自这里:
stdout, err := cmd.StdoutPipe()
Run Code Online (Sandbox Code Playgroud)
c.closeAfterStart = append(c.closeAfterStart, pw)
c.closeAfterWait = append(c.closeAfterWait, pr)
return pr, nil
Run Code Online (Sandbox Code Playgroud)
(并且pr是管道读取的返回值)。嗯:什么closeAfterWait意思?
现在,这是循环中的最后两行:
cmd.Wait()
wg.Wait()
Run Code Online (Sandbox Code Playgroud)
也就是说,首先我们等待cmd完成。(cmd完成后,什么会关闭?)然后我们等待正在读取cmdstdout的 goroutine完成。(嗯,还有什么可以从pr管道中读取?)
修复现在很明显:wg.Wait()将等待 stdout 管道的使用者完成读取的 与cmd.Wait()等待echo ...退出然后关闭管道的读取端的交换。如果您在读者仍在阅读时关闭,他们可能永远不会阅读您所期望的内容。
| 归档时间: |
|
| 查看次数: |
324 次 |
| 最近记录: |