從并發(fā)到并行解析Go語(yǔ)言中的sync.WaitGroup
在并發(fā)編程中,協(xié)調(diào)多個(gè) goroutine 的執(zhí)行是一個(gè)常見(jiàn)的需求。Go 語(yǔ)言提供了許多工具和機(jī)制來(lái)實(shí)現(xiàn)并發(fā)編程,其中之一就是 sync.WaitGroup。本文將深入討論 sync.WaitGroup,探索其工作原理和在實(shí)際應(yīng)用中的使用方法。
1. 理解并發(fā)與并行
在開(kāi)始介紹 sync.WaitGroup 之前,我們需要先了解并發(fā)和并行的概念。并發(fā)是指多個(gè)任務(wù)交替進(jìn)行,通過(guò)時(shí)間片輪轉(zhuǎn)或者調(diào)度算法進(jìn)行切換,從而給用戶一種同時(shí)執(zhí)行的感覺(jué)。而并行是指多個(gè)任務(wù)同時(shí)進(jìn)行,利用多核處理器或者分布式系統(tǒng)的計(jì)算能力,實(shí)現(xiàn)真正的同時(shí)執(zhí)行。
2. sync.WaitGroup 的作用和用法
sync.WaitGroup 是 Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的一個(gè)結(jié)構(gòu)體,用于等待一組 goroutine 完成執(zhí)行。它的主要作用是等待所有的 goroutine 完成后再繼續(xù)執(zhí)行下一步操作,以避免主程序過(guò)早退出。
2.1 sync.WaitGroup 結(jié)構(gòu)體的定義
sync.WaitGroup 的定義如下:
type WaitGroup struct {
noCopy noCopy
?
// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
// Wait 調(diào)用時(shí),counter 自增;Done 調(diào)用時(shí),counter 自減;WaitGroup 的值可以使用 Add 和 Done 方法增減。
// 當(dāng) counter 為零時(shí),所有等待的 goroutine 都會(huì)被喚醒。
// 因?yàn)?counter 是 int64 類型,所以 WaitGroup 最多支持 2^32 個(gè) goroutine。
// 如果 counter 為負(fù)數(shù),會(huì)導(dǎo)致 panic。
state1 [3]uint32
}sync.WaitGroup 結(jié)構(gòu)體中的 state1 字段包含了一個(gè) counter 計(jì)數(shù)器,用于記錄等待的 goroutine 數(shù)量。
2.2 sync.WaitGroup 的方法
sync.WaitGroup 提供了以下幾個(gè)方法:
2.2.1 Add 方法
Add 方法用于向 WaitGroup 中添加指定數(shù)量的等待的 goroutine。它的定義如下:
func (wg *WaitGroup) Add(delta int)
其中,delta 表示要添加的等待的 goroutine 的數(shù)量。Add 方法會(huì)將 delta 值加到 counter 上。
2.2.2 Done 方法
Done 方法用于標(biāo)記一個(gè)等待的 goroutine 已經(jīng)完成。它的定義如下:
func (wg *WaitGroup) Done()
Done 方法會(huì)將 counter 減 1。
2.2.3 Wait 方法
Wait 方法用于阻塞當(dāng)前的 goroutine,直到所有的等待的 goroutine 完成。它的定義如下:
func (wg *WaitGroup) Wait()
Wait 方法會(huì)檢查 counter 的值,如果不為 0,則當(dāng)前的 goroutine 會(huì)被阻塞。當(dāng) counter 的值為 0 時(shí),阻塞解除,當(dāng)前的 goroutine 可以繼續(xù)執(zhí)行。
2.3 使用示例
下面是一個(gè)示例代碼,演示了如何使用 sync.WaitGroup:
package main
?
import (
"fmt"
"sync"
"time"
)
?
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
?
func main() {
var wg sync.WaitGroup
?
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
?
wg.Wait()
fmt.Println("All workers completed")
}在上述示例中,我們創(chuàng)建了 5 個(gè) worker goroutine,并使用 wg.Add(1) 將每個(gè) goroutine 添加到 WaitGroup 中。然后,通過(guò)調(diào)用 wg.Wait() 阻塞主 goroutine,直到所有的 worker goroutine 完成執(zhí)行。最后,打印出 "All workers completed" 表示所有的 worker goroutine 已經(jīng)完成。
3. sync.WaitGroup 的工作原理
了解 sync.WaitGroup 的工作原理對(duì)于正確使用它至關(guān)重要。在深入理解 sync.WaitGroup 的工作原理之前,我們需要了解一些關(guān)于并發(fā)編程和原子操作的基本知識(shí)。
3.1 原子操作
在并發(fā)編程中,原子操作是指不能被中斷的操作,要么完全執(zhí)行,要么完全不執(zhí)行。在 Go 語(yǔ)言中,原子操作可以使用 sync/atomic 包提供的函數(shù)來(lái)實(shí)現(xiàn)。
3.2 WaitGroup 的實(shí)現(xiàn)原理
sync.WaitGroup 的實(shí)現(xiàn)依賴于原子操作。sync.WaitGroup 的 counter 字段是一個(gè) 64 位的無(wú)符號(hào)整數(shù),其中高 32 位用于計(jì)數(shù),低 32 位用于記錄等待的 goroutine 數(shù)量。
當(dāng)調(diào)用 Add 方法時(shí),它會(huì)使用原子操作將 delta 值加到 counter 上。當(dāng)調(diào)用 Done 方法時(shí),它會(huì)使用原子操作將 counter 減 1。而 Wait 方法會(huì)通過(guò)循環(huán)檢查 counter 的值,如果不為 0 則阻塞。
在 Wait 方法內(nèi)部,通過(guò)調(diào)用 runtime.gopark 函數(shù)將當(dāng)前的 goroutine 休眠,直到 counter 的值為 0。當(dāng) counter 的值為 0 時(shí),調(diào)用 runtime.goready 函數(shù)喚醒被休眠的 goroutine,使其繼續(xù)執(zhí)行。
4. 高級(jí)技巧與注意事項(xiàng)
除了基本的使用方法之外,還有一些高級(jí)技巧和注意事項(xiàng)需要了解。
4.1 使用帶緩沖的通道
在某些情況下,我們可能需要限制并發(fā)執(zhí)行的 goroutine 數(shù)量??梢酝ㄟ^(guò)使用帶緩沖的通道結(jié)合 sync.WaitGroup 來(lái)實(shí)現(xiàn)。
package main
?
import (
"fmt"
"sync"
"time"
)
?
func worker(id int, wg *sync.WaitGroup, ch chan struct{}) {
defer wg.Done()
?
// 在執(zhí)行任務(wù)前,從通道獲取一個(gè)令牌
<-ch
?
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
?
// 任務(wù)完成后,將令牌放回通道
ch <- struct{}{}
}
?
func main() {
const numWorkers = 3
var wg sync.WaitGroup
ch := make(chan struct{}, numWorkers)
?
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg, ch)
}
?
for i := 0; i < numWorkers; i++ {
ch <- struct{}{} // 初始化通道,放入令牌
}
?
wg.Wait()
fmt.Println("All workers completed")
}在上述示例中,我們創(chuàng)建了一個(gè)帶緩沖的通道 ch,其容量為 numWorkers,即最大并發(fā)執(zhí)行的 goroutine 數(shù)量。在每個(gè) worker goroutine 的開(kāi)頭,它會(huì)從通道 ch 中獲取一個(gè)令牌,這表示該 goroutine 可以執(zhí)行任務(wù)。在任務(wù)完成后,將令牌放回通道。通過(guò)控制令牌的數(shù)量,我們可以限制并發(fā)執(zhí)行的 goroutine 數(shù)量。
4.2 錯(cuò)誤處理和超時(shí)機(jī)制
在實(shí)際應(yīng)用中,我們通常需要添加錯(cuò)誤處理和超時(shí)機(jī)制來(lái)提高程序的可靠性??梢允褂?select 語(yǔ)句和 time.After 函數(shù)來(lái)實(shí)現(xiàn)這些機(jī)制。
package main
?
import (
"fmt"
"sync"
"time"
)
?
func worker(id int, wg *sync.WaitGroup, errCh chan error) {
defer wg.Done()
?
// 模擬任務(wù)執(zhí)行
time.Sleep(time.Second * 2)
?
// 模擬任務(wù)出錯(cuò)
if id == 3 {
errCh <- fmt.Errorf("error occurred in worker %d", id)
return
}
?
fmt.Printf("Worker %d done\n", id)
}
?
func main() {
var wg sync.WaitGroup
errCh := make(chan error)
?
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg, errCh)
}
?
go func() {
wg.Wait()
close(errCh)
}()
?
select {
case err := <-errCh:
fmt.Printf("Error: %v\n", err)
case <-time.After(time.Second * 3):
fmt.Println("Timeout occurred")
}
?
fmt.Println("All workers completed")
}在上述示例中,我們創(chuàng)建了一個(gè) errCh 通道,用于接收可能發(fā)生的錯(cuò)誤。在每個(gè) worker goroutine 的開(kāi)頭,如果任務(wù)出現(xiàn)錯(cuò)誤,會(huì)將錯(cuò)誤信息發(fā)送到 errCh 通道。在主 goroutine 中,使用 select 語(yǔ)句監(jiān)聽(tīng) errCh 通道和 time.After 通道。如果從 errCh 通道接收到錯(cuò)誤,會(huì)輸出錯(cuò)誤信息;如果在 3 秒內(nèi)沒(méi)有從errCh通道接收到錯(cuò)誤,會(huì)觸發(fā)超時(shí)。
5. 總結(jié)
通過(guò)深入理解和正確使用 sync.WaitGroup,我們可以實(shí)現(xiàn)優(yōu)雅且高效的并發(fā)編程。在本文中,我們?cè)敿?xì)介紹了 sync.WaitGroup 的作用和用法,包括 Add、Done 和 Wait 方法的使用。我們還討論了 sync.WaitGroup 的內(nèi)部工作原理,它依賴于原子操作來(lái)實(shí)現(xiàn)并發(fā)的同步和等待。
通過(guò)合理地使用 sync.WaitGroup,我們可以避免競(jìng)態(tài)條件和資源泄漏,提高程序的可維護(hù)性和可靠性。它是實(shí)現(xiàn)并發(fā)任務(wù)協(xié)調(diào)的重要工具之一。
希望本文能夠?qū)Υ蠹疑钊肜斫?Go 中的 sync.WaitGroup 提供幫助,并能在實(shí)際應(yīng)用中獲得更好的效果。通過(guò)掌握并正確使用 sync.WaitGroup,可以更好地控制并發(fā)任務(wù)的執(zhí)行,充分發(fā)揮 Go 語(yǔ)言在并發(fā)編程方面的優(yōu)勢(shì)。
以上就是從并發(fā)到并行解析Go語(yǔ)言中的sync.WaitGroup的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言sync.WaitGroup的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang語(yǔ)言實(shí)現(xiàn)的文件上傳與文件下載功能示例
這篇文章主要介紹了golang語(yǔ)言實(shí)現(xiàn)的文件上傳與文件下載功能,結(jié)合實(shí)例形式分析了Go語(yǔ)言實(shí)現(xiàn)的文件傳輸相關(guān)操作技巧,需要的朋友可以參考下2020-02-02
Go中map數(shù)據(jù)類型的實(shí)現(xiàn)
本文介紹了Go語(yǔ)言中的map數(shù)據(jù)類型,用于高效地存儲(chǔ)和管理鍵值對(duì),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Golang多模塊開(kāi)發(fā)的詳細(xì)過(guò)程
這篇文章主要給大家介紹了關(guān)于Golang多模塊開(kāi)發(fā)的詳細(xì)過(guò)程,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-02-02
使用Go語(yǔ)言開(kāi)發(fā)一個(gè)智能exe文件重命名工具
在日常開(kāi)發(fā)和軟件管理中,我們經(jīng)常會(huì)遇到需要整理大量EXE文件的情況,本文將介紹如何使用Go語(yǔ)言開(kāi)發(fā)一個(gè)智能EXE文件重命名工具,需要的可以了解下2025-07-07
十個(gè)Golang開(kāi)發(fā)中應(yīng)該避免的錯(cuò)誤總結(jié)
Go是一種靜態(tài)類型的、并發(fā)的、垃圾收集的編程語(yǔ)言,由谷歌開(kāi)發(fā)。開(kāi)發(fā)人員在編寫Go代碼時(shí)總會(huì)有一些常見(jiàn)的錯(cuò)誤,下面是Go語(yǔ)言中需要避免的十大壞錯(cuò)誤,希望對(duì)大家有所幫助2023-03-03
Golang極簡(jiǎn)入門教程(四):編寫第一個(gè)項(xiàng)目
這篇文章主要介紹了Golang極簡(jiǎn)入門教程(四):編寫第一個(gè)項(xiàng)目,本文講解了workspace、包路徑、第一個(gè)可執(zhí)行命令等內(nèi)容,需要的朋友可以參考下2014-10-10
深入理解Go高級(jí)并發(fā)模式編寫更高效可擴(kuò)展的應(yīng)用程序
Go對(duì)并發(fā)提供了強(qiáng)大的原生支持,本文討論Go的高級(jí)并發(fā)模式,理解這些并發(fā)模式,可以幫助我們編寫高效的Go應(yīng)用程序,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02
golang解析json數(shù)據(jù)的4種方法總結(jié)
在日常工作中每一名開(kāi)發(fā)者,不管是前端還是后端,都經(jīng)常使用 JSON,下面這篇文章主要給大家介紹了關(guān)于golang解析json數(shù)據(jù)的4種方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06

