lur.Cache是对数据lru的实现,但并不是并发安全的。groupcache.cache进行了进一步的并发封装
// cache is a wrapper around an *lru.Cache that adds synchronization,
// makes values always be ByteView, and counts the size of all keys and
// values.
type cache struct {
mu sync.RWMutex
nbytes int64 // of all keys and values
lru *lru.Cache
nhit, nget int64
nevict int64 // number of evictions
}
并发控制主要通过 sync.RWMutex实现,并且数据的值总是 ByteView
func (c *cache) add(key string, value ByteView) {
// 加锁
c.mu.Lock()
// 利用defer 进行最后的释放锁
defer c.mu.Unlock()
if c.lru == nil {
// 如果lru为nil,则进行初始化
c.lru = &lru.Cache{
// 数据被删除时的钩子函数
OnEvicted: func(key lru.Key, value interface{}) {
val := value.(ByteView)
// 减去字节数
c.nbytes -= int64(len(key.(string))) + int64(val.Len())
// 删除的数据量 ++
c.nevict++
},
}
}
// 数据添加
c.lru.Add(key, value)
// 记录key和value总字节数
c.nbytes += int64(len(key)) + int64(value.Len())
}
通过代码可以发现:
1.并发控制就是通过对lru操作时加锁实现的。
2. 释放锁使用defer,可以保证即使逻辑特别复杂,也能在方法执行完后,可以释放锁,避免某些逻辑下 忘记锁的释放
3. 通过nbytes和nevict字段进行额外信息的记录
func (c *cache) get(key string) (value ByteView, ok bool) {
// 加锁和释放锁
c.mu.Lock()
defer c.mu.Unlock()
// 数据获取次数 +1
c.nget++
// lru未初始化,直接结束
if c.lru == nil {
return
}
// 数据获取
vi, ok := c.lru.Get(key)
if !ok {
return
}
// 命中次数 +1
c.nhit++
// 返回数据, value存储的是ByteView
return vi.(ByteView), true
}
数据获取逻辑也比较简单,加锁->数据获取->额外信息记录->释放锁->数据返回
func (c *cache) items() int64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.itemsLocked()
}
其他方法逻辑类似,加锁,然后操作lru来实现并发安全
1. 使用sync.RWMutex进行并发安全控制
2. 使用defer进行锁释放,保障锁能够被释放
3. 结构体字段设计时,考虑钩子函数,满足外部的定制开发