在Go中是否有类似于Java的ConcurrentMap.computeIfAbsent的函数?

时间:2018-03-28 07:11:49

标签: dictionary go concurrency

我试图在Go的标准库和许多其他类似于Java的ConcurrentMap.computeIfAbsent的缓存库中找到此函数。 我在标准库中找到了sync.Map,它看起来像我正在寻找的东西。我想将sync.Map用作并发映射。问题是以下函数不像Java的ConcurrentMap那样提供延迟计算。

func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)

  

LoadOrStore返回密钥的现有值(如果存在)。   否则,它存储并返回给定值。加载的结果是   如果值已加载则为true,如果存储则为false。

这是原子的,但这个功能对我来说没有意义。因为我不熟悉这门语言,所以我不明白它的目的。我不知道为什么第二个参数是一个值而不是一个函数。由于param会被热切地评估,所以我不知道每次重新计算这个值时这个函数是如何有用的。

如果函数签名类似于

,那么IMO会更有用

func (m *Map) LoadOrStore(key interface{}, f func() interface{}) (actual interface{}, loaded bool)

我的意思是为什么我们在有价值时需要地图? 很确定我错过了什么。

让我详细说明一下。我不明白为什么函数将值作为参数而不是函数,我很确定我会错过一些东西。我希望以并发方式从地图中获取值。当一个键不存在时,我想计算该值并以原子方式将其放入映射中。我知道我不应该将它与Java中的ConcurrentMap进行比较,因为语言和范例是不同的,但是说出我来自哪里可能会有用,它可以使任何正在学习的人受益Go获得更好的理解关于这种差异。

这是Java ConcurrentMap中的类似函数。

computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

  

如果指定的键尚未与值关联(或者是   映射到null),尝试使用给定的映射计算其值   函数并将其输入此映射,除非为null。

问题是为什么LoadOrStore函数取值而不是函数?任何有关API为何以这种方式设计的见解都将受到赞赏。还有一种方法可以在不使用地图周围的显式锁定的情况下完成与我在Java computeIfAbsent中相同的操作吗?

更新 我发现修改sync.Map.LoadOrStore以获取函数而不是值是非常容易的。

https://play.golang.org/p/VBIaS8ZV38o

不确定它是否会按预期工作。

1 个答案:

答案 0 :(得分:2)

sync.Map的目的是 以提供推迟值计算的方法,但为并发使用提供一般的map[interface{}]interface{} 安全由多个goroutines。如果没有附加/显式同步,并发使用map类型并不安全。

sync.Map

的文件中明确指出了这一点
  

地图就像是一个Go地图[interface {}] interface {},但是多个goroutine没有额外的锁定或协调,可以安全地并发使用。加载,存储和删除以分摊的常量时间运行。

     

地图类型为专用。大多数代码应使用普通的Go映射,使用单独的锁定或协调,以获得更好的类型安全性,并使其更容易维护其他不变量以及地图内容。

     

针对两种常见用例优化了 :( 1)当给定键的条目只写入一次但多次读取时,如仅在增长的缓存中,或者( 2)当多个goroutine读取,写入和覆盖不相交的密钥集的条目时。在这两种情况下,与使用单独的互斥锁或RWMutex配对的Go地图相比,使用Map可以显着减少锁争用。

至于为什么sync.Map有一个

LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)

方法而不是:

LoadOrComputeAndStore(key interface{}, f func() interface{}) (actual interface{}, loaded bool)

这个问题是基于意见的,因此也是Stackoverflow的主题。但由于一个原因:前者更容易使用(当你已经拥有该值时,你不必使用函数文字),并且后者并不总是需要(尽管在某些情况下会非常有用)。

请注意,我们可以轻松创建具有此“功能”的自定义同步地图:

type MyMap struct {
    sync.Map
}

func (m *MyMap) LoadOrComputeAndStore(key interface{}, f func() interface{}) (actual interface{}, loaded bool) {
    actual, loaded = m.Load(key)
    if loaded {
        return
    }
    return m.LoadOrStore(key, f())
}

使用它的示例:

m := &MyMap{Map: sync.Map{}}

f := func() interface{} {
    fmt.Println("calculating...")
    return "myvalue"
}
key := "mykey"

fmt.Println(m.LoadOrComputeAndStore(key, f))
fmt.Println(m.LoadOrComputeAndStore(key, f))

输出(在Go Playground上尝试):

calculating...
myvalue false
myvalue true

如输出中所示,f()仅被调用一次。首先拨打m.LoadOrComputeAndStore()来电f(),然后报告loaded=false(因为key尚未在地图中)。第二次致电m.LoadOrComputeAndStore()不会致电f(),并报告loaded=true

请注意,这是实现您想要的“简单且充足”的方式。但它不是“防弹”,这意味着LoadOrComputeAndStore()的实现并不能保证f()只能被调用一次。这是因为它首先使用Map.Load()加载值。如果它发现它不在地图中,那么它会调用f(),在此期间并发goroutine可能会这样做。因此,如果f()需要很长时间才能返回并且多个goroutines使用了地图,则可能会多次调用f()。如果需要保证避免多次f()次调用,那么您应该在地图中使用明确的sync.RWMutex

总而言之:上面的示例实现在大多数情况下应该足够了,即当f()没有副作用时,您只需要LoadOrComputeAndStore()来避免调用{{ 1}}由于执行时间长。如果f()确实具有不得重复的副作用,则此实现不足并且需要手动锁定