首页 > 解决方案 > 在 sync.Map 中是否有必要使用 Load 后跟 LoadOrStore 来处理复杂值

问题描述

在代码中,具有昂贵的生成值结构的全局映射可能会被多个并发线程修改,哪种模式是正确的?

// equivalent to map[string]*activity where activity is a
// fairly heavyweight structure
var ipActivity sync.Map

// version 1: not safe with multiple threads, I think
func incrementIP(ip string) {
  val, ok := ipActivity.Load(ip)
  if !ok {
    val = buildComplexActivityObject()
    ipActivity.Store(ip, val)
  }

  updateTheActivityObject(val.(*activity), ip)
}

// version 2: inefficient, I think, because a complex object is built 
// every time even through it's only needed the first time
func incrementIP(ip string) {
  tmp := buildComplexActivityObject()
  val, _ := ipActivity.LoadOrStore(ip, tmp)
  updateTheActivity(val.(*activity), ip)
}

// version 3: more complex but technically correct?
func incrementIP(ip string) {
  val, found := ipActivity.Load(ip)
  if !found {
     tmp := buildComplexActivityObject()

     // using load or store incase the mapping was already made in 
     // another store
     val, _ = ipActivity.LoadOrStore(ip, tmp)
  }
  updateTheActivity(val.(*activity), ip)
}

考虑到 Go 的并发模型,第三版是正确的模式吗?

标签: goconcurrency

解决方案


选项 1 显然可以由多个 goroutine 同时调用一个新的ip,并且只有if块中的最后一个会被存储。这种可能性随着时间的延长而大大增加buildComplexActivityObject,因为在关键部分有更多的时间。

buildComplexActivityObject选项 2 有效,但每次都调用,你说这不是你想要的。

鉴于您想buildComplexActivityObject尽可能不频繁地调用,第三种选择是唯一有意义的选择。

但是sync.Map不能保护activity存储的指针引用的实际值。activity更新值时,您还需要在那里同步。


推荐阅读