Singleflight 是 Go 语言中用于防止重复函数调用的同步原语,它确保同一时间只有一个 goroutine 执行某个操作,其他并发请求会等待并共享结果。本文介绍其工作原理、使用场景及注意事项。
核心要点
- Singleflight 通过
Do方法保证同一 key 的并发请求只执行一次函数调用,其他请求等待并复用结果。 - 使用
Group结构体管理并发请求,Do方法返回结果、错误和是否共享标志。 - 适用于缓存击穿、数据库查询去重等场景,可显著降低后端负载。
- 注意:
Forget方法可主动清除 key 的状态,但需谨慎使用以避免死锁。 - 与
sync.Once不同,Singleflight 支持多个 key 并发且可处理错误返回。
正文
Singleflight 是 Go 标准库 golang.org/x/sync/singleflight 包提供的同步机制,用于抑制重复的函数调用。当多个 goroutine 同时请求相同 key 的操作时,Singleflight 确保只有一个 goroutine 实际执行该操作,其他 goroutine 等待并共享执行结果。
核心概念
- Group:Singleflight 的核心结构体,用于管理一组 key 的并发请求。
- Do(key, fn):执行函数
fn,如果相同 key 的调用正在进行,则等待其完成并返回相同结果。 - DoChan(key, fn):异步版本,返回一个 channel,用于接收结果。
- Forget(key):主动忘记 key 的状态,允许后续调用重新执行。
工作原理
- 当调用
Do(key, fn)时,Singleflight 检查是否有相同 key 的调用正在执行。 - 如果没有,则执行
fn并记录结果;如果有,则当前 goroutine 阻塞等待。 - 执行完成后,所有等待的 goroutine 同时收到结果。
- 结果会被缓存,直到
Forget被调用或 Group 被垃圾回收。
使用场景
- 缓存击穿:当缓存失效时,多个请求同时查询数据库,Singleflight 确保只有一个请求访问数据库,其他请求等待缓存更新。
- API 调用去重:防止对同一外部 API 的重复调用,减少网络开销。
- 资源初始化:确保昂贵的资源只被初始化一次。
注意事项
- 错误处理:如果
fn返回错误,所有等待的 goroutine 都会收到该错误,且结果不会被缓存。 - 内存泄漏:长时间不调用
Forget可能导致 key 一直占用内存。 - 死锁风险:在
fn内部再次调用Do可能导致死锁,需避免递归调用。
示例代码
package main
import (
"fmt"
"sync"
"golang.org/x/sync/singleflight"
)
func main() {
var g singleflight.Group
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
result, err, shared := g.Do("key", func() (interface{}, error) {
// 模拟耗时操作
return "data", nil
})
fmt.Printf("goroutine %d: result=%v, err=%v, shared=%v\n", id, result, err, shared)
}(i)
}
wg.Wait()
}
输出显示所有 goroutine 共享同一个结果,且 shared 为 true。
关联概念
- sync.Once
- 缓存击穿
- 并发控制
可操作项
- 在项目中引入
golang.org/x/sync/singleflight包。 - 使用
Group.Do包装数据库查询或 API 调用,观察并发请求减少情况。 - 测试
Forget方法对缓存更新的影响。 - 编写单元测试验证 Singleflight 在错误场景下的行为。
原文: Understanding Singleflight in Go
自动加工于 2026-05-19 05:52