Golang Singleflight實(shí)現(xiàn)

golang/groupcache

用處

保護(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)
    }
}
  1. fn只被執(zhí)行了一次 -> calls的值為1;
  2. 其他的攜程都能拿到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)

  1. call的結(jié)構(gòu)
  type call struct {
    wg  sync.WaitGroup
    val interface{}  //最終返回的結(jié)果
    err error
  }
  1. map的結(jié)構(gòu)
type Group struct {
  mu sync.Mutex       // protects m
  m  map[string]*call // lazily initialized
}
  1. 調(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
}
  1. 實(shí)際使用的例子
ProductSku = singleflight.Group{}
skuList, err, shared := ProductSku.Do(strconv.FormatInt(productId, 10), func() (i interface{}, e error) {
        return rpc.GetProductSku(ctx, productId, nil)
    })
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。