使用 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.Stdout
和 cmd.Stderr
直到它们关闭,例如,就像你对 cmd.Stdin
所做的那样(当然是在其他方向)。否则有死锁的风险 - 进程被阻塞等待写入 stdout/stderr,并且您的程序被阻塞等待进程完成。
或者,如@JimB 所说,只需将字符串缓冲区分配给 cmd.Stdout
和 cmd.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
}
现场演示:
我采纳了@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
我有代码,它以正确的方式工作,但它不是线程安全的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.Stdout
和 cmd.Stderr
直到它们关闭,例如,就像你对 cmd.Stdin
所做的那样(当然是在其他方向)。否则有死锁的风险 - 进程被阻塞等待写入 stdout/stderr,并且您的程序被阻塞等待进程完成。
或者,如@JimB 所说,只需将字符串缓冲区分配给 cmd.Stdout
和 cmd.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
}
现场演示:
我采纳了@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