Go語言中WaitGroup并發(fā)同步的實現(xiàn)
寫了十幾年代碼的Go后端老兵。今天聊聊WaitGroup,這個并發(fā)同步的小能手。
一、為什么需要WaitGroup
上周我們重構了一個數(shù)據(jù)導入功能,需要并發(fā)處理多個文件。但程序總是提前退出,有些文件還沒處理完就結束了。
這就是沒有做好并發(fā)同步的后果。WaitGroup可以幫我們等待所有goroutine完成。
二、WaitGroup的基本用法
1. 基礎示例
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // 增加計數(shù)器
go func(id int) {
defer wg.Done() // 減少計數(shù)器
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}(i)
}
wg.Wait() // 等待所有goroutine完成
fmt.Println("All workers done")
}2. 錯誤處理
func processFiles(files []string) error {
var wg sync.WaitGroup
errChan := make(chan error, len(files))
for _, file := range files {
wg.Add(1)
go func(f string) {
defer wg.Done()
if err := processFile(f); err != nil {
errChan <- err
}
}(file)
}
wg.Wait()
close(errChan)
for err := range errChan {
if err != nil {
return err
}
}
return nil
}三、WaitGroup的實戰(zhàn)技巧
1. 批量處理
func batchProcess(items []Item, batchSize int) {
var wg sync.WaitGroup
semaphore := make(chan struct{}, batchSize) // 限制并發(fā)數(shù)
for _, item := range items {
wg.Add(1)
semaphore <- struct{}{} // 獲取信號量
go func(i Item) {
defer wg.Done()
defer func() { <-semaphore }() // 釋放信號量
process(i)
}(item)
}
wg.Wait()
}2. 超時控制
func processWithTimeout(items []Item, timeout time.Duration) error {
var wg sync.WaitGroup
done := make(chan struct{})
go func() {
for _, item := range items {
wg.Add(1)
go func(i Item) {
defer wg.Done()
process(i)
}(item)
}
wg.Wait()
close(done)
}()
select {
case <-done:
return nil
case <-time.After(timeout):
return fmt.Errorf("timeout")
}
}3. 動態(tài)添加任務
func crawler(urls []string) {
var wg sync.WaitGroup
urlChan := make(chan string, 100)
// 啟動固定數(shù)量的worker
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for url := range urlChan {
newUrls := fetch(url)
for _, u := range newUrls {
urlChan <- u // 動態(tài)添加新URL
}
}
}()
}
// 發(fā)送初始URL
for _, url := range urls {
urlChan <- url
}
close(urlChan)
wg.Wait()
}四、WaitGroup的常見陷阱
1. Add和Done不匹配
// 錯誤:在goroutine內部調用Add
for i := 0; i < 5; i++ {
go func() {
wg.Add(1) // 錯誤!可能還沒Add就Wait了
defer wg.Done()
// ...
}()
}
wg.Wait()
// 正確:在啟動goroutine之前Add
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// ...
}()
}
wg.Wait()2. 復制WaitGroup
// 錯誤:WaitGroup被復制
func wrong(wg sync.WaitGroup) { // 值傳遞,復制了WaitGroup
wg.Done()
}
// 正確:傳遞指針
func right(wg *sync.WaitGroup) {
wg.Done()
}3. 重復調用Done
// 錯誤:可能調用多次Done
func bad() {
wg.Add(1)
go func() {
defer wg.Done()
if err := doSomething(); err != nil {
wg.Done() // 重復調用!
return
}
}()
}
// 正確:只調用一次
func good() {
wg.Add(1)
go func() {
defer wg.Done()
doSomething()
}()
}
五、WaitGroup vs Channel
| 場景 | 推薦方案 |
|---|---|
| 等待多個goroutine完成 | WaitGroup |
| 傳遞數(shù)據(jù) | Channel |
| 控制并發(fā)數(shù) | Channel(信號量) |
| 錯誤處理 | Channel + WaitGroup |
六、性能優(yōu)化
1. 避免過度并發(fā)
// 不好的做法:無限制并發(fā)
for _, item := range items {
wg.Add(1)
go func(i Item) {
defer wg.Done()
process(i)
}(item)
}
// 好的做法:限制并發(fā)數(shù)
semaphore := make(chan struct{}, 10)
for _, item := range items {
wg.Add(1)
semaphore <- struct{}{}
go func(i Item) {
defer wg.Done()
defer func() { <-semaphore }()
process(i)
}(item)
}2. 復用goroutine
// 使用worker pool
func workerPool(jobs []Job, workers int) {
var wg sync.WaitGroup
jobChan := make(chan Job, len(jobs))
// 啟動固定數(shù)量的worker
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobChan {
process(job)
}
}()
}
// 發(fā)送任務
for _, job := range jobs {
jobChan <- job
}
close(jobChan)
wg.Wait()
}
七、總結
WaitGroup是并發(fā)編程的必備工具,用好它可以:
- 優(yōu)雅地等待多個goroutine完成
- 實現(xiàn)復雜的并發(fā)控制邏輯
- 提高程序的性能和穩(wěn)定性
記?。耗芘芫托?,別折騰。但該用WaitGroup的時候,一定要用對。
到此這篇關于Go語言中WaitGroup并發(fā)同步的實現(xiàn)的文章就介紹到這了,更多相關Go語言 WaitGroup并發(fā)同步內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
從入門到精通淺析Go語言中如何處理JSON數(shù)據(jù)
JSON(JavaScript Object Notation)是一種輕量級的數(shù)據(jù)交換格式,廣泛應用于Web開發(fā)和API設計中,本文將詳細介紹Go語言中的JSON處理,從基礎用法到高級技巧,幫助開發(fā)者掌握JSON處理的各個方面2026-04-04
Go結合Gin導出Mysql數(shù)據(jù)到Excel表格
本文主要介紹了Go結合Gin導出Mysql數(shù)據(jù)到Excel表格,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08

