是否可以LoadOrStore
进入Go sync.Map
而无需每次都创建新结构?如果没有,有哪些替代方法可用?
这里的用例是,如果我将sync.Map
用作高速缓存,其中高速缓存未命中的情况很少(但可能),并且在高速缓存未命中的情况下,我想将其添加到地图中,则需要初始化一个结构每次LoadOrStore
都会被调用,而不仅仅是在需要时创建结构。我担心这会对GC造成伤害,会初始化数十万个不需要的结构。
在Java中,可以使用computeIfAbsent
完成。
答案 0 :(得分:2)
import "sync"
地图就像Go地图[interface {}] interface {},但对于 由多个goroutine并发使用,而无需额外的锁定或 协调。加载,存储和删除以摊销常量运行 时间。
地图类型是专用的。大多数代码应使用普通的Go地图 相反,具有单独的锁定或协调功能,以提高类型安全性 并使其更易于维护其他不变式和地图 内容。
Map类型针对两种常见用例进行了优化:(1)输入 对于给定的密钥,只能写入一次但可以读取多次,例如 仅增长的缓存,或者(2)当多个goroutine进行读取,写入, 并覆盖不相交的键集的条目。在这两种情况下, 与Go相比,使用Map可以显着减少锁争用 地图与单独的Mutex或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
}
}
}
}
答案 1 :(得分:0)
您可以尝试:
var m sync.Map
s, ok := m.Load("key")
if !ok {
s, _ = m.LoadOrStore("key", "value")
}
fmt.Println(s)