singleflight
是 Go 语言中一个用于并发控制的库,主要用于防止对相同资源的重复请求。singleflight.Do
函数是 singleflight
库的核心函数之一,它的作用是确保在并发环境下,对相同资源的请求只会执行一次,其他并发的请求会等待第一个请求的结果并共享其结果。
singleflight.Do
函数签名func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)
key
:用于标识请求的唯一键值。相同的 key
会被认为是相同的请求。fn
:实际执行请求的函数,返回请求的结果和可能的错误。v
:请求的结果。err
:请求过程中可能发生的错误。shared
:一个布尔值,表示结果是否被共享。shared
参数的含义shared
参数表示当前请求的结果是否是从其他并发请求中共享而来的。具体来说:
shared
为 true
,表示当前请求的结果是从其他并发请求中共享的,也就是说当前请求并没有真正执行 fn
函数,而是直接使用了其他请求的结果。shared
为 false
,表示当前请求是第一个执行 fn
函数的请求,结果是由当前请求计算得出的。在 singleflight
的实现中,Do
函数会维护一个 call
结构体,用于存储每个 key
对应的请求状态。当多个并发请求使用相同的 key
调用 Do
函数时,singleflight
会确保只有一个请求真正执行 fn
函数,其他请求会等待这个请求的结果。
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error, bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err, true
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err, false
}
Do
函数首先会加锁,确保并发安全。key
的请求:如果已经有相同 key
的请求在处理中(即 c, ok := g.m[key]; ok
为 true
),则当前请求会等待该请求的结果,并返回 shared = true
。call
结构体:如果没有相同 key
的请求在处理中,则创建一个新的 call
结构体,并将其添加到 g.m
中。fn
函数:当前请求会执行 fn
函数,并将结果存储在 call
结构体中。Do
函数会释放锁,并返回结果。如果是第一个请求,shared
为 false
;如果是共享的结果,shared
为 true
。shared
参数的作用是告诉调用者当前请求的结果是否是从其他并发请求中共享而来的。如果 shared
为 true
,则表示当前请求没有真正执行 fn
函数,而是直接使用了其他请求的结果。这在某些场景下非常有用,比如缓存穿透、防止重复计算等。