亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在 goroutine 中使用 exec.CommandContext 時如何調用 cancel()

在 goroutine 中使用 exec.CommandContext 時如何調用 cancel()

Go
偶然的你 2023-04-04 16:01:12
我想按需取消正在運行的命令,為此,我正在嘗試,exec.CommandContext目前正在嘗試:https://play.golang.org/p/0JTD9HKvyadpackage mainimport (    "context"    "log"    "os/exec"    "time")func Run(quit chan struct{}) {    ctx, cancel := context.WithCancel(context.Background())    cmd := exec.CommandContext(ctx, "sleep", "300")    err := cmd.Start()    if err != nil {        log.Fatal(err)    }    go func() {        log.Println("waiting cmd to exit")        err := cmd.Wait()        if err != nil {            log.Println(err)        }    }()    go func() {        select {        case <-quit:            log.Println("calling ctx cancel")            cancel()        }    }()}func main() {    ch := make(chan struct{})    Run(ch)    select {    case <-time.After(3 * time.Second):        log.Println("closing via ctx")        ch <- struct{}{}    }}我面臨的問題是被cancel()調用但進程沒有被殺死,我的猜測是主線程先退出并且不等待cancel()正確終止命令,主要是因為如果我time.Sleep(time.Second)在最后使用 amain它退出/終止正在運行的命令的功能。關于如何wait確保在不使用 a 退出之前命令已被終止的任何想法sleep?cancel()成功殺死命令后可以在頻道中使用嗎?在嘗試使用單個 goroutine 時,我嘗試了這個:https://play.golang.org/p/r7IuEtSM-gL但是cmd.Wait()似乎一直在阻塞select并且無法調用cancel()
查看完整描述

1 回答

?
慕容708150

TA貢獻1831條經驗 獲得超4個贊

在 Go 中,如果到達main方法的末尾(在包中)?,程序將停止。main此行為在 Go 語言規范中有關程序執行的部分(強調我自己的)下進行了描述:

程序執行從初始化main包開始,然后調用函數main。當該函數調用返回時,程序退出。它不會等待其他(非主)goroutines 完成。


缺陷

我將考慮您的每個示例及其相關的控制流缺陷。您會在下面找到指向 Go playground 的鏈接,但這些示例中的代碼不會在限制性 playground 沙箱中執行,因為sleep找不到可執行文件。復制粘貼到自己的環境進行測試。

多個 goroutine 示例

case?<-time.After(3?*?time.Second):
????????log.Println("closing?via?ctx")
????????ch?<-?struct{}{}

在計時器觸發并且您向 goroutine 發出信號是時候殺死孩子并停止工作之后,沒有什么可以導致方法main阻塞并等待它完成,所以它返回。根據語言規范,程序退出。

main調度程序可能會在通道傳輸后觸發,因此在退出和其他 goroutine 醒來接收來自之間可能存在競爭ch。然而,假設任何特定的行為交錯是不安全的——而且,出于實際目的,在main退出之前不太可能發生任何有用的工作。子sleep進程將成為孤兒;在 Unix 系統上,操作系統通常會將進程的父進程重放到進程上init

單個 goroutine 示例

