cs.opensource.google/go/x/sync/+…
zhuanlan.zhihu.com/p/392975826
singleflightd的设计思路就是将一组相同的请求合并成一个请求,使用map存储,只会有一个请求到达mysql,使用sync.waitgroup包进行同步,对所有的请求返回相同的结果。
type Group struct {
mu sync.Mutex // 互斥锁,保证并发安全
m map[string]*call // 存储相同的请求,key是相同的请求,value保存调用信息。
}
type call struct {
wg sync.WaitGroup
// 存储返回值,在wg done之前只会写入一次
val interface{}
// 存储返回的错误信息
err error
// 标识别是否调用了Forgot方法
forgotten bool
// 统计相同请求的次数,在wg done之前写入
dups int
// 使用DoChan方法使用,用channel进行通知
chans []chan<- Result
}
// Dochan方法时使用
type Result struct {
Val interface{} // 存储返回值
Err error // 存储返回的错误信息
Shared bool // 标示结果是否是共享结果
}
// 入参:key:标识相同请求,fn:要执行的函数
// 返回值:v: 返回结果 err: 执行的函数错误信息 shard: 是否是共享结果
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
// 代码块加锁
g.mu.Lock()
// map进行懒加载
if g.m == nil {
// map初始化
g.m = make(map[string]*call)
}
// 判断是否有相同请求
if c, ok := g.m[key]; ok {
// 相同请求次数+1
c.dups++
// 解锁就好了,只需要等待执行结果了,不会有写入操作了
g.mu.Unlock()
// 已有请求在执行,只需要等待就好了
c.wg.Wait()
// 区分panic错误和runtime错误
if e, ok := c.err.(*panicError); ok {
panic(e)
} else if c.err == errGoexit {
runtime.Goexit()
}
return c.val, c.err, true
}
// 之前没有这个请求,则需要new一个指针类型
c := new(call)
// sync.waitgroup的用法,只有一个请求运行,其他请求等待,所以只需要add(1)
c.wg.Add(1)
// m赋值
g.m[key] = c
// 没有写入操作了,解锁即可
g.mu.Unlock()
// 唯一的请求该去执行函数了
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}