4

问题 1

我正在为我的服务器构建/搜索 RAM 内存缓存层。它是一个简单的 LRU 缓存,需要处理并发请求(都 Gets an Sets)。

我发现https://github.com/pmylund/go-cache声称是线程安全的。

就获取存储的接口而言,这是正确的。但是如果多个 goroutine 请求相同的数据,它们都在检索指向同一内存块的指针(存储在接口中)。如果任何 goroutine 更改了数据,这将不再是非常安全的。

有没有解决这个问题的缓存包?


问题 1.1

如果问题 1的答案是否定的,那么建议的解决方案是什么?
我看到两个选项:

备选方案 1
解决方案:使用 a 将值存储在包装结构中,sync.Mutex以便每个 goroutine 在读取/写入数据之前需要锁定数据。
type cacheElement struct { value interface{}, lock sync.Mutex }
缺点:缓存变得不知道对数据所做的更改,甚至可能已将其从缓存中删除。一个 goroutine 也可能锁定其他 goroutine。

备选方案2
解决方案:制作数据副本(假设数据本身不包含指针)
缺点:每次执行缓存Get 时分配内存,垃圾收集更多。


对不起,多部分问题。但您不必全部回答。如果您对问题 1 有一个好的答案,那对我来说就足够了!

4

2 回答 2

5

备选方案 2 对我来说听起来不错,但请注意,您不必为每个cache.Get(). 只要您的数据可以被认为是不可变的,您就可以同时使用多个读取器访问它。

如果您打算修改它,您只需创建一个副本。这个成语称为 COW(写时复制),在并发软件设计中很常见。它特别适合具有高读/写比率的场景(就像缓存一样)。

因此,无论何时要修改缓存条目,基本上都必须:

  1. 创建旧缓存数据的副本(如果有)。
  2. 修改数据(在这一步之后,数据应该被认为是不可变的,不能再改变了)
  3. 添加/替换缓存中的现有元素。为此,您可以使用您之前指出的 go-cache 库(它基于锁),或者编写您自己的无锁库来简单地以原子方式交换指向数据元素的指针。

此时任何执行cache.Get操作的 goroutine 都会获得新数据。然而,现有的 goroutines 可能仍在读取旧数据。因此,您的程序可能会同时对同一数据的许多不同版本进行操作。不过不用担心,只要所有 goroutine 完成对旧数据的访问,GC 就会自动收集它。

于 2012-07-11T12:28:50.550 回答
3

tux21b 给出了一个很好的答案。我只是指出您不必返回指向数据的指针。您可以将非指针值存储在缓存中,并且 go 将按值传递,这将是一个副本。然后您的 Get 和 Set 方法将是安全的,因为实际上没有任何东西可以修改缓存内容。

于 2012-07-11T14:20:13.960 回答