m <- token{}: // 当g.sem的缓冲区满了过后,就会执行default,也代表着未启动成功
// Note: this allows barging iff channels in general allow barging.
default:
return false
}
}
//----主要看上面的逻辑,下面的逻辑和Go中的一样-------
g.wg.Add(1)
go func() {
defer g.done()
if err := f(); err != nil {
g.errOnce.Do(func() {
g.err = err
if g.cancel != nil {
g.cancel()
}
})
}
}()
return true
}
2.5 Wait
代码逻辑很简单,这里主要注意这里:
//我看这里的时候,有点疑惑,为啥这里会去调用 cancel()方法呢?
//这里是为了代码的健壮性,用 context.WithCancel() 创建得到的 cancel,在代码执行完毕之前取消是一个好习惯
g.cancel()
// 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() //通过 g.wg.Wait() 阻塞等待所有的 goroutine 执行完
if g.cancel != nil {
//我看这里的时候,有点疑惑,为啥这里会去调用 cancel()方法呢?
//这里是为了代码的健壮性,用 context.WithCancel() 创建得到的 cancel,在代码执行完毕之前取消是一个好习惯
g.cancel()
}
return g.err
}
2.6 SetLimit
看代码的注释,我们知道:SetLimit
的逻辑主要是限制同时执行的 goroutines 的数量为n,当n小于0时,没有限制。如果有运行的 goroutine,调用此方法会报错。
// SetLimit limits the number of active goroutines in this group to at most n.
// A negative value indicates no limit.
//
// Any subsequent call to the Go method will block until it can add an active
// goroutine without exceeding the configured limit.
//
// The limit must not be modified while any goroutines in the group are active.
func (g *Group) SetLimit(n int) {
if n < 0 {
g.sem = nil
return
}
if len(g.sem) != 0 {
panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem)))
}
g.sem = make(chan token, n)
}
3、errgroup 容易忽视的坑
这个坑是看别人的记录看到的,对errgroup
不太熟悉时,是不小心确实容易掉进去,所以摘抄了过来,如果侵权,请联系删除,谢谢!
原文链接:并发编程包之 errgroup
需求:
开启多个Goroutine
去缓存中设置数据,同时开启一个Goroutine
去异步写日志,很快我的代码就写出来了:
package main
import (
"context"
"errors"
"fmt"
"golang.org/x/sync/errgroup"
"time"
)
func main() {
g, ctx := errgroup.WithContext(context.Background())
// 单独开一个协程去做其他的事情,不参与waitGroup
go WriteChangeLog(ctx)
for i:=0 ; i< 3; i++{
g.Go(func() error {
return errors.New("访问redis失败\n")
})
}
if err := g.Wait();err != nil{
fmt.Printf("appear error and err is %s",err.Error())
}
time.Sleep(1 * time.Second)
}
func WriteChangeLog(ctx context.Context) error {
select {
case <- ctx.Done():
return nil
case <- time.After(time.Millisecond * 50):
fmt.Println("write changelog")
}
return nil
}
结果:
appear error and err is 访问redis失败
代码看着没有问题,但是日志一直没有写入。这是为什么呢?
其实原因就是因为这个ctx
是errgroup.WithContext
方法返回的一个带取消的ctx
,我们把这个ctx
当作父context
传入WriteChangeLog
方法中了,如果errGroup
取消了,也会导致上下文的context
都取消了,所以WriteChangelog
方法就一直执行不到。
这个点是我们在日常开发中想不到的,所以需要注意一下~。
解决方法:
解决方法就是在 go WriteChangeLog(context.Background()) 传入新的ctx
参考资料:
八. Go并发编程--errGroup
并发编程包之 errgroup
上面这个案例中讲了一个容易忽视的坑,大家可以看看