Help us shape the future of Go! Take the 2026 Go Developer Survey

singleflight

package
v0.23.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: MIT Imports: 5 Imported by: 0

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

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 New

func New[T any]() *Flight[T]

New 创建一个 Flight 实例。

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) 不动,不破坏现有调用点。

func (*Flight[T]) Forget

func (f *Flight[T]) Forget(key string)

Forget 移除指定 key 的进行中调用记录。 之后对该 key 的 Do 调用会发起新的 fn 执行。

type Result added in v0.6.0

type Result[T any] struct {
	Val    T
	Err    error
	Shared bool // true 表示与其他调用方共享了同一次 fn
	// Waiters 是本轮折叠掉的并发调用方数(leader 视角:不含自己,>=0)。
	// 排障时可直接看出「这一轮合并了几个调用方」(#18)。
	Waiters int
}

Result 是 DoChan 的单次结果。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL