在sync.Map中LoadOrStore,无需每次创建新结构

问题描述 投票:0回答:4

是否可以

LoadOrStore
进入 Go
sync.Map
而无需每次都创建新的结构?如果没有,有哪些替代方案?

这里的用例是,如果我使用

sync.Map
作为缓存,其中缓存未命中很少见(但有可能),并且在缓存未命中时我想添加到地图中,我需要每次都初始化一个结构
 LoadOrStore
被调用,而不仅仅是在需要时创建结构。我担心这会损害 GC,初始化数十万个不需要的结构。

在 Java 中,可以使用

computeIfAbsent
来完成。

go concurrency
4个回答
0
投票

你可以尝试:

var m sync.Map
s, ok := m.Load("key")
if !ok {
    s, _ = m.LoadOrStore("key", "value")
}

fmt.Println(s)

播放演示


0
投票

这是我的解决方案:使用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
}

0
投票

可以使用

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

-1
投票

包同步

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

            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.