1 回答

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
(聲明為ch
)main
是一個無緩沖通道。無緩沖通道上的發送操作將阻塞,直到接收方準備好接收數據。來自頻道的語言規范(再次強調我自己的):以元素數量表示的容量設置通道中緩沖區的大小。如果容量為零或不存在,則通道是無緩沖的,只有當發送方和接收方都準備好時,通信才會成功。
由于
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.WaitGroup
main
引入是為了明確解決在子進程(在單獨的 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()
}
- 1 回答
- 0 關注
- 211 瀏覽
添加回答
舉報