Go中使用sync.Map实现线程安全的缓存( 二 )

在SafeCache结构中嵌入了一个sync.Map,它提供了对键值对的并发安全访问:
type SafeCache struct {syncMap sync.Map}向缓存中添加值然后我们定义了一个 Set 方法,该方法允许我们在缓存中存储一个带有指定生存时间(TTL , Time To Live)的值 。TTL 决定了缓存条目应被认为有效的时间长度 。一旦 TTL 过期,在下一个清理周期中将会移除该缓存条目 。
func (sc *SafeCache) Set(key string, value interface{}, ttl time.Duration) {expiration := time.Now().Add(ttl).UnixNano()sc.syncMap.Store(key, CacheEntry{value: value, expiration: expiration})}从缓存中检索值接下来需要的方法是 Get,它使用键从缓存中检索值 。如果没有找到该值或该值已过期,该方法将返回 false:
func (sc *SafeCache) Get(key string) (interface{}, bool) {// ... (see the provided code for the full implementation)}在 Get 方法中重要的是从缓存加载值后进行类型断言 。我们依赖于 sync.Map 的 Load 方法,该方法返回接口 。
entry, found := sc.syncMap.Load(key) if !found {return nil, false } // Type assertion to CacheEntry, as entry is an interface{} cacheEntry := entry.(CacheEntry)从缓存中移除值当然 , 我们还需要一个 Delete 方法,使我们能够从缓存中移除一个值:
func (sc *SafeCache) Delete(key string) {sc.syncMap.Delete(key)}清理过期条目我们通过 CleanUp 方法扩展了缓存,该方法负责定期从缓存中删除过期的条目 。它使用 sync.Map 提供的 Range 方法遍历缓存中的所有键值对,并删除那些TTL已过期的条目:
func (sc *SafeCache) CleanUp() {// ... (see the provided code for the full implementation)}要运行 CleanUp 方法 , 我们可以在初始化缓存时启动一个单独的 Goroutine:
cache := &SafeCache{}go cache.CleanUp()完整的代码片段package cacheimport ( "sync" "time")// CacheEntry is a value stored in the cache.type CacheEntry struct { valueinterface{} expiration int64}// SafeCache is a thread-safe cache.type SafeCache struct { syncMap sync.Map}// Set stores a value in the cache with a given TTL// (time to live) in seconds.func (sc *SafeCache) Set(key string, value interface{}, ttl time.Duration) { expiration := time.Now().Add(ttl).UnixNano() sc.syncMap.Store(key, CacheEntry{value: value, expiration: expiration})}// Get retrieves a value from the cache. If the value is not found// or has expired, it returns false.func (sc *SafeCache) Get(key string) (interface{}, bool) { entry, found := sc.syncMap.Load(key) if !found {return nil, false } // Type assertion to CacheEntry, as entry is an interface{} cacheEntry := entry.(CacheEntry) if time.Now().UnixNano() > cacheEntry.expiration {sc.syncMap.Delete(key)return nil, false } return cacheEntry.value, true}// Delete removes a value from the cache.func (sc *SafeCache) Delete(key string) { sc.syncMap.Delete(key)}// CleanUp periodically removes expired entries from the cache.func (sc *SafeCache) CleanUp() { for {time.Sleep(1 * time.Minute)sc.syncMap.Range(func(key, entry interface{}) bool {cacheEntry := entry.(CacheEntry)if time.Now().UnixNano() > cacheEntry.expiration {sc.syncMap.Delete(key)}return true}) }}最后,你可以运行以下的 mAIn.go 程序来检查缓存是否工作 。我们创建了一个HTTP服务器,它在“/compute”端点监听请求 。该服务器接受一个整数n作为查询参数,并返回昂贵计算的结果(在这种情况下,带有模拟延迟的简单平方操作) 。服务器首先检查缓存,看看给定输入的结果是否已经被缓存;如果没有,它会计算结果,将其存储在缓存中 , 并将其返回给客户端 。
要测试服务器 , 运行代码并请求http://localhost:8080/compute?n=5 。第一个请求会花费更长的时间(由于模拟的延迟),但具有相同n的后续请求将立即返回缓存的结果 。
package mainimport ( "fmt" "log" ".NET/http" "safe-cache/cache" "strconv" "time")func expensiveComputation(n int) int { // Simulate an expensive computation time.Sleep(2 * time.Second) return n * n}func main() { safeCache := &cache.SafeCache{} // Start a goroutine to periodically clean up the cache go safeCache.CleanUp() http.HandleFunc("/compute", func(w http.ResponseWriter, r *http.Request) {query := r.URL.Query()n, err := strconv.Atoi(query.Get("n"))if err != nil {http.Error(w, "Invalid input", http.StatusBadRequest)return}cacheKey := fmt.Sprintf("result_%d", n)cachedResult, found := safeCache.Get(cacheKey)var result intif found {result = cachedResult.(int)} else {result = expensiveComputation(n)safeCache.Set(cacheKey, result, 1*time.Minute)}_, err = fmt.Fprintf(w, "Result: %dn", result)if err != nil {return} }) log.Fatal(http.ListenAndServe(":8080", nil))}


推荐阅读