我一直在研究 Golang,并通過其創新的 goroutines 構造來了解它的并發性以及它的僅協程通道模型的實施有多好。我立即發現麻煩的一件事是該方法的使用Wait(),該方法用于等待父 goroutine 內產生的多個未完成的 goroutine 完成。引用Golang 文檔等待可用于阻塞,直到所有 goroutine 完成事實上,許多 Go 開發人員將其規定 Wait()為實現并發的首選方式,這似乎與 Golang 使開發人員能夠編寫高效軟件的使命背道而馳,因為阻塞是低效的,而真正的異步代碼永遠不會阻塞。阻塞的進程 [或線程] 是等待某個事件的進程,例如資源變得可用或 I/O 操作完成。換句話說,阻塞的線程將花費 CPU 周期做無用的事情,只是反復檢查當前運行的任務是否可以停止等待并繼續執行。在真正的異步代碼中,當協程遇到無法繼續執行直到結果到達的情況時,它必須將其執行交給調度程序而不是阻塞,方法是將其狀態從running切換到waiting,以便調度程序可以開始執行下一個 -來自可運行隊列的內聯協程。只有當它需要的結果到達時,等待協程才應該將其狀態從等待更改為可運行。因此,由于Wait()阻塞直到 x 數量的 goroutine 被調用Done(),調用的 goroutineWait()將始終保持在可運行或運行狀態,浪費 CPU 周期并依賴調度程序搶占長時間運行的 goroutine 只是將其狀態從運行更改為可運行,而不是將其更改為應有的等待狀態。如果這一切都是真的,并且我理解如何Wait()正確工作,那么為什么人們不使用內置的 Go 通道來完成等待子 goroutines 完成的任務?如果我理解正確,發送到緩沖通道和從任何通道讀取都是異步操作,這意味著調用它們會使 goroutine 進入等待狀態,那么為什么它們不是首選方法呢?我引用的文章給出了幾個例子。以下是作者所說的“老派”方式:package mainimport ( "fmt" "time")func main() { messages := make(chan int) go func() { time.Sleep(time.Second * 3) messages <- 1 }() go func() { time.Sleep(time.Second * 2) messages <- 2 }() go func() { time.Sleep(time.Second * 1) messages <- 3 }() for i := 0; i < 3; i++ { fmt.Println(<-messages) }}這是首選的“規范”方式:package mainimport ( "fmt" "sync" "time")func main() { messages := make(chan int) var wg sync.WaitGroup wg.Add(3) go func() { defer wg.Done() time.Sleep(time.Second * 3) messages <- 1 }() go func() { defer wg.Done() time.Sleep(time.Second * 2) messages <- 2 }() go func() { defer wg.Done() time.Sleep(time.Second * 1) messages <- 3 }() wg.Wait() for i := range messages { fmt.Println(i) }}我可以理解第二個可能比第一個更容易理解,但第一個是異步的,沒有協程阻塞,第二個有一個阻塞的協程:運行主函數的協程。這是另一個Wait()被普遍接受的方法的例子。如果創建低效的阻塞線程,為什么 Go 社區不Wait()將其視為反模式?為什么在這種情況下通道不是大多數人首選的,因為它們可以用來保持所有代碼異步和線程優化?
1 回答

ibeautiful
TA貢獻1993條經驗 獲得超6個贊
您對“阻塞”的理解不正確。諸如WaitGroup.Wait()
或通道接收之類的阻塞操作(當沒有要接收的值時)只會阻塞 goroutine 的執行,它們不會(必然)阻塞用于執行 goroutine(的語句)的 OS 線程。
每當遇到阻塞操作(例如上面提到的)時,goroutine 調度程序可能(并且它會)切換到另一個可能繼續運行的 goroutine。在調用期間沒有(重要的)CPU 周期丟失WaitGroup.Wait()
,如果有其他 goroutines 可能繼續運行,它們將會。
- 1 回答
- 0 關注
- 181 瀏覽
添加回答
舉報
0/150
提交
取消