io.pipe会阻塞,而os.pipe()不会阻塞

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值