线程安全(Goroutine安全)的Go缓存

huangapple go评论97阅读模式
英文:

Thread-safe (Goroutine-safe) cache in Go

问题

问题 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也可能锁定其他goroutine。

备选方案2
*解决方案:*复制数据(假设数据本身不包含指针)
*缺点:*每次执行缓存获取时都需要进行内存分配,增加了垃圾回收的负担。


对于这个多部分问题我很抱歉。但是你不必回答所有问题。如果你对问题1有一个好的答案,那对我来说就足够了!

英文:

Question 1

I am building/searching for a RAM memory cache layer for my server. It is a simple LRU cache that needs to handle concurrent requests (both Gets an Sets).

I have found https://github.com/pmylund/go-cache claiming to be thread safe.

This is true as far as getting the stored interface. But if multiple goroutines requests the same data, they are all retrieving a pointer (stored in the interface) to the same block of memory. If any goroutine changes the data, this is no longer very safe.

Are there any cache-packages out there that tackles this problem?


Question 1.1

If the answer to Question 1 is No, then what would be the suggested solution?
I see two options:

Alternative 1
Solution: Storing the values in a wrapping struct with a sync.Mutex so that each goroutine needs to lock the data before reading/writing to it.
type cacheElement struct { value interface{}, lock sync.Mutex }
Drawbacks: The cache becomes unaware of changes made to data or might even have dropped it out of the cache. One goroutine might also lock others.

Alternative 2
Solution: Make a copy of the data (assuming the data in itself doesn't contain pointers)
Drawbacks: Memory allocation every time a cache Get is performed, more garbage collection.


Sorry for the multipart question. But you don't have to answer all of them. If you have a good answer to Question 1, that would be sufficient for me!

答案1

得分: 5

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

只有在您打算修改数据时才需要创建副本。这种习惯用法称为 COW(写时复制),在并发软件设计中非常常见。它特别适用于具有高读写比(就像缓存)的场景。

因此,每当您想要修改缓存条目时,您基本上需要:

  1. 如果有的话,创建旧缓存数据的副本。
  2. 修改数据(在此步骤之后,数据应被视为不可变,不得再更改)
  3. 添加/替换缓存中的现有元素。您可以使用之前提到的 go-cache 库(基于锁)进行操作,或者编写自己的无锁库,只需原子地交换数据元素的指针。

此时执行 cache.Get 操作的任何 goroutine 都将获得新数据。但是,现有的 goroutine 可能仍在读取旧数据。因此,您的程序可能同时操作许多不同版本的相同数据。但不用担心,一旦所有 goroutine 完成对旧数据的访问,垃圾回收器将自动收集它。

英文:

Alternative 2 sounds good to me, but please note that you do not have to copy the data for each cache.Get(). As long as your data can be considered immutable, you can access it with many multiple readers at once.

You only have to create a copy if you intend to modify it. This idiom is called COW (copy on write) and is quite common in concurrent software design. It's especially well suited for scenarios with a high read/write ratio (just like a cache).

So, whenever you want to modify a cached entry, you basically have to:

  1. create a copy of the old cached data, if any.
  2. modify the data (after this step, the data should be considered immutable and must not be changed anymore)
  3. add / replace the existing element in the cache. You could either use the go-cache library you have pointed out earlier (which is based on locks) for that, or write your own lock-free library that simply swaps the pointers to the data element atomically.

At this point any goroutine that performs a cache.Get operation will get the new data. Existing goroutines however, might still be reading the old data. So, your program might operate on many different versions of the same data at once. But don't worry, as soon as all goroutines have finished accessing the old data, the GC will collect it automatically.

答案2

得分: 3

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

英文:

tux21b gave a good answer. I'll just point out that you don't have to return pointers to data. you can store non pointer values in your cache and go will pass by value which will be a copy. Then your Get and Set methods will be safe since nothing can actually modify the cache contents.

huangapple
  • 本文由 发表于 2012年7月11日 20:06:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/11432308.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定