使用 Stdout 和 Stderr 的线程安全操作(exec.Cmd)

Thread-safe operation with Stdout and Stderr (exec. Cmd)

我有代码,它以正确的方式工作,但它不是线程安全的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()

link https://play.golang.org/p/htbn2zXXeQk

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

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

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

如何实现第一个例子中的逻辑,但它应该是线程安全的?

你必须在单独的 goroutines 中抽取 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

我采纳了@rusty 的建议,无论如何比赛 `runner.go:264' 行是

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

解决方法: 创建您自己的 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