如何在Golang中限制協程數量時避免死鎖?

如何在Golang中限制協程數量時避免死鎖?

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語言免費學習筆記(深入)”;

解決方案

為了避免死鎖,需要進行如下修改:

  1. 移除defer close(creport): 在原代碼中,creport通道的關閉操作永遠無法執行到。

  2. 移除主協程中的循環接收: 主協程中的循環接收creport與子協程的功能重復,且是死循環,導致creport關閉后仍然嘗試讀取,從而死鎖。

  3. 在wg.Wait()之后關閉creport: 確保所有協程完成后再關閉creport通道,避免死鎖。

修改后的代碼如下:

// ... (其它代碼與前面相同) ...  func main() {     // ... (其它代碼與前面相同) ...      wg.Wait() // 等待所有協程完成     close(creport) // 在wg.Wait()之后關閉creport通道      // 處理creport中的數據     for bag := range creport {         fmt.Println(bag)     } }

通過這些修改,既能限制協程數量,又能避免死鎖,確保程序的正確運行。 關鍵在于正確地管理協程生命周期和通道的關閉時機。

? 版權聲明
THE END
喜歡就支持一下吧
點贊14 分享