问题内容
我正在尝试找出在 golang 中为泛型变量实现单例的最不糟糕的方法。使用普通的 sync.once
模式与全局变量是行不通的,因为通用类型信息在那里不可用(如下)。
这个示例是人为设计的,但实际上,维护单例的代码可以与定义 t
的客户端代码分开(例如在库中)。
假设此库代码,其中 t 的具体值未知:
type cache[t any] struct{} var ( cacheonce sync.once cache cache[any] // global singleton ) func getorcreatecache[t any]() cache[t] { cacheonce.do(func() { typedcache := buildcache() cache = typedcache.(cache[any]) // invalid type assertion }) return cache.(cache[t]) // invalid type assertion }
并假设这个单独的客户端代码,其中 t
被定义为 string
:
stringCache := getOrCreateCache[string]()
实现这一目标的最佳方法是什么?
正确答案
我最终使用 atomic.pointer
api 解决了这个问题,并将其整合到一个简单的库中,供其他感兴趣的人使用:单线。使用单线态重新处理原始帖子如下所示:
示例库代码:
type cache[t any] struct{} var singleton = &singlet.singleton{} func getorcreatecache[t any]() (cache[t], err) { return singlet.getordo(singleton, func() cache[t] { return cache[t]{} }) }
客户端代码:
stringcache := getorcreatecache[string]()
以及支持此功能的 singlet 库代码:
var ErrTypeMismatch = errors.New("the requested type does not match the singleton type") type Singleton struct { p atomic.Pointer[any] mtx sync.Mutex } func GetOrDo[T any](singleton *Singleton, fn func() T) (result T, err error) { maybeResult := singleton.p.Load() if maybeResult == nil { // Lock to guard against applying fn twice singleton.mtx.Lock() defer singleton.mtx.Unlock() maybeResult = singleton.p.Load() // Double check if maybeResult == nil { result = fn() var resultAny any = result singleton.p.Store(&resultAny) return result, nil } } var ok bool result, ok = (*maybeResult).(T) if !ok { return *new(T), ErrTypeMismatch } return result, nil }
我希望对遇到这种情况的其他人有所帮助。