在使用子进程并通过管道读取标准输出时,我注意到了一些有趣的行为。
如果我使用 io.Pipe() 读取通过 os/exec 创建的子进程的标准输出,即使达到 EOF,从该管道读取也会永远挂起(流程结束):
cmd := exec.Command(“/bin/echo”, “Hello, world!”)
r, w := io.Pipe()
cmd.Stdout = w
cmd.Start()
io.Copy(os.Stdout, r) // Prints “Hello, World!” but never returns
但是,如果我使用内置方法 StdoutPipe() 它会起作用:
cmd := exec.Command(“/bin/echo”, “Hello, world!”)
p := cmd.StdoutPipe()
cmd.Start()
io.Copy(os.Stdout, p) // Prints “Hello, World!” and returns
深入/usr/lib/go/src/os/exec/exec.go的源代码,我可以看到StdoutPipe()方法实际上使用了os.Pipe (),而不是 io.Pipe():
pr, pw, err := os.Pipe()
cmd.Stdout = pw
cmd.closeAfterStart = append(c.closeAfterStart, pw)
cmd.closeAfterWait = append(c.closeAfterWait, pr)
return pr, nil
这给了我两条线索:
文件描述符在某些时候被关闭。至关重要的是,管道的“写入”端在进程启动后关闭。
不是我上面使用的 io.Pipe(),而是 os.Pipe()(一个较低级别的调用,大致映射到 pipe(2) 在 POSIX 中)被使用。
但是,在考虑了这些新发现的知识后,我仍然无法理解为什么我的原始示例的行为方式如此。
如果我尝试关闭 io.Pipe()(而不是 os.Pipe())的写端,那么它似乎会完全破坏它并且没有任何内容被读取(就像我正在从一个封闭的管道中读取一样,即使我认为我将它传递给了子进程):
cmd := exec.Command(“/bin/echo”, “Hello, world!”)
r, w := io.Pipe()
cmd.Stdout = w
cmd.Start()
w.Close()
io.Copy(os.Stdout, r) // Prints nothing, no read buffer available
好吧,所以我猜 io.Pipe() 与 os.Pipe() 完全不同,并且可能不像 Unix 管道那样close() 不会为所有人关闭它。
只是为了让您不要认为我要求快速修复,我已经知道我可以通过使用此代码实现我的预期行为:
cmd := exec.Command(“/bin/echo”, “Hello, world!”)
r, w, _ := os.Pipe() // using os.Pipe() instead of io.Pipe()
cmd.Stdout = w
cmd.Start()
w.Close()
io.Copy(os.Stdout, r) // Prints “Hello, World!” and returns on EOF. Works. 😃
我要问的是为什么 io.Pipe() 似乎忽略了作者的 EOF,让读者永远阻塞?一个有效的答案可能是 io.Pipe() 是错误的工具,因为 $REASONS 但我无法弄清楚那些 $REASONS 是因为根据文档,我正在尝试做的事情似乎完全合理。
这里有一个完整的例子来说明我在说什么:
package main
import (
“fmt”
“os”
“os/exec”
“io”
)
func main() {
cmd := exec.Command(“/bin/echo”, “Hello, world!”)
r, w := io.Pipe()
cmd.Stdout = w
cmd.Start()
io.Copy(os.Stdout, r) // Blocks here even though EOF is reached
fmt.Println(“Finished io.Copy()”)
cmd.Wait()
}
“为什么 io.Pipe() 似乎忽略了作者的 EOF,让读者永远阻塞?”因为没有“作者的EOF”之类的东西。所有 EOF(在 unix 中)都向读者表明没有进程保持管道的写入端打开。当进程试图从没有写入器的管道中读取时,read 系统调用返回一个值,该值方便地命名为 EOF。由于您的 parent 仍然打开了管道写入端的一个副本,因此 read block 。停止将 EOF 视为一件事。它只是一种抽象,作者从不“发送”它。
关于go - 为什么即使达到 EOF io.Pipe() 也会继续阻塞?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47486128/