Documentation
¶
Overview ¶
Package singleflight 提供并发请求合并(防击穿)工具。
同一个 key 的并发调用只执行一次 fn,其他调用共享结果。 适用于缓存穿透防护、token 刷新、配置加载等场景。
核心特性 ¶
- 基于 sync.Map + close(chan struct{}) 实现,零外部依赖
- 泛型支持,类型安全
- race detector 友好
- panic 安全:fn panic 不会导致等待者死锁
快速接入 ¶
sf := singleflight.New[string]()
val, err := sf.Do(ctx, "token:wechat_app", func(ctx context.Context) (string, error) {
return fetchAccessToken(ctx)
})
原理 ¶
调用 Do(key, fn) 时: 1. sync.Map.LoadOrStore 保证只有一个 goroutine 成为 caller 2. caller 执行 fn,完成后 close(ready) 广播通知 3. 其他 goroutine (waiter) 通过 <-ready 等待,读取共享结果 4. close 提供 happens-before 保证,字段读取无 data race
Index ¶
- type Flight
- func (f *Flight[T]) Do(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (T, error)
- func (f *Flight[T]) DoChan(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) <-chan Result[T]
- func (f *Flight[T]) DoShared(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (T, bool, error)
- func (f *Flight[T]) Forget(key string)
- type Result
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Flight ¶
type Flight[T any] struct { // contains filtered or unexported fields }
Flight 对同一个 key 的并发调用只执行一次 fn。 零值可用,无需初始化。
func (*Flight[T]) Do ¶
func (f *Flight[T]) Do(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (T, error)
Do 执行 fn,同一个 key 的并发调用共享结果。
如果多个 goroutine 同时调用 Do 且 key 相同:
- 只有第一个 goroutine(caller)实际执行 fn
- 其他 goroutine(waiter)阻塞等待结果
- caller 完成后所有 waiter 同时获得返回值
panic 处理(与 golang.org/x/sync/singleflight 语义一致):
- 若 fn panic,panic 向首个 caller 自然传播(保留原始调用栈)
- waiter 被唤醒并拿到由 panic 转化的 error(而非零值+nil 的伪成功—— 调用方无法区分"合法零值"与"执行失败",会静默污染下游缓存/状态)
- map 中的 key 记录会被清理,不会因 panic 残留导致后续调用死锁
func (*Flight[T]) DoChan ¶ added in v0.6.0
func (f *Flight[T]) DoChan(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) <-chan Result[T]
DoChan 与 Do 语义相同,但结果经 channel 返回,调用方可用 select 组合:
// leader 免疫取消 + waiter 各自 ctx 退出
resCh := f.DoChan(context.WithoutCancel(ctx), key, dial)
select {
case r := <-resCh:
...
case <-ctx.Done():
return ctx.Err() // 仅本 waiter 退出,不影响 leader
}
fn 始终在内部 goroutine 执行;返回的 channel 缓冲 1,调用方放弃读取 不会泄漏 goroutine。leader 的 fn 是否免疫取消由调用方传入的 ctx 决定 (配合 context.WithoutCancel)。
func (*Flight[T]) DoShared ¶ added in v0.8.0
func (f *Flight[T]) DoShared(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (T, bool, error)
DoShared 是 Do 的折叠可观测形态(#18):额外返回 Shared——true 表示 本次调用与其他调用方共享了同一次 fn(自己是 waiter 而非 leader)。 用于埋点区分「本轮真采集」与「复用了并发中的那一轮」;Do 保持 (T, error) 不动,不破坏现有调用点。
Click to show internal directories.
Click to hide internal directories.