singleflight

工作原理

  • 当多个 goroutine 同时调用 Do 方法时,只有第一个会真正执行函数 fn。
  • 后续的调用会等待第一个调用完成,然后直接返回结果。
  • 使用 sync.WaitGroup 来同步等待。
  • 使用 sync.Mutex 来保护共享的 map。

底层实现

type Group struct {
    mu sync.Mutex       // 保护 m
    m  map[string]*call // 懒加载
}

type call struct {
    wg sync.WaitGroup
    val interface{}
    err error
    dups int
}
  • Group 结构体使用一个 map 来存储正在进行的调用。
  • call 结构体表示一个正在进行的函数调用。

Do 方法

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 {
        c.dups++
        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()

    g.doCall(c, key, fn)
    return c.val, c.err, c.dups > 0
}

使用场景

  • 标准库 net/lookup.go, 如果多个请求查询同一个 host, lookGroup 会合并请求,只请求一次就行
  • 缓存失效后的重新加载
  • 并发请求的合并
  • 避免重复计算