头图

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()  

}


运维社
12 声望4 粉丝