问题1
我正在为我的服务器构建/搜索RAM内存缓存层。它是一个简单的LRU缓存,需要处理并发请求(两者都是一组)。
我发现https://github.com/pmylund/go-cache声称是线程安全的。
就获取存储的界面而言,这是事实。但是如果多个goroutine请求相同的数据,它们都会检索指针(存储在接口中)到同一块内存。如果任何goroutine改变了数据,这将不再是非常安全的。
是否有解决此问题的缓存包?
问题1.1
如果问题1 的答案是否,那么建议的解决方案是什么?
我看到两个选择:
备选方案1
解决方案:使用sync.Mutex
将值存储在包装结构中,以便每个goroutine在读取/写入数据之前都需要锁定数据。
type cacheElement struct { value interface{}, lock sync.Mutex }
缺点:缓存变得不知道对数据所做的更改,甚至可能已将其从缓存中删除。一个goroutine也可能锁定其他人。
备选方案2
解决方案:制作数据的副本(假设数据本身不包含指针)
缺点:每次执行缓存Get时都会分配内存,更多的垃圾回收。
对于多部分问题,我们深表歉意。但你不必回答所有问题。如果您对问题1有一个很好的答案,那对我来说已经足够了!
答案 0 :(得分:5)
备选方案2对我来说听起来不错,但请注意,您不必为每个cache.Get()
复制数据。只要您的数据可以被视为不可变,您就可以同时使用多个读取器访问它。
如果您想要修改副本,则只需创建副本。这个习惯用法称为COW(写入时复制),在并发软件设计中很常见。它特别适用于具有高读/写比率的场景(就像缓存一样)。
因此,每当您想要修改缓存条目时,您基本上都必须:
此时执行cache.Get
操作的任何goroutine都将获得新数据。但是,现有的goroutine可能仍在读取旧数据。因此,您的程序可能同时在同一数据的许多不同版本上运行。但不要担心,只要所有goroutine都已完成访问旧数据,GC就会自动收集它。
答案 1 :(得分:3)