用處
保護(hù)下游,針對(duì)下游的同一批請(qǐng)求,只有一個(gè)負(fù)責(zé)去請(qǐng)求,其他等待結(jié)果;
例如:緩存更新能夠做到對(duì)同一個(gè)失效key的多個(gè)請(qǐng)求,只有一個(gè)請(qǐng)求執(zhí)行對(duì)key的更新操作。
示例
func TestDoDupSuppress(t *testing.T) {
var g Group
c := make(chan string)
var calls int32
fn := func() (interface{}, error) {
atomic.AddInt32(&calls, 1)
return <-c, nil
}
const n = 10
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() { // n個(gè)協(xié)程同時(shí)調(diào)用了g.Do,fn中的邏輯只會(huì)被一個(gè)協(xié)程執(zhí)行
v, err := g.Do("key", fn)
if err != nil {
t.Errorf("Do error: %v", err)
}
if v.(string) != "bar" {
t.Errorf("got %q; want %q", v, "bar")
}
wg.Done()
}()
}
time.Sleep(100 * time.Millisecond) // let goroutines above block
c <- "bar"
wg.Wait()
if got := atomic.LoadInt32(&calls); got != 1 {
t.Errorf("number of calls = %d; want 1", got)
}
}
- fn只被執(zhí)行了一次 -> calls的值為1;
- 其他的攜程都能拿到fn執(zhí)行的結(jié)果;
原理
map存儲(chǔ)每個(gè)key對(duì)應(yīng)的call,每個(gè)call會(huì)被多個(gè)攜程同時(shí)調(diào)用。
一個(gè)call里邊有個(gè)waitgroup,第一個(gè)攜程去執(zhí)行調(diào)用,其他攜程阻塞在wg上邊。 (關(guān)鍵就是這個(gè)wg)
- call的結(jié)構(gòu)
type call struct {
wg sync.WaitGroup
val interface{} //最終返回的結(jié)果
err error
}
- map的結(jié)構(gòu)
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
- 調(diào)用
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
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() //其他的請(qǐng)求阻塞
return c.val, c.err
}
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
c.val, c.err = fn() //第一個(gè)去執(zhí)行調(diào)用
c.wg.Done() //同一批都返回
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
- 實(shí)際使用的例子
ProductSku = singleflight.Group{}
skuList, err, shared := ProductSku.Do(strconv.FormatInt(productId, 10), func() (i interface{}, e error) {
return rpc.GetProductSku(ctx, productId, nil)
})