任何人都可以幫忙嗎?
我有一個通過 exec.CommandContext 運行的應用程式(所以我可以通過 ctx 取消它)。它通常不會停止,除非它出錯。
我目前將其輸出中繼到 os.stdOut ,效果很好。但我也想通過通道獲取每一行 - 這背后的想法是我將在該行上查找正則運算式,如果它為真,那么我將設定一個“錯誤”的內部狀態,例如。
雖然我無法讓它作業,但我嘗試了 NewSscanner。這是我的代碼。
正如我所說,它確實輸出到 os.StdOut,這很棒,但我想在我設定的頻道中接收每一行。
有任何想法嗎 ?
提前致謝。
func (d *Daemon) Start() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
go func() {
args := "-x f -a 1"
cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...)
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
lines := make(chan string)
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println("I am reading a line!")
lines <- scanner.Text()
}
}()
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
select {
case outputx := <-lines:
// I will do somethign with this!
fmt.Println("Hello!!", outputx)
case <-ctx.Done():
log.Println("I am done!, probably cancelled!")
}
}()
}
也嘗試使用這個
go func() {
scanner := bufio.NewScanner(&stdoutBuf)
for scanner.Scan() {
fmt.Println("I am reading a line!")
lines <- scanner.Text()
}
}()
即便如此,“我正在閱讀一行”永遠不會出來,我也除錯了它,它從來沒有進入“掃描儀......”
Also tried scanning on &stderrBuf, same, nothing enters.
uj5u.com熱心網友回復:
cmd.Start()不等待命令完成。此外,cmd.Wait()需要被呼叫以獲知該程序的結束。
reader, writer := io.Pipe()
cmdCtx, cmdDone := context.WithCancel(context.Background())
scannerStopped := make(chan struct{})
go func() {
defer close(scannerStopped)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
cmd := exec.Command("ls")
cmd.Stdout = writer
_ = cmd.Start()
go func() {
_ = cmd.Wait()
cmdDone()
writer.Close()
}()
<-cmdCtx.Done()
<-scannerStopped
scannerStopped 添加是為了演示掃描儀 goroutine 現在停止。
reader, writer := io.Pipe()
scannerStopped := make(chan struct{})
go func() {
defer close(scannerStopped)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
cmd := exec.Command("ls")
cmd.Stdout = writer
_ = cmd.Run()
go func() {
_ = cmd.Wait()
writer.Close()
}()
<-scannerStopped
并在有幫助時處理線條。
注意:寫這個有點匆忙。如果有任何不清楚或不正確的地方,請告訴我。
uj5u.com熱心網友回復:
對于使用并發和 goroutines 的正確程式,我們應該嘗試證明沒有資料競爭,程式不會死鎖,并且 goroutines 不會泄漏。讓我們努力實作這一目標。
完整代碼
游樂場: https : //play.golang.org/p/Xv1hJXYQoZq。我建議在本地復制和運行,因為操場不會流輸出 afaik 并且它有超時。
請注意,我已將 test 命令更改為% find /usr/local,這是一個典型的長時間運行命令(> 3 秒),帶有大量輸出行,因為它更適合我們應該測驗的場景。
演練
我們來看看Daemon.Start方法。一開始,情況大致相同。不過,最值得注意的是,新代碼在方法的大部分內容周圍沒有 goroutine。即使沒有這個,該Daemon.Start方法仍然是非阻塞的,并且會“立即”回傳。
第一個值得注意的修復是這些更新的行。
outR, outW := io.Pipe()
cmd.Stdout = io.MultiWriter(outW, os.Stdout)
我們沒有構造 bytes.Buffer 變數,而是呼叫io.Pipe. 如果我們沒有進行此更改并堅持使用 bytes.Buffer,那么scanner.Scan()一旦沒有更多資料要讀取,就會回傳 false。如果命令只是偶爾寫入 stdout(對于這個問題,即使相隔一毫秒),就會發生這種情況。之后scanner.Scan()回傳false,夠程退出,我們命中處理未來的產出。
相反,通過使用 的讀端io.Pipe,scanner.Scan()將等待來自管道讀端的輸入,直到管道的寫端關閉。
這解決了掃描儀和命令輸出之間的競爭問題。
接下來,我們構建兩個密切相關的 goroutine:第一個從 消費<-lines,第二個從生產到lines<-。
go func() {
for line := range lines {
fmt.Println("output line from channel:", line)
...
}
}()
go func() {
defer close(lines)
scanner := bufio.NewScanner(outR)
for scanner.Scan() {
lines <- scanner.Text()
}
...
}()
當lines通道關閉時,消費者 goroutine 將退出,因為通道關閉自然會導致范圍回圈終止;生產者 goroutinelines在退出時關閉。
生產者 goroutine 將在scanner.Scan()回傳 false時退出,這發生在io.Pipe關閉寫入端時。這種關閉發生在即將到來的代碼中。
Note from the two paragraphs above that the two goroutines are guaranteed to exit (i.e. will not leak).
Next, we start the command. Standard stuff, it's a non-blocking call, and it returns immediately.
// Start the command.
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
Moving on to the final piece of code in Daemon.Start. This goroutine waits for the command to exit via cmd.Wait(). Handling this is important because the command may for reasons other than Context cancellation.
Particularly, we want to close the write end of the io.Pipe (which, in turn, closes the output lines producer goroutine as mentioned earlier).
go func() {
err := cmd.Wait()
fmt.Println("command exited; error is:", err)
outW.Close()
...
}()
As a side note, by waiting on cmd.Wait(), we don't have to separately wait on ctx.Done(). Waiting on cmd.Wait() handles both exits caused by natural reasons (command successfully finished, command ran into internal error etc.) and exits caused by Context-cancelation.
This goroutine, too, is guaranteed to exit. It will exit when cmd.Wait() returns. This can happen either because the command exited normally with success; exited with failure due to a command error; or exited with failure due to Context cancelation.
That's it! We should have no data races, no deadlocks, and no leaked goroutines.
消隱(”線...“)在上面的代碼段被朝向齒輪Done(),CmdErr()以及Cancel()守護行程型別的方法。這些方法在代碼中得到了很好的記錄,因此這些省略的行希望是不言自明的。
除此之外,TODO根據您的需要查找您可能想要做的錯誤處理的注釋!
測驗一下!
使用此驅動程式來測驗代碼。
func main() {
var d Daemon
d.Start()
// Enable this code to test Context cancellation:
// time.AfterFunc(100*time.Millisecond, d.Cancel)
<-d.Done()
fmt.Println("d.CmdErr():", d.CmdErr())
}
uj5u.com熱心網友回復:
您必須掃描stdoutBuf而不是os.Stdin:
scanner := bufio.NewScanner(&stdoutBuf)
uj5u.com熱心網友回復:
當背景關系取消時,命令終止。如果在命令終止之前可以讀取命令的所有輸出,請使用以下代碼:
func (d *Daemon) Start() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
args := "-x f -a 1"
cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
go func() {
defer cmd.Wait()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
s := scanner.Text()
fmt.Println(s) // echo to stdout
// Do something with s
}
}()
}
當背景關系被取消時,命令終止。當命令終止時,
Read onstdout回傳 io.EOF。當stdout回傳錯誤時,goroutine 會跳出掃描回圈。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/358299.html
