golang基于errgroup實(shí)現(xiàn)并發(fā)調(diào)用的方法
串行調(diào)用
在用go編寫(xiě)web/rpc服務(wù)器的時(shí)候,經(jīng)常會(huì)出現(xiàn)需要對(duì)下游多 個(gè)/組 服務(wù)調(diào)用rpc(或者其他比較耗時(shí)的操作)的情況。
按照自然的寫(xiě)法,比如對(duì)下游有ABC三個(gè)調(diào)用,串行順著寫(xiě),就總共要花費(fèi)TimeA+TimeB+TimeC的時(shí)間:
func Handler(ctx context.Context) {
var a, b, c respType
a = A(ctx)
b = B(ctx)
c = C(ctx)
}

基于sync.WaitGroup實(shí)現(xiàn)簡(jiǎn)單的并發(fā)調(diào)用
但經(jīng)常地,幾個(gè)rpc相互之間沒(méi)有依賴(lài)關(guān)系的情況,這時(shí),我們稍加思考就會(huì)想到使用并發(fā)的方式,同時(shí)發(fā)出請(qǐng)求,阻塞等到所有請(qǐng)求返回,這樣,總體耗時(shí)就變成了Max(TimeA, TimeB, TimeC),我們可以通過(guò)常用的sync.WaitGroup輕松實(shí)現(xiàn)這事:
func Handler(ctx context.Context) {
var a, b, c respType
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
a = A(ctx)
}()
go func() {
defer wg.Done()
b = B(ctx)
}()
go func() {
defer wg.Done()
c = C(ctx)
}()
wg.Wait()
}

但是現(xiàn)實(shí)事件是不完美的,尤其是在加入了網(wǎng)絡(luò)這一因素后,我們經(jīng)常會(huì)需要處理調(diào)用失敗的情況,很多情況下,并發(fā)的幾個(gè)操作只要任一失敗,整個(gè)處理就算失敗了,但是由于WaitGroup要等所有調(diào)用都done才能返回,因此調(diào)用時(shí)間是由耗時(shí)最長(zhǎng)的那個(gè)(不一定是失敗的)決定的,如果不是失敗的那個(gè),其實(shí)就產(chǎn)生了資源浪費(fèi),如下圖,B最先失敗了,此時(shí)邏輯上已經(jīng)可以返回,但是實(shí)際卻等到了最長(zhǎng)的調(diào)用-A返回了整個(gè)函數(shù)才返回:

func Handler(ctx context.Context) {
var a, b, c respType
var errA, errB, errC error
wg := sync.WaitGroup{}
wg.Add(3)
go func() {
defer wg.Done()
a, errA = A(ctx)
}()
go func() {
defer wg.Done()
b, errB = B(ctx)
}()
go func() {
defer wg.Done()
c, errC = C(ctx)
}()
wg.Wait()
if errA != nil {
// ...
}
if errB != nil {
// ...
}
if errC != nil {
// ...
}
}
基于errgroup.Group實(shí)現(xiàn)并發(fā)調(diào)用
這對(duì)于追求極致的我們來(lái)說(shuō)顯然是不能接受的,我們希望達(dá)到,如果有任意一個(gè)調(diào)用報(bào)錯(cuò),立刻讓所有調(diào)用返回的效果:

