perf(cache): Get 方法读锁优先,double-check 升级写锁

将 FileCache.Get 的全局写锁改为读锁优先,仅在需要修改状态
(过期删除、CachedAt 迁移)时升级为写锁并 double-check,
减少读路径的锁竞争。近似 LRU 场景下 Get 不再更新访问时间。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-24 13:13:18 +08:00
parent 0cf943fede
commit 308529d568

View File

@ -88,31 +88,54 @@ func NewFileCache(maxEntries, maxSize int64, inactive time.Duration) *FileCache
// - *FileEntry: 缓存条目,包含文件内容和元数据
// - bool: 是否找到有效缓存
func (c *FileCache) Get(path string) (*FileEntry, bool) {
c.mu.Lock()
c.mu.RLock()
entry, ok := c.entries[path]
if !ok {
c.mu.Unlock()
c.mu.RUnlock()
return nil, false
}
// 检查是否过期
// 只读判断:检查是否过期
if time.Since(entry.LastAccess) > c.inactive {
c.removeEntry(entry)
c.mu.RUnlock()
// 升级为写锁double-check entry 仍存在且仍过期
c.mu.Lock()
if entry, ok = c.entries[path]; ok && time.Since(entry.LastAccess) > c.inactive {
c.removeEntry(entry)
}
c.mu.Unlock()
return nil, false
}
// 迁移处理: CachedAt 为零值时视为刚刚缓存(旧条目)
// 在锁内执行,确保并发安全
if entry.CachedAt.IsZero() {
// 只读判断CachedAt 是否需要迁移
needMigrate := entry.CachedAt.IsZero()
c.mu.RUnlock()
if needMigrate {
// 升级为写锁double-check entry 仍存在
c.mu.Lock()
entry, ok = c.entries[path]
if !ok {
c.mu.Unlock()
return nil, false
}
// double-check 过期RUnlock 和 Lock 之间可能已过期)
if time.Since(entry.LastAccess) > c.inactive {
c.removeEntry(entry)
c.mu.Unlock()
return nil, false
}
entry.CachedAt = time.Now()
entry.LastAccess = time.Now()
c.lruList.MoveToFront(entry.element)
c.mu.Unlock()
return entry, true
}
// 更新访问时间并移到 LRU 链表头部
entry.LastAccess = time.Now()
c.lruList.MoveToFront(entry.element)
c.mu.Unlock()
// 近似 LRU: 不更新 LastAccess/LRU 位置,直接返回
// 精确 LRU 由 Set/Delete/evict 操作保证
return entry, true
}