在玩子流程和通過管道讀取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
這給了我兩條線索:
- 文件描述符在某些點被關閉。關鍵的是,過程開始後,管道的「寫入」結束關閉。
- 使用上面使用的
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()
}
您的進程仍然會打開管道的寫入端。所以io.Copy會阻止你等待,直到你關閉它。這是預期的行爲。 –
EOF尚未達到。 EOF是關閉管道時。 – JimB