Go語言動態(tài)并發(fā)控制sync.WaitGroup的靈活運用示例詳解
概述
在并發(fā)編程中,控制主程序等待所有 Goroutine 完成任務(wù)是一項關(guān)鍵任務(wù)。Go 語言提供了 sync.WaitGroup 來解決這一問題。
1. 基本使用
1.1 初始化和添加計數(shù)
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go worker(i, &wg) } wg.Wait() fmt.Println("All workers have completed.") } func worker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d started\n", id) time.Sleep(2 * time.Second) fmt.Printf("Worker %d completed\n", id) }
在上面示例中,用一個 sync.WaitGroup 實例 wg,然后使用 wg.Add(1) 來增加計數(shù),表示有一個 Goroutine 需要等待。
在每個 Goroutine 的結(jié)束處,使用 defer wg.Done() 來減少計數(shù),表示一個 Goroutine 已完成。
最后,用 wg.Wait() 來等待所有 Goroutine 完成。
1.2 處理錯誤
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go workerWithError(i, &wg) } wg.Wait() fmt.Println("All workers have completed.") } func workerWithError(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d started\n", id) time.Sleep(2 * time.Second) // 模擬錯誤發(fā)生 if id == 2 { fmt.Printf("Worker %d encountered an error\n", id) return } fmt.Printf("Worker %d completed\n", id) }
有時候,需要在 Goroutine 中處理錯誤。在這個示例中,當(dāng) id 為 2 時,模擬了一個錯誤的情況。
通過在錯誤發(fā)生時提前返回,可以確保計數(shù)正確減少,避免等待組出現(xiàn)死鎖。
2. 多級等待組
2.1 嵌套使用
package main import ( "fmt" "sync" "time" ) func main() { var outerWG sync.WaitGroup var innerWG sync.WaitGroup for i := 1; i <= 2; i++ { outerWG.Add(1) go outerWorker(i, &outerWG, &innerWG) } outerWG.Wait() fmt.Println("All outer workers have completed.") } func outerWorker(id int, outerWG, innerWG *sync.WaitGroup) { defer outerWG.Done() fmt.Printf("Outer Worker %d started\n", id) for j := 1; j <= 3; j++ { innerWG.Add(1) go innerWorker(id, j, innerWG) } innerWG.Wait() fmt.Printf("Outer Worker %d completed\n", id) } func innerWorker(outerID, innerID int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Inner Worker %d of Outer Worker %d started\n", innerID, outerID) time.Sleep(2 * time.Second) fmt.Printf("Inner Worker %d of Outer Worker %d completed\n", innerID, outerID) }
在示例中,使用了嵌套的 sync.WaitGroup。
外部的等待組 outerWG 等待所有外部 Goroutine 完成,而每個外部 Goroutine 內(nèi)部的 innerWG 則等待其內(nèi)部的所有 Goroutine 完成。
2.2 動態(tài)添加等待組
package main import ( "fmt" "sync" "time" ) func main() { var dynamicWG sync.WaitGroup for i := 1; i <= 3; i++ { dynamicWG.Add(1) go dynamicWorker(i, &dynamicWG) } // 模擬動態(tài)添加更多任務(wù) time.Sleep(1 * time.Second) for i := 4; i <= 6; i++ { dynamicWG.Add(1) go dynamicWorker(i, &dynamicWG) } dynamicWG.Wait() fmt.Println("All dynamic workers have completed.") } func dynamicWorker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Dynamic Worker %d started\n", id) time.Sleep(2 * time.Second) fmt.Printf("Dynamic Worker %d completed\n", id) }
在上述示例中,創(chuàng)建了一個等待組 dynamicWG,然后在運行時動態(tài)添加了更多的任務(wù)。
用這種方式,可以動態(tài)地管理需要等待的 Goroutine 數(shù)量。
3. 超時處理
3.1 帶超時的等待
package main import ( "fmt" "sync" "time" ) func main() { var timeoutWG sync.WaitGroup for i := 1; i <= 3; i++ { timeoutWG.Add(1) go timeoutWorker(i, &timeoutWG) } // 等待最多5秒,超時則不再等待 timeout := time.After(5 * time.Second) done := make(chan struct{}) go func() { timeoutWG.Wait() close(done) }() select { case <-done: fmt.Println("All timeout workers have completed.") case <-timeout: fmt.Println("Timeout reached. Not all workers have completed.") } } func timeoutWorker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Timeout Worker %d started\n", id) time.Sleep(time.Duration(id) * time.Second) fmt.Printf("Timeout Worker %d completed\n", id) }
在上面示例中,用 time.After 創(chuàng)建了一個 5 秒的超時通道。
在另一個 Goroutine 中監(jiān)聽等待組的完成情況,可以在超時或任務(wù)完成時得知等待的最終結(jié)果。
3.2 處理超時錯誤
package main import ( "errors" "fmt" "sync" "time" ) func main() { var timeoutWG sync.WaitGroup for i := 1; i <= 3; i++ { timeoutWG.Add(1) go timeoutWorkerWithError(i, &timeoutWG) } // 等待最多5秒,超時則返回錯誤 err := waitWithTimeout(&timeoutWG, 5*time.Second) if err != nil { fmt.Printf("Timeout reached. Not all workers have completed. Error: %v\n", err) } else { fmt.Println("All timeout workers have completed.") } } func timeoutWorkerWithError(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Timeout Worker %d started\n", id) time.Sleep(time.Duration(id) * time.Second) // 模擬錯誤發(fā)生 if id == 2 { fmt.Printf("Timeout Worker %d encountered an error\n", id) return } fmt.Printf("Timeout Worker %d completed\n", id) } func waitWithTimeout(wg *sync.WaitGroup, timeout time.Duration) error { done := make(chan struct{}) go func() { defer close(done) wg.Wait() }() select { case <-done: return nil case <-time.After(timeout): return errors.New("timeout reached") } }
有時候,希望在程序超時的時候返回一個錯誤。
在這個示例中,用封裝等待組的超時檢查,可以在主程序中獲得一個清晰的錯誤提示。
總結(jié)
通過討論 sync.WaitGroup 的基本用法、避免常見錯誤以及實際應(yīng)用,深入了解了這個強大的同步工具。
在 Go 語言并發(fā)編程中,合理使用 sync.WaitGroup 能夠優(yōu)雅地處理并發(fā)等待,確保主程序在所有任務(wù)完成后再繼續(xù)執(zhí)行。
以上就是Go語言動態(tài)并發(fā)控制sync.WaitGroup的靈活運用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go sync.WaitGroup動態(tài)并發(fā)控制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Golang函數(shù)式選項(Functional?Options)模式
什么是函數(shù)式選項模式,為什么要這么寫,這個編程模式解決了什么問題呢?其實就是為了解決動態(tài)靈活的配置不同的參數(shù)的問題。下面通過本文給大家介紹Golang函數(shù)式選項(Functional?Options)模式的問題,感興趣的朋友一起看看吧2021-12-12Golang使用gob實現(xiàn)結(jié)構(gòu)體的序列化過程詳解
Golang struct類型數(shù)據(jù)序列化用于網(wǎng)絡(luò)傳輸數(shù)據(jù)或在磁盤上寫入數(shù)據(jù)。在分布式系統(tǒng)中,一端生成數(shù)據(jù)、然后序列化、壓縮和發(fā)送;在另一端,接收數(shù)據(jù)、然后解壓縮、反序列化和處理數(shù)據(jù),整個過程必須快速有效2023-03-03Go切片導(dǎo)致rand.Shuffle產(chǎn)生重復(fù)數(shù)據(jù)的原因與解決方案
在 Go 語言的實際開發(fā)中,切片(slice)是一種非常靈活的數(shù)據(jù)結(jié)構(gòu),然而,由于其底層數(shù)據(jù)共享的特性,在某些情況下可能會導(dǎo)致意想不到的 Bug,本文將詳細(xì)分析 rand.Shuffle 之后,切片中的數(shù)據(jù)出現(xiàn)重復(fù)的問題,探討其根本原因,并給出最佳解決方案,需要的朋友可以參考下2025-02-02執(zhí)行g(shù)o?build報錯go:?go.mod?file?not?found?in?current?dir
本文主要為大家介紹了執(zhí)行g(shù)o build報錯go:?go.mod?file?not?found?in?current?directory?or?any?parent?directory解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Golang算法問題之?dāng)?shù)組按指定規(guī)則排序的方法分析
這篇文章主要介紹了Golang算法問題之?dāng)?shù)組按指定規(guī)則排序的方法,結(jié)合實例形式分析了Go語言數(shù)組排序相關(guān)算法原理與操作技巧,需要的朋友可以參考下2017-02-02