Golang如何使用WaitGroup Golang并發同步詳解

waitgroup用于等待一組goroutine完成。其核心是通過add()增加計數器,done()減少計數器(等價于add(-1)),wait()阻塞主goroutine直到計數器歸零。使用時應在啟動goroutine前調用add(),并在每個goroutine中使用defer wg.done()確保計數器正確減少。避免錯誤的方法包括:使用defer確保done()調用、通過指針傳遞waitgroup、借助工具審查代碼。與channel相比,waitgroup適用于僅需等待完成而無需數據傳遞的場景,channel則適合需要數據傳輸或復雜同步的情況。結合context可控制goroutine生命周期,如通過context.withcancel()實現優雅退出。

Golang如何使用WaitGroup Golang并發同步詳解

WaitGroup用于等待一組goroutine完成。你可以理解為一個計數器,每啟動一個goroutine,計數器加一,goroutine執行完畢,計數器減一。主goroutine調用Wait()方法阻塞,直到計數器變為零。

Golang如何使用WaitGroup Golang并發同步詳解

解決方案:

Golang如何使用WaitGroup Golang并發同步詳解

golang中使用WaitGroup非常簡單,主要涉及三個方法:Add(), Done(), 和 Wait()。

立即學習go語言免費學習筆記(深入)”;

  1. Add(delta int):增加WaitGroup的計數器。通常在啟動goroutine之前調用。delta可以是正數也可以是負數,但通常是正數,表示新增的goroutine數量。
  2. Done():減少WaitGroup的計數器。在goroutine執行完畢后調用。相當于Add(-1)。
  3. Wait():阻塞調用它的goroutine(通常是主goroutine),直到WaitGroup的計數器變為零。

下面是一個簡單的例子:

Golang如何使用WaitGroup Golang并發同步詳解

package main  import (     "fmt"     "sync"     "time" )  func worker(id int, wg *sync.WaitGroup) {     defer wg.Done() // 確保goroutine退出時計數器減一     fmt.Printf("Worker %d startingn", id)     time.Sleep(time.Second) // 模擬耗時操作     fmt.Printf("Worker %d donen", id) }  func main() {     var wg sync.WaitGroup      for i := 1; i <= 3; i++ {         wg.Add(1) // 啟動一個goroutine,計數器加一         go worker(i, &wg)     }      wg.Wait() // 阻塞直到所有goroutine完成     fmt.Println("All workers done") }

這個例子創建了三個worker goroutine,每個goroutine休眠一秒后退出。主goroutine等待所有worker goroutine完成后才退出。

WaitGroup的零值是有效的,意味著你可以直接聲明一個sync.WaitGroup類型的變量,而不需要進行初始化。

WaitGroup的計數器不能為負數。如果計數器變為負數,會panic。因此,確保Add()的調用次數與Done()的調用次數匹配。

如何避免WaitGroup使用中的常見錯誤?

使用WaitGroup時,最常見的錯誤包括:

  • 忘記Done():如果goroutine忘記調用Done(),WaitGroup將永遠不會變為零,導致Wait()永久阻塞。
  • Done()調用次數過多:如果Done()的調用次數超過Add()的調用次數,會導致panic。
  • Add()和goroutine啟動順序問題:如果Add()在goroutine啟動之后調用,可能導致WaitGroup計數器不準確。應該始終在啟動goroutine之前調用Add()。
  • WaitGroup傳遞問題:WaitGroup應該通過指針傳遞,而不是值傳遞。值傳遞會導致每個goroutine都操作的是WaitGroup的副本,而不是同一個WaitGroup。

為了避免這些錯誤,可以考慮使用defer語句來確保Done()被調用,并且仔細檢查Add()和Done()的調用次數是否匹配。同時,使用代碼審查工具可以幫助發現潛在的錯誤。

WaitGroup和channel有什么區別?何時使用哪個?

WaitGroup和channel都是用于goroutine同步的機制,但它們的使用場景有所不同。

  • WaitGroup主要用于等待一組goroutine完成。它不涉及數據的傳遞,只關注goroutine的完成狀態。
  • Channel主要用于goroutine之間的數據傳遞和同步。它可以用于等待單個或多個goroutine,也可以用于在goroutine之間傳遞數據。

通常情況下,如果只需要等待一組goroutine完成,而不需要傳遞數據,那么WaitGroup是更簡單和高效的選擇。如果需要在goroutine之間傳遞數據,或者需要更復雜的同步邏輯,那么channel是更好的選擇。

例如,可以使用channel來收集多個goroutine的結果,或者使用channel來實現一個工作池。

package main  import (     "fmt"     "sync" )  func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {     defer wg.Done()     for j := range jobs {         fmt.Printf("Worker %d processing job %dn", id, j)         results <- j * 2     } }  func main() {     numJobs := 5     jobs := make(chan int, numJobs)     results := make(chan int, numJobs)     var wg sync.WaitGroup      for i := 1; i <= 3; i++ {         wg.Add(1)         go worker(i, jobs, results, &wg)     }      for j := 1; j <= numJobs; j++ {         jobs <- j     }     close(jobs)      wg.Wait()     close(results)      for a := range results {         fmt.Println(a)     } }

這個例子使用了channel來傳遞任務和結果,同時使用WaitGroup來等待所有worker goroutine完成。

如何使用context控制goroutine的生命周期?

Context可以用于控制goroutine的生命周期,例如取消goroutine的執行。結合WaitGroup和context,可以實現更復雜的goroutine管理。

可以使用context.WithCancel()創建一個可取消的context。當調用cancel()函數時,所有監聽該context的goroutine都會收到取消信號。

package main  import (     "context"     "fmt"     "sync"     "time" )  func worker(ctx context.Context, id int, wg *sync.WaitGroup) {     defer wg.Done()     fmt.Printf("Worker %d startingn", id)     defer fmt.Printf("Worker %d donen", id)      for {         select {         case <-ctx.Done():             fmt.Printf("Worker %d cancelledn", id)             return         default:             fmt.Printf("Worker %d workingn", id)             time.Sleep(time.Millisecond * 500)         }     } }  func main() {     var wg sync.WaitGroup     ctx, cancel := context.WithCancel(context.Background())      for i := 1; i <= 3; i++ {         wg.Add(1)         go worker(ctx, i, &wg)     }      time.Sleep(time.Second * 2)     fmt.Println("Cancelling context")     cancel()      wg.Wait()     fmt.Println("All workers done") }

這個例子創建了三個worker goroutine,每個goroutine會循環執行,直到收到取消信號。主goroutine在2秒后取消context,導致所有worker goroutine退出。WaitGroup用于等待所有worker goroutine退出。

使用context可以更優雅地控制goroutine的生命周期,避免資源泄漏和死鎖。

以上就是Golang如何使用W

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