为 go 函数添加缓存,就好像它是静态成员一样

问题描述 投票:0回答:4

假设我有一个昂贵的功能

func veryExpensiveFunction(int) int

并且这个函数会因为同一个号码而被多次调用。

有没有一种好方法可以让这个函数存储以前的结果,以便在再次调用该函数时使用,甚至可以为veryExppressiveFunction2重用?

显然,可以添加一个参数

func veryExpensiveFunctionCached(p int, cache map[int]int) int {
    if val, ok := cache[p]; ok {
        return val
    }
    result := veryExpensiveFunction(p)
    cache[p] = result
    return result
}

但现在我必须在某个地方创建缓存,而我不关心它。如果可能的话,我宁愿将其作为“静态函数成员”。

在go中模拟静态成员缓存的好方法是什么?

go memoization
4个回答
9
投票

可以使用闭包;并让闭包管理缓存。

func InitExpensiveFuncWithCache() func(p int) int {
    var cache = make(map[int]int)
    return func(p int) int {
        if ret, ok := cache[p]; ok {
            fmt.Println("from cache")
            return ret
        }
        // expensive computation
        time.Sleep(1 * time.Second)
        r := p * 2
        cache[p] = r
        return r
    }
}

func main() {
    ExpensiveFuncWithCache := InitExpensiveFuncWithCache()
    
    fmt.Println(ExpensiveFuncWithCache(2))
    fmt.Println(ExpensiveFuncWithCache(2))
}

output:
4
from cache
4

veryExpensiveFunctionCached := InitExpensiveFuncWithCache()

并将包装的函数与您的代码一起使用。 你可以尝试一下这里

如果您希望它可重用,请将签名更改为

InitExpensiveFuncWithCache(func(int) int)
,以便它接受函数作为参数。将它包裹在闭包中,用它代替昂贵的计算部分。


6
投票

如果此缓存将在 http 处理程序中使用,则需要小心同步。在Go标准库中,每个http请求都在专用的goroutine中处理,此时我们处于并发和竞争条件的领域。我建议使用 RWMutex 来确保数据一致性。

对于缓存注入,您可以将其注入到创建http处理程序的函数中。 这是原型

type Cache struct {
    store map[int]int
    mux   sync.RWMutex
}

func NewCache() *Cache {
    return &Cache{make(map[int]int), sync.RWMutex{}}
}

func (c *Cache) Set(id, value int) {
    c.mux.Lock()
    c.store[id] = id
    c.mux.Unlock()
}

func (c *Cache) Get(id int) (int, error) {
    c.mux.RLock()
    v, ok := c.store[id]
    c.mux.RUnlock()

    if !ok {
        return -1, errors.New("a value with given key not found")
    }

    return v, nil
}


func handleComplexOperation(c *Cache) http.HandlerFunc {
    return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request){
        
    })
}

3
投票

Go 标准库使用以下样式来提供“静态”函数(例如 flag.CommandLine),但它利用底层状态:

// "static" function is just a wrapper
func Lookup(p int) int { return expCache.Lookup(p) }

var expCache = NewCache()

func newCache() *CacheExpensive { return &CacheExpensive{cache: make(map[int]int)} }

type CacheExpensive struct {
    l     sync.RWMutex // lock for concurrent access
    cache map[int]int
}

func (c *CacheExpensive) Lookup(p int) int { /*...*/ }

这种设计模式不仅允许简单的一次性使用,还允许隔离使用:

var (
    userX = NewCache()
    userY = NewCache()
)

userX.Lookup(12)
userY.Lookup(42)

0
投票

您可以编写一个生成器来创建cacheMap。

像这样:

    type cachedFn[K any, V any] struct {
        cacheMap       sync.Map
        getFunc        func(K) (V, error)
    }
    
    func NewCacheFn[K any, V any](getFunc func(K) (V, error)) *cachedFn[K, V] {
        return &cachedFn[K, V]{getFunc: getFunc}
    }
    func (c *cachedFn[K, V]) Get(key K) (V, error) {
          // you logic
    }

像这样使用这个生成器:

    ExpensiveFuncWithCache := NewCacheFn(ExpensiveFunc).Get
    ExpensiveFuncWithCache(arg)

这是一个示例:https://go.dev/play/p/guzOWRKi-yp

还有这个:https://github.com/ahuigo/golib/blob/f973a33841c74b3105579b5ac268c773b38f50ce/lock/sync/cache/cache-wrapper_test.go

© www.soinside.com 2019 - 2024. All rights reserved.