golang協程數量限制與死鎖避免
在go語言編程中,限制并發協程數量是常見需求,但稍有不慎就會導致死鎖(fatal Error: all goroutines are asleep – deadlock!)。本文探討如何安全地限制協程數量,避免死鎖。
問題描述
使用sync.WaitGroup管理協程生命周期,并通過通道限制并發協程數,可能出現死鎖。 問題源于協程間數據傳輸和協程數量控制機制的沖突。
代碼示例及問題分析
以下代碼片段展示了可能導致死鎖的場景:
package main import ( "fmt" "log" "math/rand" "sync" "time" ) // ... (Row, Report, Bag 結構體定義,與原文相同) ... func GetRows() (rows []Row) { // ... (GetRows 函數實現,與原文相同) ... } func processRow(row Row, c chan struct{}, creport chan Bag) { defer func() { <-c // 釋放協程槽位 }() // ... (處理row邏輯,產生Bag數據,與原文相同) ... creport <- bag // 發送報告結果 } func main() { c := make(chan struct{}, 10) // 限制同時運行的協程數量為10 creport := make(chan Bag) var wg sync.WaitGroup rows := GetRows() for _, row := range rows { c <- struct{}{} // 獲取協程槽位 wg.Add(1) go func(row Row) { defer wg.Done() processRow(row, c, creport) }(row) } // 以下循環會導致死鎖 //go func() { // for bag := range creport { // fmt.Println(bag) // } //}(creport) wg.Wait() // 等待所有協程完成 close(creport) // 關閉creport通道,避免死鎖 //處理creport中的數據 for bag := range creport { fmt.Println(bag) } }
原代碼中defer close(creport)和主協程中的死循環接收creport通道數據是導致死鎖的關鍵。
立即學習“go語言免費學習筆記(深入)”;
解決方案
為了避免死鎖,需要進行如下修改:
-
移除defer close(creport): 在原代碼中,creport通道的關閉操作永遠無法執行到。
-
移除主協程中的循環接收: 主協程中的循環接收creport與子協程的功能重復,且是死循環,導致creport關閉后仍然嘗試讀取,從而死鎖。
-
在wg.Wait()之后關閉creport: 確保所有協程完成后再關閉creport通道,避免死鎖。
修改后的代碼如下:
// ... (其它代碼與前面相同) ... func main() { // ... (其它代碼與前面相同) ... wg.Wait() // 等待所有協程完成 close(creport) // 在wg.Wait()之后關閉creport通道 // 處理creport中的數據 for bag := range creport { fmt.Println(bag) } }
通過這些修改,既能限制協程數量,又能避免死鎖,確保程序的正確運行。 關鍵在于正確地管理協程生命周期和通道的關閉時機。
? 版權聲明
文章版權歸作者所有,未經允許請勿轉載。
THE END