MRE
go 体验AI代码助手 代码解读复制代码package main
import (
"fmt"
"sync" "time")
// Store 接口模拟原代码中的 Store
type Store interface {
Add(key string, value int)
Get(key string) int
GetLength() int
}
// LRUCache 模拟原代码中的 lru.Cachetype LRUCache struct {
cache map[string]int
mu sync.RWMutex // 内部锁
}
func NewLRUCache() *LRUCache {
return &LRUCache{
cache: make(map[string]int),
}
}
// Add 方法没有加锁保护
func (l *LRUCache) Add(key string, value int) {
l.cache[key] = value
}
// Get 方法
func (l *LRUCache) Get(key string) int {
return l.cache[key]
}
// GetLength 使用读锁
func (l *LRUCache) GetLength() int {
l.mu.RLock()
defer l.mu.RUnlock()
return len(l.cache)
}
// MemoryCache 模拟原代码中的 MemoryCache
type MemoryCache struct {
store Store
mu sync.Mutex // 外部锁
}
func NewMemoryCache(store Store) *MemoryCache {
return &MemoryCache{
store: store,
}
}
// Set 方法加了外部锁
func (m *MemoryCache) Set(key string, value int) {
m.mu.Lock()
defer m.mu.Unlock()
m.store.Add(key, value)
}
func main() {
lru := NewLRUCache()
cache := NewMemoryCache(lru)
// 模拟并发写入和读取长度
var wg sync.WaitGroup
// 启动多个写入 goroutine
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key_%d", i)
cache.Set(key, i)
}(i)
}
// 启动多个读取长度的 goroutine
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 10; j++ {
//直接访问底层实现绕过了上层的并发保护机制
length := lru.GetLength()
fmt.Printf("当前缓存长度: %d\n", length)
time.Sleep(time.Millisecond)
}
}()
}
wg.Wait()
}
该代码会导致以下问题
shell 体验AI代码助手 代码解读复制代码Read at 0x00c000124180 by goroutine 41:
main.(*LRUCache).GetLength()
main.go:42 +0xb6
main.main.func2()
main.go:87 +0xc4
Previous write at 0x00c000124180 by goroutine 40:
runtime.mapassign_faststr()
go1.18/src/runtime/map_faststr.go:203 +0x0
main.(*LRUCache).Add()
main.go:30 +0x5b
main.(*MemoryCache).Set()
main.go:61 +0xd8
main.main.func1()
main.go:77 +0xf1
main.main.func3()
main.go:78 +0x47
疑问点
代码的 58 行已经加锁了,为什么还是触发了 Data Race?
仔细看代码可以发现,加锁的时候 对象为 MemoryCache,而 GetLength()的调用者则为 LRUCache,解释一下就是。 MemoryCache 相当于 青帮,58 行的加锁操作则为 ,帮派保护了帮派成员免收外部帮派欺负。相应的 GetLength()是直接帮派内部发生了内讧,从而导致问题无法解决。
解决方法
Go 体验AI代码助手 代码解读复制代码// Add 方法加锁保护
func (l *LRUCache) Add(key string, value int) {
l.mu.Lock()
l.cache[key] = value
l.mu.Unlock()
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。