douwen5681 2018-03-28 07:11
浏览 119

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

I tried to find this function in Go's standard library and many other cache libraries that resemble to Java's ConcurrentMap.computeIfAbsent. I found sync.Map in the standard library which looks like what I'm looking for. I’d like to use sync.Map as a concurrent map. The problem is that the following function does not provide a defer computation as Java's ConcurrentMap does.

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

LoadOrStore returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.

It’s atomic, but the function doesn’t make sense to me. I don’t understand its purpose since I'm new to the language. I don’t know why the second param is a value not a function. Since the param will be evaluated eagerly, I have no idea how is this function useful when the value is recomputed every time anyway.

It would be much more useful IMO if the function signature is like

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

I mean why do we need a map when we have a value already? Pretty sure I miss something.

Let me elaborate that a bit. I don't get the reason why the function takes a value as parameter instead of a function and I'm pretty sure that I miss something. I want to get a value out of a map in a concurrent manner. When a key doesn't exist, I want to compute the value and put it into the map atomically. I know I shouldn't compare it with the ConcurrentMap in Java since the language and paradigm are different, but it could be useful to say where I'm from and it could benefit anyone who is learning Go to get a little bit of better understanding on this difference.

Here is the similar function in Java ConcurrentMap.

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

If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.

The question is why LoadOrStore function takes a value instead of a function? Any insight on why the API is designed this way would be appreciated. Also Is there a way to accomplish the same thing as I do in Java computeIfAbsent without using an explicit lock around the map?

Updated I found that it's very easy to modify the sync.Map.LoadOrStore to take a function instead of a value.

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

Not sure that it will work as expected though.

  • 写回答

1条回答 默认 最新

  • doupai5450 2018-03-28 07:25
    关注

    The purpose of sync.Map is not to provide a way to defer the value calculation, but to provide a general map[interface{}]interface{} safe for concurrent use by multiple goroutines. The map type in go is not safe for concurrent use without additional / explicit synchronization.

    This is clearly stated in the doc of sync.Map:

    Map is like a Go map[interface{}]interface{} but is safe for concurrent use by multiple goroutines without additional locking or coordination. Loads, stores, and deletes run in amortized constant time.

    The Map type is specialized. Most code should use a plain Go map instead, with separate locking or coordination, for better type safety and to make it easier to maintain other invariants along with the map content.

    The Map type is optimized for two common use cases: (1) when the entry for a given key is only ever written once but read many times, as in caches that only grow, or (2) when multiple goroutines read, write, and overwrite entries for disjoint sets of keys. In these two cases, use of a Map may significantly reduce lock contention compared to a Go map paired with a separate Mutex or RWMutex.

    As to why sync.Map has a

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

    method instead of:

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

    That question is opinion based and therefore off-topic for Stackoverflow. But for one reason: the former is easier to use (you don't have to use a function literal when you already have the value) and the latter is not always needed (even though in some cases would be really useful).

    Note that we can easily create our custom sync map that has this "feature":

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

    Example using it:

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

    Output (try it on the Go Playground):

    calculating...
    myvalue false
    myvalue true
    

    As seen in the output, f() is only called once. First call to m.LoadOrComputeAndStore() calls f(), and reports loaded=false (as key was not yet in the map). Second call to m.LoadOrComputeAndStore() does not call f(), and reports loaded=true.

    Note that this is an "easy and sufficient" way to achieve what you want. But it is not "bullet proof", meaning that the implementation of LoadOrComputeAndStore() does not guarantee that f() may only be called once. This is because it first loads the value using Map.Load(). If it finds that it's not in the map, then it calls f(), during which a concurrent goroutine may do the same. So f() may be called multiple times if f() takes long to return and the map is used by multiple goroutines. If guarantee is needed to avoid multiple f() calls, then you should use an explicit sync.RWMutex in the map.

    So to sum it up: the above example implementation should be sufficient in most cases, namely when f() does not have a side effect, and you only need LoadOrComputeAndStore() to avoid having to call f() due to its long execution time. If f() does have a side effect which must not be repeated, then this implementation is insufficient and manual locking is required.

    评论

报告相同问题?

悬赏问题

  • ¥15 (希望可以解决问题)ma和mb文件无法正常打开,打开后是空白,但是有正常内存占用,但可以在打开Maya应用程序后打开场景ma和mb格式。
  • ¥20 ML307A在使用AT命令连接EMQX平台的MQTT时被拒绝
  • ¥20 腾讯企业邮箱邮件可以恢复么
  • ¥15 有人知道怎么将自己的迁移策略布到edgecloudsim上使用吗?
  • ¥15 错误 LNK2001 无法解析的外部符号
  • ¥50 安装pyaudiokits失败
  • ¥15 计组这些题应该咋做呀
  • ¥60 更换迈创SOL6M4AE卡的时候,驱动要重新装才能使用,怎么解决?
  • ¥15 让node服务器有自动加载文件的功能
  • ¥15 jmeter脚本回放有的是对的有的是错的