是否可以
LoadOrStore
进入 Go sync.Map
而无需每次都创建新的结构?如果没有,有哪些替代方案?
这里的用例是,如果我使用
sync.Map
作为缓存,其中缓存未命中很少见(但有可能),并且在缓存未命中时我想添加到地图中,我需要每次都初始化一个结构 LoadOrStore
被调用,而不仅仅是在需要时创建结构。我担心这会损害 GC,初始化数十万个不需要的结构。
computeIfAbsent
来完成。
你可以尝试:
var m sync.Map
s, ok := m.Load("key")
if !ok {
s, _ = m.LoadOrStore("key", "value")
}
fmt.Println(s)
这是我的解决方案:使用sync.Map和sync.One
type syncData struct {
data interface{}
once *sync.Once
}
func LoadOrStore(m *sync.Map, key string, f func() (interface{}, error)) (interface{}, error) {
temp, _ := m.LoadOrStore(key, &syncData{
data: nil,
once: &sync.Once{},
})
d := temp.(*syncData)
var err error
if d.data == nil {
d.once.Do(func() {
d.data, err = f()
if err != nil {
//if failed, will try again by new sync.Once
d.once = &sync.Once{}
}
})
}
return d.data, err
}
可以使用
sync.OnceValue
来实现。但是,您需要谨慎对待值的类型。
func computeIfAbsent[E any](m *sync.Map, key any, f func() E) E {
if v, ok := m.Load(key); ok {
return v.(E)
}
v, ok := m.LoadOrStore(key, sync.OnceValue(f))
if !ok {
v := v.(func() E)()
m.Store(key, v)
return v
} else if f, ok := v.(func() E); ok {
return f()
}
return v.(E)
}
import "sync"
Map 就像 Go 的 map[interface{}]interface{} 但对于 多个 goroutine 并发使用,无需额外锁定或 协调。加载、存储和删除以摊余常量运行 时间。
Map 类型是专门的。大多数代码应该使用普通的 Go 映射 相反,通过单独的锁定或协调,以获得更好的类型安全性 并更容易维护地图上的其他不变量 内容。
Map 类型针对两种常见用例进行了优化:(1) 当条目 对于给定的键仅写入一次但读取多次,如 仅增长的缓存,或 (2) 当多个 goroutine 读取、写入、 并覆盖不相交的键集的条目。在这两种情况下, 与 Go 相比,使用 Map 可以显着减少锁争用 映射与单独的互斥体或 RWMutex 配对。
解决这些问题的通常方法是构建使用模型,然后对其进行基准测试。
例如,由于“缓存未命中很少见”,因此假设
Load
将在大部分时间工作,并且仅在必要时使用 LoadOrStore
(具有值分配和初始化)。
$ go test map_test.go -bench=. -benchmem
BenchmarkHit-4 2 898810447 ns/op 44536 B/op 1198 allocs/op
BenchmarkMiss-4 1 2958103053 ns/op 483957168 B/op 43713042 allocs/op
$
map_test.go
:
package main
import (
"strconv"
"sync"
"testing"
)
func BenchmarkHit(b *testing.B) {
for N := 0; N < b.N; N++ {
var m sync.Map
for i := 0; i < 64*1024; i++ {
for k := 0; k < 256; k++ {
// Assume cache hit
v, ok := m.Load(k)
if !ok {
// allocate and initialize value
v = strconv.Itoa(k)
a, loaded := m.LoadOrStore(k, v)
if loaded {
v = a
}
}
_ = v
}
}
}
}
func BenchmarkMiss(b *testing.B) {
for N := 0; N < b.N; N++ {
var m sync.Map
for i := 0; i < 64*1024; i++ {
for k := 0; k < 256; k++ {
// Assume cache miss
// allocate and initialize value
var v interface{} = strconv.Itoa(k)
a, loaded := m.LoadOrStore(k, v)
if loaded {
v = a
}
_ = v
}
}
}
}