在這里,你有相反的問題:main不返回,所以子進程沒有被殺死。這種情況只有在子進程退出時(5 分鐘后)才能解決。發生這種情況是因為:

  • cmd.Wait方法中的調用Run是阻塞調用 (?docs?)。該select語句被阻塞等待cmd.Wait返回錯誤值,因此無法從quit通道接收。

  • 通道quit(聲明為chmain是一個無緩沖通道。無緩沖通道上的發送操作將阻塞,直到接收方準備好接收數據。來自頻道的語言規范(再次強調我自己的):

    以元素數量表示的容量設置通道中緩沖區的大小。如果容量為零或不存在,則通道是無緩沖的,只有當發送方和接收方都準備好時,通信才會成功。

    由于Run在 中被阻塞cmd.Wait,沒有準備好的接收器來接收方法ch <- struct{}{}中的語句在通道上傳輸的值main。main塊等待傳輸此數據,從而阻止進程返回。

我們可以通過較小的代碼調整來演示這兩個問題。

cmd.Wait正在阻塞

要公開 的阻塞性質cmd.Wait,請聲明以下函數并使用它代替調用Wait。此函數是一個包裝器,具有與 相同的行為cmd.Wait,但有額外的副作用來打印 STDOUT 發生的情況。(游樂場鏈接):

func waitOn(cmd *exec.Cmd) error {

? ? fmt.Printf("Waiting on command %p\n", cmd)

? ? err := cmd.Wait()

? ? fmt.Printf("Returning from waitOn %p\n", cmd)

? ? return err

}


// Change the select statement call to cmd.Wait to use the wrapper

case e <- waitOn(cmd):

Waiting on command <pointer>運行此修改后的程序后,您將觀察到控制臺的輸出。計時器啟動后,您將觀察到輸出calling ctx cancel,但沒有相應的Returning from waitOn <pointer>文本。這只會在子進程返回時發生,您可以通過將睡眠持續時間減少到更小的秒數(我選擇了 5 秒)來快速觀察到這一點。

在退出頻道上發送,,ch

main無法返回,因為用于傳播退出請求的信號通道是無緩沖的,并且沒有相應的偵聽器。通過更改行:

????ch?:=?make(chan?struct{})

????ch?:=?make(chan?struct{},?1)

通道中的發送main將繼續(到通道的緩沖區)并main退出——與多 goroutine 示例相同的行為。然而,這個實現仍然是錯誤的:在返回之前,不會從通道的緩沖區中讀取值來真正開始停止子進程main,所以子進程仍然是孤立的。


固定版

我已經為你制作了一個固定版本,代碼如下。還有一些風格上的改進可以將您的示例轉換為更慣用的 go:

  • 不需要通過通道間接發出停止時間的信號。相反,我們可以通過將上下文和取消函數的聲明提升到方法來避免聲明通道main。上下文可以在適當的時候直接取消。

    我保留了單獨的Run函數來演示以這種方式傳遞上下文,但在許多情況下,它的邏輯可以嵌入到方法中main,并生成一個 goroutine 來執行cmd.Wait阻塞調用。

  • select方法中的語句是main不必要的,因為它只有一個case語句。

  • sync.WaitGroupmain引入是為了明確解決在子進程(在單獨的 goroutine 中等待)被殺死之前退出的問題。等待組實現了一個計數器;對塊的調用,Wait直到所有 goroutines 完成工作并調用Done.

package main


import (

? ? "context"

? ? "log"

? ? "os/exec"

? ? "sync"

? ? "time"

)


func Run(ctx context.Context) {

? ? cmd := exec.CommandContext(ctx, "sleep", "300")

? ? err := cmd.Start()

? ? if err != nil {

? ? ? ? // Run could also return this error and push the program

? ? ? ? // termination decision to the `main` method.

? ? ? ? log.Fatal(err)

? ? }


? ? err = cmd.Wait()

? ? if err != nil {

? ? ? ? log.Println("waiting on cmd:", err)

? ? }

}


func main() {

? ? var wg sync.WaitGroup

? ? ctx, cancel := context.WithCancel(context.Background())


? ? // Increment the WaitGroup synchronously in the main method, to avoid

? ? // racing with the goroutine starting.

? ? wg.Add(1)

? ? go func() {

? ? ? ? Run(ctx)

? ? ? ? // Signal the goroutine has completed

? ? ? ? wg.Done()

? ? }()


? ? <-time.After(3 * time.Second)

? ? log.Println("closing via ctx")

? ? cancel()


? ? // Wait for the child goroutine to finish, which will only occur when

? ? // the child process has stopped and the call to cmd.Wait has returned.

? ? // This prevents main() exiting prematurely.

? ? wg.Wait()

}


查看完整回答
反對 回復 2023-04-04
  • 1 回答
  • 0 關注
  • 211 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號