【问题标题】:Thread-safe operation with Stdout and Stderr (exec. Cmd)使用 Stdout 和 Stderr 的线程安全操作(exec.Cmd)
【发布时间】:2021-06-24 13:11:51
【问题描述】:

我有代码并且它以正确的方式工作,但在这些行中它不是线程安全的https://play.golang.org/p/8EY3i1Uk_aO 比赛在这里发生。

stdout := cmd.Stdout.(*bytes.Buffer).String()
stderr := cmd.Stderr.(*bytes.Buffer).String()

我是这样重写的

readout, _ := cmd.StdoutPipe()
readerr, _ := cmd.StderrPipe()

链接 https://play.golang.org/p/htbn2zXXeQk

我不喜欢这里使用MultiReader,我无法将数据标准输出与标准错误分开

r, _ := bufio.NewReader(io.MultiReader(readout, readerr)).ReadString('\n')

第二个例子也不行(在代码中有注释)。我希望 stdout 不是空的(就像这里 https://play.golang.org/p/8EY3i1Uk_aO

如何让逻辑和第一个例子一样,但它应该是线程安全的?

【问题讨论】:

  • I don’t like that MultiReader is used here - 那为什么要使用它呢?您可以分别从两个流中读取。
  • 你第一个操场代码play.golang.org/p/8EY3i1Uk_aO 显示空标准输出结果
  • @Emile Pels,由于某种原因,它没有从两个线程中读取,它在阅读器上冻结,我知道这是因为那里没有写任何东西

标签: go stdout


【解决方案1】:

你必须在单独的 goroutine 中注入 cmd.Stdoutcmd.Stderr 直到它们关闭,例如,就像你对 cmd.Stdin 所做的那样(当然,在其他方向)。否则会有死锁的风险 - 进程被阻塞等待写入 stdout/stderr,并且您的程序被阻塞等待进程完成。

或者,就像@JimB 所说,只需将字符串缓冲区分配给cmd.Stdoutcmd.Stderr,它们将在进程运行时被填充。

func invoke(cmd *exec.Cmd) (stdout string, stderr string, err error) {
    stdoutbuf, stderrbuf := new(strings.Builder), new(strings.Builder)
    cmd.Stdout = stdoutbuf
    cmd.Stderr = stderrbuf
    err = cmd.Start()
    if err != nil {
        return
    }
    err = cmd.Wait()
    return stdoutbuf.String(), stderrbuf.String(), err
}

现场演示:

https://play.golang.org/p/hakSVNbqirB

【讨论】:

  • 如果您将非文件io.Writer 直接分配给Stdout 和/或Stderrexec.Cmd 将自行执行此操作。
  • @JimB 是的,但在这种情况下如何排出它们?程序完成后,管道仍在写入。这个demo 有时没有输出,例如(或者我做错了)
  • @JimB 谢谢。当然我做错了 - 需要使用 cmd.Wait() 而不是 cmd.Process.Wait() 才能工作。
  • 啊,我也没看到! ;)
【解决方案2】:

我使用了@rusty 的建议,反正比赛 行 `runner.go:264' 是

append(normalizeEncoding(stdoutbuf.String()), normalizeEncoding(stderrbuf.String()), false)

【讨论】:

    【解决方案3】:

    解决方案: 创建自己的 Writer 包装器

    type lockedWriter struct {
        sync.RWMutex
    
        buf []byte
        w   io.Writer
    }
    
    
    func (w *lockedWriter) Write(b []byte) (n int, err error) {
        w.Lock()
        defer w.Unlock()
    
        w.buf = append(w.buf, b...)
        return w.w.Write(b)
    }
    
    func (w *lockedWriter) String() string {
        w.RLock()
        defer w.RUnlock()
    
        return string(w.buf)
    }
    

    用法

    stdoutbuf, stderrbuf := &lockedWriter{w: new(strings.Builder)}, &lockedWriter{w: new(strings.Builder)}
    cmd.Stdout = stdoutbuf
    cmd.Stderr = stderrbuf
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-08
      相关资源
      最近更新 更多