douzhi1919 2017-11-25 12:45
浏览 79
已采纳

为什么即使达到EOF io.Pipe()仍继续阻塞?

While playing with subprocesses and reading stdout through pipes I noticed interesting behaviour.

If I use an io.Pipe() to read the stdout of a subprocess created through os/exec, reading from that pipe hangs forever even when EOF is reached (the process is finished):

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

However, if I use the built-in method StdoutPipe() it works:

cmd := exec.Command("/bin/echo", "Hello, world!")
p := cmd.StdoutPipe()
cmd.Start()

io.Copy(os.Stdout, p) // Prints "Hello, World!" and returns

Digging into the source code of /usr/lib/go/src/os/exec/exec.go, I can see that the StdoutPipe() method actually uses os.Pipe(), not 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

This gives me two clues:

  1. File descriptors are being closed at certain points. Critically, the "write" end of the pipe is being closed after process start.
  2. Instead of io.Pipe() as I used above, os.Pipe() (a lower level call that roughly maps to pipe(2) in POSIX) is used.

However I am still unable to understand why my original example behaves the way it does after taking into account this newfound knowledge.

If I try to close the write end of an io.Pipe() (instead of an os.Pipe()) then it appears to break it completely and nothing gets read (as if I'm reading from a closed pipe even though I thought I passed it to the subprocess):

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

Okay, so I guess an io.Pipe() is quite different than an os.Pipe(), and probably doesn't behave like Unix pipes where one close() doesn't close it for everybody.

Just so you don't think I'm asking for a quick fix, I already know I can achieve my expected behaviour by using this code:

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. :-)

What I'm asking for is why does io.Pipe() seem to ignore an EOF from the writer, leaving the reader blocking forever? A valid answer could be that io.Pipe() is the wrong tool for the job because $REASONS but I can't figure out what those $REASONS are because according to the documentation what I'm trying to do seems perfectly reasonable.

Here is a complete example to illustrate what I'm talking about:

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()
}
  • 写回答

1条回答 默认 最新

  • dtg78700 2017-11-25 12:52
    关注

    "why does io.Pipe() seem to ignore an EOF from the writer, leaving the reader blocking forever?" Because there is no such thing as "EOF from the writer". All an EOF is (in unix) is an indication to the reader that no processes hold the write side of the pipe open. When a process attempts to read from a pipe which has no writers, the read system call returns a value that is conveniently named EOF. Since your parent still has one copy of the write side of the pipe open, read blocks. Stop thinking of EOF as a thing. It is merely an abstraction, and the writer never "sends" it.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 乌班图ip地址配置及远程SSH
  • ¥15 怎么让点阵屏显示静态爱心,用keiluVision5写出让点阵屏显示静态爱心的代码,越快越好
  • ¥15 PSPICE制作一个加法器
  • ¥15 javaweb项目无法正常跳转
  • ¥15 VMBox虚拟机无法访问
  • ¥15 skd显示找不到头文件
  • ¥15 机器视觉中图片中长度与真实长度的关系
  • ¥15 fastreport table 怎么只让每页的最下面和最顶部有横线
  • ¥15 java 的protected权限 ,问题在注释里
  • ¥15 这个是哪里有问题啊?