好在,我們有現(xiàn)成的工具可以用,通過(guò)引入"golang.org/x/sync/errgroup",可以輕松實(shí)現(xiàn)上面的目的。
為了使用errgroup,先使用WithContext方法創(chuàng)建一個(gè)Group
wg, groupCtx := errgroup.WithContext(ctx)
返回的第一個(gè)參數(shù)是*errgroup.Group,第二個(gè)則是在子調(diào)用中應(yīng)該使用的context。
然后,使用Go方法調(diào)用所有的并發(fā)方法
wg.Go(func() error {
var err error
a, err = A(groupCtx)
return err
})
最后, 使用Wait方法等待并發(fā)結(jié)束,返回值是所有子調(diào)用中第一個(gè)非nil的error,全成功的話(huà)就是nil。
if err := wg.Wait(); err != nil {
// ...
}
因此整體,我們的代碼差不多就長(zhǎng)這個(gè)樣子
func handler(ctx context.Context) {
var a, b, c respType
wg, groupCtx := errgroup.WithContext(ctx)
wg.Go(func() error {
var err error
a, err = A(groupCtx)
return err
})
wg.Go(func() error {
var err error
b, err = B(groupCtx)
return err
})
wg.Go(func() error {
var err error
c, err = C(groupCtx)
return err
})
if err := wg.Wait(); err != nil {
// ... 錯(cuò)誤處理
}
// 全部成功
}
errgroup內(nèi)部通過(guò)封裝了waitGroup和sync.Once實(shí)現(xiàn)了這個(gè)語(yǔ)法糖。
使用時(shí)特別要注意的是,errgroup的提前取消調(diào)用rpc是通過(guò)cancel那個(gè)返回的context(即上面的groupCtx)實(shí)現(xiàn)的,因此在所有子調(diào)用中都要實(shí)現(xiàn)監(jiān)聽(tīng)groupCtx的Done事件。而在正常的rpc框架中都已經(jīng)幫我們實(shí)現(xiàn)了這件事,因此我們只要保證傳進(jìn)去的是groupCtx即可。
總結(jié)
errgroup幫我們封裝了并發(fā)調(diào)用下游時(shí)快速失敗的邏輯,我們能很方便地使用它進(jìn)行業(yè)務(wù)代碼的編寫(xiě)。使用的關(guān)鍵是一定要記得在子調(diào)用中傳遞WithContext中返回的Context。
好用的工具千千萬(wàn),讓我們一個(gè)個(gè)來(lái)掌握!
相關(guān)文章
golang調(diào)試bug及性能監(jiān)控方式實(shí)踐總結(jié)
這篇文章主要為大家介紹了golang調(diào)試bug及性能監(jiān)控方式實(shí)踐是總結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
GoLang中socket心跳檢測(cè)的實(shí)現(xiàn)
本文主要介紹了GoLang中socket心跳檢測(cè)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
淺析Go常量為什么只支持基本數(shù)據(jù)類(lèi)型
這篇文章主要來(lái)和大家一起討論一下Golang中常量為什么只支持基本數(shù)據(jù)類(lèi)型,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-09-09
Go語(yǔ)言操作mysql數(shù)據(jù)庫(kù)簡(jiǎn)單例子
這篇文章主要介紹了Go語(yǔ)言操作mysql數(shù)據(jù)庫(kù)簡(jiǎn)單例子,本文包含插入數(shù)據(jù)和查詢(xún)代碼實(shí)例,需要的朋友可以參考下2014-10-10
Golang空結(jié)構(gòu)體struct{}用途,你知道嗎
這篇文章主要介紹了Golang空結(jié)構(gòu)體struct{}用途,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Golang map實(shí)踐及實(shí)現(xiàn)原理解析
這篇文章主要介紹了Golang map實(shí)踐以及實(shí)現(xiàn)原理,Go 語(yǔ)言中,通過(guò)哈希查找表實(shí)現(xiàn) map,用鏈表法解決哈希沖突,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06
Go?gRPC服務(wù)proto數(shù)據(jù)驗(yàn)證進(jìn)階教程
這篇文章主要為大家介紹了Go?gRPC服務(wù)proto數(shù)據(jù)驗(yàn)證進(jìn)階教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
go語(yǔ)言調(diào)用其他包中的函數(shù)簡(jiǎn)單示例
這篇文章主要給大家介紹了關(guān)于go語(yǔ)言調(diào)用其他包中的函數(shù)的相關(guān)資料,文中還介紹了Go語(yǔ)言同一個(gè)包中不同文件之間函數(shù)調(diào)用的相關(guān)問(wèn)題,需要的朋友可以參考下2023-01-01

