2017-11-25 117 views
2

在玩子流程和通過管道讀取stdout時,我注意到有趣的行爲。即使到達EOF時,爲什麼io.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 

這給了我兩條線索:

  1. 文件描述符在某些點被關閉。關鍵的是,過程開始後,管道的「寫入」結束關閉。
  2. 使用上面使用的os.Pipe()而不是io.Pipe()(POSIX中大致映射到pipe(2)的較低級調用)。

但是,我仍然無法理解爲什麼我的原始示例在考慮到這些新發現的知識後表現得如此。

如果我試圖關閉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() 
} 
+1

您的進程仍然會打開管道的寫入端。所以io.Copy會阻止你等待,直到你關閉它。這是預期的行爲。 –

+1

EOF尚未達到。 EOF是關閉管道時。 – JimB

回答

3

「?爲什麼io.Pipe()似乎忽略了從作家的EOF,留給讀者永遠堵」因爲沒有「作家的EOF」這樣的東西。所有的EOF(在unix中)都是向讀者指示沒有進程將管道的寫入側保持在打開狀態。當進程嘗試從沒有寫入者的管道讀取數據時,系統調用會返回一個名爲EOF的值。由於您的父級仍然有一個管道寫入側的副本打開,因此read塊。不要以爲EOF是一件事情。它僅僅是一種抽象,作家從不「發送」它。

相關問題