Golang协程中的读写安全性探讨
Golang协程中的读写安全性探讨 大家好
我编写了一个函数,用于读取一些数据,这些数据可能计算量很大,需要反复计算。因此,该函数在首次读取时将数据缓存到映射中,之后直接从映射返回值。
从多个 Go 协程中访问此函数是否安全?
2 回复
在Go语言中,从多个协程并发访问一个缓存映射是不安全的,除非你采取适当的同步措施。你的函数需要保护对共享映射的读写操作,以避免数据竞争和未定义行为。
以下是一个使用sync.RWMutex实现线程安全缓存的示例:
package main
import (
"sync"
)
type Cache struct {
mu sync.RWMutex
items map[string]interface{}
}
func NewCache() *Cache {
return &Cache{
items: make(map[string]interface{}),
}
}
func (c *Cache) Get(key string, computeFunc func() interface{}) interface{} {
// 先尝试获取读锁
c.mu.RLock()
val, found := c.items[key]
c.mu.RUnlock()
if found {
return val
}
// 未命中缓存,获取写锁
c.mu.Lock()
defer c.mu.Unlock()
// 双重检查,防止其他协程已经写入
if val, found := c.items[key]; found {
return val
}
// 计算并缓存结果
val = computeFunc()
c.items[key] = val
return val
}
// 使用示例
func main() {
cache := NewCache()
// 模拟多个协程并发访问
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
result := cache.Get("expensive_data", func() interface{} {
// 模拟耗时计算
return id * 100
})
_ = result // 使用结果
}(i)
}
wg.Wait()
}
如果你使用的是Go 1.9+版本,也可以使用sync.Map来简化并发安全的映射操作:
package main
import (
"sync"
)
type SyncMapCache struct {
m sync.Map
}
func (c *SyncMapCache) Get(key string, computeFunc func() interface{}) interface{} {
// 尝试直接加载
if val, ok := c.m.Load(key); ok {
return val
}
// 计算新值
newVal := computeFunc()
// 尝试存储,如果键已存在则返回现有值
if val, loaded := c.m.LoadOrStore(key, newVal); loaded {
return val
}
return newVal
}
// 使用示例
func main() {
cache := &SyncMapCache{}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
result := cache.Get("data", func() interface{} {
return "computed_value"
})
_ = result
}(i)
}
wg.Wait()
}
第一个示例使用读写锁,适合读多写少的场景;第二个示例使用sync.Map,更适合键值对数量较多或键空间较大的情况。两个实现都确保了多协程访问的安全性。


