2017-04-16 88 views
1

我在這裏遇到問題,無法安全退出goroutine。如何停止由進程啓動的外部I/O阻塞的goroutine?

我在使用exec.Command創建一個外部進程(存儲CMD,標準輸入管和該方法的標準輸出管):

exec.Command(args[0], args[1]...) // args[0] is a base command 

每當有必要啓動過程我打電話:

cmd.Start() 

然後在啓動和附加我跑2個夠程:

shutdown := make(chan struct{}) 
// Run the routine which will read from process and send the data to CmdIn channel 
go pr.cmdInRoutine() 
// Run the routine which will read from CmdOut and write to process 
go pr.cmdOutRoutine() 

cmdInRo utine:

func (pr *ExternalProcess) cmdInRoutine() { 
    app.At(te, "cmdInRoutine") 

    for { 
     println("CMDINROUTINE") 
     select { 
     case <-pr.shutdown: 
      println("!!! Shutting cmdInRoutine down !!!") 
      return 
     default: 
      println("Inside the for loop of the CmdInRoutine") 
      if pr.stdOutPipe == nil { 
       println("!!! Standard output pipe is nil. Sending Exit Request !!!") 
       pr.ProcessExit <- true 
       close(pr.shutdown) 
       return 
      } 

      buf := make([]byte, 2048) 

      size, err := pr.stdOutPipe.Read(buf) 
      if err != nil { 
       println("!!! Sending exit request from cmdInRoutine !!!") 
       pr.ProcessExit <- true 
       close(pr.shutdown) 
       return 
      } 

      println("--- Received data for sending to CmdIn:", string(buf[:size])) 
      pr.CmdIn <- buf[:size] 
     } 

    } 
} 

cmdOutRoutine:

func (pr *ExternalProcess) cmdOutRoutine() { 
    app.At(te, "cmdOutRoutine") 

    for { 
     select { 
     case <-pr.shutdown: 
      println("!!! Shutting cmdOutRoutine down !!!") 
      return 
     case data := <-pr.CmdOut: 
      println("Received data for sending to Process: ", data) 
      if pr.stdInPipe == nil { 
       println("!!! Standard input pipe is nil. Sending Exit Request !!!") 
       pr.ProcessExit <- true 
       return 
      } 

      println("--- Received input to write to external process:", string(data)) 
      _, err := pr.stdInPipe.Write(append(data, '\n')) 
      if err != nil { 
       println("!!! Couldn't Write To the std in pipe of the process !!!") 
       pr.ProcessExit <- true 
       return 
      } 
     } 
    } 
} 

有趣的情況在這裏:

1)當進程發送EOF(不介意pr.ProcessExit < - 真我被通知到父處理程序使用通道來停止並退出進程)cmdInRoutine我也關閉了關閉通道,這讓cmdOutRouti ne退出,因爲在select語句中不存在默認情況,所以它會阻止並等待退出或等待使用存儲的stdInPipe寫入正在運行的進程的數據。

2)當我想停止goroutines,但離開進程運行,即暫停讀取和寫入我正在關閉關閉通道,希望這2個goroutines將結束。
- cmdOutRoutine打印!!!關閉cmdOutRoutine!因爲選擇不具有默認情況下並關閉關閉通道使返回幾乎立即
- cmdOutRoutine沒有打印任何東西,我有一種奇怪的感覺,它甚至沒有回來,我想是因爲它擋在默認情況下從stdInPipe讀取。

我想之前在for循環運行cmdOutRoutine內的另一個夠程,並有標準輸入轉換成通道的過程數據,那麼我將能夠在消除默認情況下cmdInRoutine但是這創造了另一個問題,新的goroutine也必須停止,它仍然會被正在運行的進程的stdIn中的讀取屏蔽。

任何想法如何解決這個問題(修改邏輯)以滿足隨時關閉和啓動goroutines(進程I/O)而不是運行進程本身的需求?或者有沒有辦法避免阻止讀和寫的調用,我還沒有意識到?

非常感謝。

+0

你有兩個獨立的程序運行時,你應該使用兩個獨立的關斷通道。只有像你這樣做的人,你不知道哪一個會首先從通道收集關閉信號,而另一個則會阻塞。 – RayfenWindspear

+0

另請注意,命令運行後無法重新使用https://golang.org/pkg/os/exec/#Cmd – RayfenWindspear

+1

@RayfenWindspear可以使用單個關閉通道。從關閉的通道讀取將立即返回零值,因此兩個goroutine都會收集關閉信號。 – user1431317

回答

2

它可能被阻止在pr.stdOutPipe.Read(buf)。您可以嘗試關閉pr.stdOutPipe,這會中斷Read。

您還可以關閉pr.stdInPipe,確保寫入不會被阻止。

編輯:這將不允許您重新附加,但沒有其他方式來中斷該讀取。最好保留這兩個goroutine運行整個進程,並暫停堆棧中的其他位置(例如,如果你不想在暫停狀態下接收命令的輸出,不要寫bufpr.CmdIn - 但是小心避免競賽狀況)。

在當前版本的go中,close可能是buggy:issue 6817

編輯

同樣的結束,要小心pr.CmdIn。如果關閉stdOutPipe不會導致Read返回錯誤,則cmdInRoutine將嘗試寫入通道。如果沒有從它讀取,cmdInRoutine將永遠阻止。我會提出pr.stdOutPipe.Read(buf)出選擇的,那麼作爲另一種情況增加pr.CmdIn <- buf[:size]的選擇:

func (pr *ExternalProcess) cmdInRoutine() { 
    app.At(te, "cmdInRoutine") 

    // this check should probably only happen once. 
    // if stdOutPipe can change during the loop, 
    // then that's a race condition. 
    if pr.stdOutPipe == nil { 
     println("!!! Standard output pipe is nil. Sending Exit Request !!!") 
     pr.ProcessExit <- true 
     close(pr.shutdown) 
     return 
    } 

    for { 
     println("CMDINROUTINE") 
     // we need to allocate a new buffer in each iteration, 
     // because when we pass it through the channel, 
     // we can no longer safely overwrite the data in it, 
     // since the other goroutine might still be using it. 
     buf := make([]byte, 2048) 
     size, err := pr.stdOutPipe.Read(buf) 
     if err != nil { 
      println("!!! Sending exit request from cmdInRoutine !!!") 
      // Be careful with this, if you also closed pr.shutdown when you closed stdOutPipe, then this is going to panic (closing a closed channel). 
      pr.ProcessExit <- true 
      close(pr.shutdown) 
      return 
     } 

     // now that we have some data, try to send it, 
     // unless we're done. 
     select { 
     case <-pr.shutdown: 
      println("!!! Shutting cmdInRoutine down !!!") 
      return 
     case pr.CmdIn <- buf[:size]: 
      println("--- Received data for sending to CmdIn:", string(buf[:size])) 
     } 
    } 
} 
+1

我想他不想關閉管道。他提到通過關閉goroutines來暫停'cmd',所以我認爲關閉它們不是一種選擇。我的兩分錢。我喜歡在頻道的情況下,偉大的想法。 – RayfenWindspear

+0

你是對的,我編輯了我的答案。 – user1431317

+0

@ user1431317感謝您的回答。起初,我有for循環外的緩衝區,但然後數據(字節)混合從我認爲連續讀取。我不確定** buf [:] **的行爲如何。我要試試這個,我會讓你知道它是怎麼回事。 –