在代码中,具有多个昂贵的生成价值结构的全局映射可能会被多个并发线程修改的模式正确吗?
// 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的并发模型,第三版是否是正确的模式?
答案 0 :(得分:2)
显然,多个goroutine可以同时使用新的ip
来调用选项1,并且仅存储if
块中的最后一个。 buildComplexActivityObject
花费的时间越长,这种可能性就会大大增加,因为关键部分有更多的时间。
选项2有效,但是每次都调用buildComplexActivityObject
,而您声明的不是您想要的。
鉴于您希望不经常调用buildComplexActivityObject
,第三个选项是唯一有意义的选项。
但是sync.Map
无法保护存储的指针引用的实际activity
值。在更新activity
值时,您还需要在那里进行同步。