CyclicBarrier
在Go的标准库中、开发组扩展库中其实也并没有CyclicBarrier的实现,有个第三方的CyclicBarrier实现:github.com/marusama/cy… 它的逻辑为:一组goroutine彼此等待直到所有的goroutine都达到某个执行点,再往下执行。就如栅栏一样等指定数量的人到齐了,开始抬起栅栏放行;它的执行逻辑与Java的cyclicbarrier类似;
在Go标准库中有个对象有类似的功能:WaitGroup,但该对象并没有CyclicBarrier那么简单易用;
for i := 0; i < 3; i++ {
go func(id int) {
log.Printf("start: %v", id)
barrier.Await(context.Background())
log.Printf("finish: %v", id)
}(i)
}
time.Sleep(5 * time.Second)
log.Printf("完成")
}
SingleFlight
在Go中SingleFlight并不是原生提供的,而是开发组提供的扩展并发原语。它可实现多个goroutine调用通过一函数时,只让一个goroutine调用该函数,等到该goroutine调用函数返回结果时再将结果返回给其他同时调用的goroutine,从而做到了减少并发调用的次数;
在秒杀、缓存等场景下SingleFlight作用很明显,能够大规模的减少并发数量避免缓存穿透、系统崩溃等。将多个并发请求 合并成一,瞬间将下游系统压力从N减少到1;
key := "flight"
for i := 0; i < 5; i++ {
log.Printf("ID: %d 请求获取缓存", i)
go func(id int) {
value, _ := getCache(key, id)
log.Printf("ID :%d 获取到缓存 , key: %v,value: %v", id, key, value)
}(i)
}
time.Sleep(20 * time.Second)
}
func getCache(key string, id int) (string, error) {
var ret, _, _ = group.Do(key, func() (ret interface{}, err error) {
time.Sleep(2 * time.Second)//模拟获取缓存
log.Printf("ID: %v 执行获取缓存", id)
return id, nil
})
return strconv.Itoa(ret.(int)), nil
}
SingleFlight内部使用了互斥锁Mutex与Map实现,Mutex用于提供并发时的读写保护,Map用于保存同一个key的处理请求;SingleFlight提供了如下三个方法:
Do: 执行一个函数,返回函数的执行结果;
DoChan: 与Do方法类似,返回的是一个chan,函数fn执行完成产生结果后,可从chan中接受到函数的执行结果;
Forget: 丢弃某个key,之后这个key请求会继续执行函数fn,不在等待前一个请求fn函数的执行结果;