一般在golang 中想要并發(fā)運(yùn)行業(yè)務(wù)時(shí)會(huì)直接開(kāi)goroutine算灸,關(guān)鍵字go ,但是直接go的話函數(shù)是無(wú)法對(duì)返回?cái)?shù)據(jù)進(jìn)行處理error的怜森。
解決方案:
-
初級(jí)版本:
一般是直接在出錯(cuò)的地方打入log日志,將出的錯(cuò)誤記錄到日志文件中痕檬,也可以集合日志收集系統(tǒng)直接將該錯(cuò)誤用郵箱或者辦公軟件發(fā)送給你如:釘釘機(jī)器人+graylog.
-
中級(jí)版本
當(dāng)然你也可以自己在log包里封裝好可以接受channel强胰。
利用channel通道,將go中出現(xiàn)的error傳入到封裝好的帶有channel接受器的log包中撬码,進(jìn)行錯(cuò)誤收集或者通知通道接受return出來(lái)即可
-
終極版本 errgroup
這個(gè)包是google對(duì)go的一個(gè)擴(kuò)展包:
golang.org/x/sync/errgroup
怎么調(diào)用
我們直接看看官方test的demo調(diào)用:
func ExampleGroup_justErrors() {
var g errgroup.Group
var urls = []string{
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
}
for _, url := range urls {
// Launch a goroutine to fetch the URL.
url := url // https://golang.org/doc/faq#closures_and_goroutines
g.Go(func() error {
// Fetch the URL.
resp, err := http.Get(url)
if err == nil {
resp.Body.Close()
}
return err
})
}
// Wait for all HTTP fetches to complete.
if err := g.Wait(); err == nil {
fmt.Println("Successfully fetched all URLs.")
}
}
很簡(jiǎn)單的一個(gè)并發(fā)爬蟲(chóng)網(wǎng)頁(yè)請(qǐng)求维蒙,請(qǐng)求中只要有一個(gè)有錯(cuò)誤就會(huì)返回error掰吕。
再來(lái)看一種代有上下文context的調(diào)用:
func TestWithContext(t *testing.T) {
errDoom := errors.New("group_test: doomed")
cases := []struct {
errs []error
want error
}{
{want: nil},
{errs: []error{nil}, want: nil},
{errs: []error{errDoom}, want: errDoom},
{errs: []error{nil, errDoom}, want: errDoom},
}
for _, tc := range cases {
g, ctx := errgroup.WithContext(context.Background())
for _, err := range tc.errs {
err := err
g.Go(func() error {
log.Error(err) // 當(dāng)此時(shí)的err = nil 時(shí),g.Go不會(huì)將 為nil 的 err 放入g.err中
return err
})
}
err := g.Wait() // 這里等待所有Go跑完即add==0時(shí)颅痊,此處的err是g.err的信息殖熟。
log.Error(err)
log.Error(tc.want)
if err != tc.want {
t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
"g.Wait() = %v; want %v",
g, tc.errs, err, tc.want)
}
canceled := false
select {
case <-ctx.Done():
// 由于上文中內(nèi)部調(diào)用了cancel(),所以會(huì)有Done()接受到了消息
// returns an error or ctx.Done is closed
// 在當(dāng)前工作完成或者上下文被取消之后關(guān)閉
canceled = true
default:
}
if !canceled {
t.Errorf("after %T.Go(func() error { return err }) for err in %v\n"+
"ctx.Done() was not closed",
g, tc.errs)
}
}
}
關(guān)于上下文的知識(shí)補(bǔ)充可以看鏈接:
這里 調(diào)用了 errgroup.WithContext, 用于控制每一個(gè)goroutined的生理周期斑响。
在不同的 Goroutine 之間同步請(qǐng)求特定的數(shù)據(jù)菱属、取消信號(hào)以及處理請(qǐng)求的截止日期
總的來(lái)說(shuō)api的使用還是很簡(jiǎn)單的钳榨,再來(lái)看看源碼包吧
源碼解析
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package errgroup provides synchronization, error propagation, and Context
// cancelation for groups of goroutines working on subtasks of a common task.
package errgroup
import (
"context"
"sync"
)
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid and does not cancel on error.
type Group struct {
// cancel 初始化時(shí)會(huì)直接扔一個(gè)context.WithCancel(ctx)的cancel進(jìn)入,然后通過(guò)它來(lái)進(jìn)行removeChild removes a context from its parent
// 這樣真正調(diào)用其實(shí)是:func() { c.cancel(true, Canceled) }
cancel func()
// 包含了個(gè) WaitGroup用于同步等待所有Gorourine執(zhí)行
wg sync.WaitGroup
// go語(yǔ)言中特有的單例模式照皆,利用原子操作進(jìn)行鎖定值判斷
errOnce sync.Once
err error
}
// WithContext returns a new Group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &Group{cancel: cancel}, ctx
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.err
// 這里返回的error其實(shí)是從眾多goroutine中返回第一個(gè)非nil的錯(cuò)誤信息重绷,所以這個(gè)錯(cuò)誤信息如果全部都是一樣的話,你是不知道到底是哪個(gè)goroutine中報(bào)的錯(cuò)膜毁,應(yīng)該在goroutine內(nèi)部就寫(xiě)清楚錯(cuò)誤信息的別分類似可以加入id值這種昭卓。
}
// Go calls the given function in a new goroutine.
//
// The first call to return a non-nil error cancels the group; its error will be
// returned by Wait.
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 {
// 如果出現(xiàn)第一個(gè)err為非nil就會(huì)去調(diào)用關(guān)閉接口,即關(guān)閉后面的所有子gorourine
g.cancel()
}
})
}
}()
}
開(kāi)源引用demo
這里介紹一個(gè) bilibili errgroup 包:
另外 官方包golang.org/sync/errgroup 的 test 也可以看看瘟滨。