go - 为什么即使达到 EOF io.Pipe() 也会继续阻塞?

标签 go io

在使用子进程并通过管道读取标准输出时,我注意到了一些有趣的行为。

如果我使用 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

这给了我两条线索:

  1. 文件描述符在某些时候被关闭。至关重要的是,管道的“写入”端在进程启动后关闭。
  2. 不是我上面使用的 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/

相关文章:

go - 方法集(指针与值接收器)

go - 来自 r.URL.Query() 的键映射不一致

go - golang 中的数组比较

sql - 使用 Postgres 时为 "Operator does not exist: integer =?"

c++ - Ifstream 从文本文件中读取错误的字符

go - Go语言中的观察者模式

string - Haskell 读取文件 : Couldn't match expected type ‘[String]’ with actual type ‘IO String’

c - 如何对涉及 IO 的 c 函数进行单元测试?

java - 在 Java 中测量线程 I/O

Python - 如何在没有引号和空格的情况下将字符串写入文件?