功能说明
Group.Do 保证同一个 key 同时只有一个加载在执行。第一个调用者创建 call 并真正执行 load,其余调用者在锁外等待同一个 WaitGroup,随后共享结果与错误。返回值中的布尔量表示当前调用者是否是发起加载的 leader;loads 计数器用于验证实际只加载了一次。
关键代码
g.mu.Lock()
if existing, ok := g.calls[key]; ok {
g.mu.Unlock()
existing.wg.Wait()
return existing.val, false, existing.err
}
current := &call[T]{}
current.wg.Add(1)
g.calls[key] = current
g.loads.Add(1)
g.mu.Unlock()
等待必须在释放锁之后进行,否则 leader 完成时无法重新加锁删除条目,造成死锁。
使用注意
- 所有等待者共享同一个结果和错误;如果加载失败,调用方需要自行决定是否重试。
- key 的粒度决定合并范围,粒度过粗会把不同请求错误地合并。
- leader 完成到删除 map 条目之间存在很短窗口,新来的调用可能看到已完成的条目并立即返回,这是可接受的。
- 如果
load发生 panic,wg.Done不会执行,等待者会永久阻塞;生产代码应使用 defer。 - 该实现不缓存结果,只合并进行中的请求;常与带 TTL 的缓存配合使用。
验证方式
示例启动 8 个调用者同时请求 user:42,加载函数睡眠 30 毫秒。输出中应只有 1 行 leader=true,其余为 leader=false,最后打印 loads: 1。把 key 改成每个调用者不同,可验证不再合并。