为了账号安全,请及时绑定邮箱和手机立即绑定

从 Go 中的标准管道() 读取冻结

从 Go 中的标准管道() 读取冻结

Go
守候你守候我 2022-09-12 17:00:38
我试图从命令的Stdout中读取,但每(大约)一次它冻结一次(大约)。func runProcess(process *exec.Cmd) (string, string, error) {    var stdout strings.Builder    var stderr string    process := exec.Command(programPath, params...)    go func() {        pipe, err := process.StderrPipe()        if err != nil {            return        }        buf, err := io.ReadAll(pipe)        if err != nil {            log.Warn("Error reading stderr: %v", err)        }        stderr = string(buf)    }()    pipe, err := process.StdoutPipe()    if err = process.Start(); err != nil {        return "", "", err    }    buf := make([]byte, 1024)    read, err := pipe.Read(buf)            // it reads correctly from the pipe    for err == nil && read > 0 {        _, err = stdout.Write(buf[:read])        read, err = pipe.Read(buf)         // this is where is stalls    }    if err = process.Wait(); err != nil {        return stdout.String(), stderr, err    }    return stdout.String(), stderr, nil}我试图一次阅读所有内容,而不是阅读块,但我得到了同样的行为。调用的程序似乎已成功执行。它的日志文件已创建并已完成。另外,当我第一次从管道中读取时(在循环之前),所有的输出都在那里。但是在循环内部,当第二次调用 时,它应该返回一个 EOF(输出小于 1024 字节),它会冻结。stdout, err := io.ReadAll(pipe).Read()
查看完整描述

2 回答

?
幕布斯6054654

TA贡献1876条经验 获得超7个赞

此代码中有许多争用条件。一般来说,如果你创建一个 goroutine,应该有某种同步 -- 如 、 、 或原子。chansync.Mutexsync.WaitGroup

修复争用条件。

  1. 呼叫之前先呼叫 。代码不这样做。StderrPipe()Start()

  2. 等待戈鲁丁完成,然后再返回。

竞争条件可能会破坏结构...这可能意味着它泄漏了一根管道,这可以解释为什么挂起(因为管道的写入端没有关闭)。exec.CmdRead()

根据经验,请始终修复争用条件。将它们视为高优先级 Bug。

以下是如何在没有竞争条件的情况下编写它的草图:


查看完整回答
反对 回复 2022-09-12
?
qq_笑_17

TA贡献1818条经验 获得超7个赞

func runProcess(process *exec.Cmd) (stdout, stderr string, err error) {

    outPipe, err := process.StdoutPipe()

    if err != nil {

        return "", "", err

    }


    // Call StderrPipe BEFORE Start().

    // Easy way to do it: outside the goroutine.

    errPipe, err := process.StderrPipe()

    if err != nil {

        return "", "", err

    }


    // Start process.

    if err := process.Start(); err != nil {

        return "", "", err

    }


    // Read stderr in goroutine.

    var wg sync.WaitGroup

    var stderrErr error

    wg.Add(1)

    go func() {

        defer wg.Done()

        data, err := ioutil.ReadAll(errPipe)

        if err != nil {

            stderrErr = err

        } else {

            stderr = string(data)

        }

    }()


    // Read stdout in main thread.

    data, stdoutErr := ioutil.ReadAll(outPipe)


    // Wait until we are done reading stderr.

    wg.Wait()


    // Wait for process to finish.

    if err := process.Wait(); err != nil {

        return "", "", err

    }


    // Handle error from reading stdout.

    if stdoutErr != nil {

        return "", "", stderrErr

    }

    // Handle error from reading stderr.

    if stderrErr != nil {

        return "", "", stderrErr

    }


    stdout = string(data)

    return stdout, stderr, nil

}

更简单的代码

所有这些都是由包自动完成的。您可以使用 任何 for 和 ,您不限于 。os/execio.WriterStdoutStderr*os.File


func runProcess(process *exec.Cmd) (stdout, stderr string, err error) {

    var stdoutbuf, stderrbuf bytes.Buffer

    process.Stdout = &stdoutbuf

    process.Stderr = &stderrbuf

    if err := process.Run(); err != nil {

        return "", "", err

    }

    return stdoutbuf.String(), stderrbuf.String(), nil

}


查看完整回答
反对 回复 2022-09-12
  • 2 回答
  • 0 关注
  • 113 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号