Golang sync包中errgroup的使用詳解
1、初識(shí) errgroup
WaitGroup 主要用于控制任務(wù)組下的并發(fā)子任務(wù)。它的具體做法就是,子任務(wù) goroutine 執(zhí)行前通過 Add 方法添加任務(wù)數(shù)目,子任務(wù) goroutine 結(jié)束時(shí)調(diào)用 Done 標(biāo)記已完成任務(wù)數(shù),主任務(wù) goroutine 通過 Wait 方法等待所有的任務(wù)完成后才能執(zhí)行后續(xù)邏輯。
package main
import (
"net/http"
"sync"
)
func main() {
var wg sync.WaitGroup
var urls = []string{
"http://www.golang.org/",
"http://www.baidu.com/",
"http://www.bokeyuan12111.com/",
}
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
resp, err := http.Get(url)
if err != nil {
return
}
resp.Body.Close()
}(url)
}
wg.Wait()
}
在以上示例代碼中,我們通過三個(gè) goroutine 去并發(fā)的請(qǐng)求 url,直到所有的子任務(wù) goroutine 均完成訪問,主任務(wù) goroutine 下的 wg.Wait 才會(huì)停止阻塞。
但在實(shí)際的項(xiàng)目代碼中,子任務(wù) goroutine 的執(zhí)行并不總是順風(fēng)順?biāo)?,它們也許會(huì)產(chǎn)生 error。而 WaitGroup 并沒有告訴我們?cè)谧?goroutine 發(fā)生錯(cuò)誤時(shí),如何將其拋給主任務(wù) groutine。
這個(gè)時(shí)候可以考慮使用 errgroup
package main
import (
"fmt"
"net/http"
"golang.org/x/sync/errgroup"
)
func main() {
var urls = []string{
"http://www.golang.org/",
"http://www.baidu.com/",
"http://www.bokeyuan12111.com/",
}
g := new(errgroup.Group)
for _, url := range urls {
url := url
g.Go(func() error {
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
return err
}
fmt.Printf("get [%s] success: [%d] \n", url, resp.StatusCode)
return resp.Body.Close()
})
}
if err := g.Wait(); err != nil {
fmt.Println(err)
} else {
fmt.Println("All success!")
}
}
結(jié)果如下:
get [http://www.baidu.com/] success: [200]
Get "http://www.bokeyuan12111.com/": dial tcp: lookup www.bokeyuan12111.com: no such host
Get "http://www.golang.org/": dial tcp 142.251.42.241:80: i/o timeout
Get "http://www.bokeyuan12111.com/": dial tcp: lookup www.bokeyuan12111.com: no such host
可以看到,執(zhí)行獲取www.bokeyuan12111.com和www.golang.org兩個(gè) url 的子 groutine 均發(fā)生了錯(cuò)誤,在主任務(wù) goroutine 中成功捕獲到了第一個(gè)錯(cuò)誤信息。
除了 擁有 WaitGroup 的控制能力 和 錯(cuò)誤傳播 的功能之外,errgroup 還有最重要的 context 反向傳播機(jī)制,我們來看一下它的設(shè)計(jì)。
2、errgroup 源碼解析
errgroup 的設(shè)計(jì)非常精練,全部代碼如下
type Group struct {
cancel func()
wg sync.WaitGroup
errOnce sync.Once
err error
}
func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &Group{cancel: cancel}, ctx
}
func (g *Group) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.err
}
func (g *Group) Go(f func() error) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
}()
}
可以看到,errgroup 的實(shí)現(xiàn)依靠于結(jié)構(gòu)體 Group,它通過封裝 sync.WaitGroup,繼承了 WaitGroup 的特性,在 Go() 方法中新起一個(gè)子任務(wù) goroutine,并在 Wait() 方法中通過 sync.WaitGroup 的 Wait 進(jìn)行阻塞等待。
同時(shí) Group 利用 sync.Once 保證了它有且僅會(huì)保留第一個(gè)子 goroutine 錯(cuò)誤。
最后,Group 通過嵌入 context.WithCancel 方法產(chǎn)生的 cancel 函數(shù),能夠在子 goroutine 發(fā)生錯(cuò)誤時(shí),及時(shí)通過調(diào)用 cancle 函數(shù),將 Context 的取消信號(hào)及時(shí)傳播出去。當(dāng)然,這一特性需要用戶代碼的配合。
3、errgroup 上下文取消
在 errgroup 的文檔(https://pkg.go.dev/golang.org/x/sync@v0.0.0-20210220032951-036812b2e83c/errgroup#example-Group-Pipeline)中,它基于 Go 官方文檔的 pipeline( https://blog.golang.org/pipelines) ,實(shí)現(xiàn)了一個(gè)任務(wù)組 goroutine 中上下文取消(Context cancelation)演示的示例。但該 Demo 的前提知識(shí)略多,本文這里基于其思想,提供一個(gè)易于理解的使用示例。
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
g, ctx := errgroup.WithContext(context.Background())
dataChan := make(chan int, 20)
// 數(shù)據(jù)生產(chǎn)端任務(wù)子 goroutine
g.Go(func() error {
defer close(dataChan)
for i := 1; ; i++ {
if i == 10 {
return fmt.Errorf("data 10 is wrong")
}
dataChan <- i
fmt.Println(fmt.Sprintf("sending %d", i))
}
})
// 數(shù)據(jù)消費(fèi)端任務(wù)子 goroutine
for i := 0; i < 3; i++ {
g.Go(func() error {
for j := 1; ; j++ {
select {
case <-ctx.Done():
return ctx.Err()
case number := <-dataChan:
fmt.Println(fmt.Sprintf("receiving %d", number))
}
}
})
}
// 主任務(wù) goroutine 等待 pipeline 結(jié)束數(shù)據(jù)流
err := g.Wait()
if err != nil {
fmt.Println(err)
}
fmt.Println("main goroutine done!")
}
在以上示例中,我們模擬了一個(gè)數(shù)據(jù)傳送管道。在數(shù)據(jù)的生產(chǎn)與消費(fèi)任務(wù)集中,有四個(gè)子任務(wù) goroutine:一個(gè)生產(chǎn)數(shù)據(jù)的 goroutine,三個(gè)消費(fèi)數(shù)據(jù)的 goroutine。當(dāng)數(shù)據(jù)生產(chǎn)方存在錯(cuò)誤數(shù)據(jù)時(shí)(數(shù)據(jù)等于 10 ),我們停止數(shù)據(jù)的生產(chǎn)與消費(fèi),并將錯(cuò)誤拋出,回到 main goroutine 的執(zhí)行邏輯中。
可以看到,因?yàn)?errgroup 中的 Context cancle 函數(shù)的嵌入,我們?cè)谧尤蝿?wù) goroutine 中也能反向控制任務(wù)上下文。
程序的某一次運(yùn)行,輸出結(jié)果如下:
sending 1
sending 2
sending 3
sending 4
sending 5
sending 6
sending 7
sending 8
sending 9
receiving 1
receiving 3
receiving 2
receiving 4
data 10 is wrong
main goroutine done!
4、總結(jié)
errgroup 是 Go 官方的并發(fā)原語補(bǔ)充庫,相對(duì)于標(biāo)準(zhǔn)庫中提供的原語而言,顯得沒那么核心。這里總結(jié)一下 errgroup 的特性。
- 繼承了 WaitGroup 的功能
- 錯(cuò)誤傳播:能夠返回任務(wù)組中發(fā)生的第一個(gè)錯(cuò)誤,但有且僅能返回該錯(cuò)誤
- context 信號(hào)傳播:如果子任務(wù) goroutine 中有循環(huán)邏輯,則可以添加 ctx.Done 邏輯,此時(shí)通過 context 的取消信號(hào),提前結(jié)束子任務(wù)執(zhí)行。
到此這篇關(guān)于Golang sync包中errgroup的使用詳解的文章就介紹到這了,更多相關(guān)Golang errgroup內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang定時(shí)任務(wù)cron項(xiàng)目實(shí)操指南
Go實(shí)現(xiàn)的cron 表達(dá)式的基本語法跟linux 中的 crontab基本是類似的,下面這篇文章主要給大家介紹了關(guān)于golang定時(shí)任務(wù)cron項(xiàng)目實(shí)操的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12
go 異常處理panic和recover的簡(jiǎn)單實(shí)踐
在Go語言中,異常處理主要通過panic和recover這兩個(gè)內(nèi)建函數(shù)來實(shí)現(xiàn),本文主要介紹了go異常處理panic和recover的簡(jiǎn)單實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2025-04-04
Golang如何調(diào)用windows下的dll動(dòng)態(tài)庫中的函數(shù)
這篇文章主要介紹了Golang如何調(diào)用windows下的dll動(dòng)態(tài)庫中的函數(shù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05
Go標(biāo)準(zhǔn)庫encoding/gob的具體使用
Go標(biāo)準(zhǔn)庫encoding/gob實(shí)現(xiàn)二進(jìn)制序列化與反序列化,本文主要介紹了Go標(biāo)準(zhǔn)庫encoding/gob的具體使用,感興趣的可以了解一下2025-06-06
Go?channel實(shí)現(xiàn)批量讀取數(shù)據(jù)
Go中的?channel?其實(shí)并沒有提供批量讀取數(shù)據(jù)的方法,需要我們自己實(shí)現(xiàn)一個(gè),使用本文就來為大家大家介紹一下如何通過Go?channel實(shí)現(xiàn)批量讀取數(shù)據(jù)吧2023-12-12
Go語言中實(shí)現(xiàn)Unix風(fēng)格的進(jìn)程管道方法實(shí)例
這篇文章主要為大家介紹了Go語言中實(shí)現(xiàn)Unix風(fēng)格的進(jìn)程管道方法實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12

