天天看点

Golang中的SingleFlight与CyclicBarrier

  SingleFlight将并发请求合并成一个请求,可用于减少下游压力;CyclicBarrier可重用栅栏并发原语,控制一组请求同时执行;

  在Go中SingleFlight并不是原生提供的,而是开发组提供的扩展并发原语。它可实现多个goroutine调用通过一函数时,只让一个goroutine调用该函数,等到该goroutine调用函数返回结果时再将结果返回给其他同时调用的goroutine,从而做到了减少并发调用的次数;

  在秒杀、缓存等场景下SingleFlight作用很明显,能够大规模的减少并发数量避免缓存穿透、系统崩溃等。将多个并发请求 合并成一,瞬间将下游系统压力从N减少到1;

执行结果:

  这个Demo中有五个goroutine同时发起获取key为flight的缓存,由于使用了SingleFlight对象,ID为0的请求率先发起了获取缓存,其他4个goroutine并不会去执行获取缓存请求逻辑,而是等到ID为0的请求取得到结果后直接使用该结果;

  SingleFlight内部使用了互斥锁Mutex与Map实现,Mutex用于提供并发时的读写保护,Map用于保存同一个key的处理请求;SingleFlight提供了如下三个方法:

  Do: 执行一个函数,返回函数的执行结果;

  DoChan: 与Do方法类似,返回的是一个chan,函数fn执行完成产生结果后,可从chan中接受到函数的执行结果;

  Forget: 丢弃某个key,之后这个key请求会继续执行函数fn,不在等待前一个请求fn函数的执行结果;

  SingleFlight的实现部分代码如下,其中call为具体的的请求、Group代表Singleflight、map[string]*call用于存储相对应的key所发起的请求;

  在Go的标准库中、开发组扩展库中其实也并没有CyclicBarrier的实现,有个第三方的CyclicBarrier实现:https://github.com/marusama/cyclicbarrier, 它的逻辑为:一组goroutine彼此等待直到所有的goroutine都达到某个执行点,再往下执行。就如栅栏一样等指定数量的人到齐了,开始抬起栅栏放行;它的执行逻辑与Java的cyclicbarrier类似;

  在Go标准库中有个对象有类似的功能:WaitGroup,但该对象并没有CyclicBarrier那么简单易用;

  通过上面Demo可以看到ID为2、0的goroutine输出start后并没有继续往下执行,而是等到ID为0的goroutine执行到start后三个goroutine一起往下执行;

  如没有使用栅栏,则这个Demo的执行结果如下: