// Insert the newly created *memorySource.
p.sources[name] = source
}
// if present is true, then another goroutine has already inserted
// the element we want, and source is set to what we want.
} // added this line
// Note that if the source was present, we avoid the lock completely!
该实现可以达到 5,500,000 插入/秒,比第一个版本快 3.93 倍。有 4 个协程在跑测试,结果数值和预期是基本吻合的。
这个实现是 ok 的,因为我们没有删除、修改操作。在 CPU 缓存中的指针地址我们可以安全使用,不过要注意的是我们还是需要加锁。如果不加,某协程在创建插入 source 时另一个协程可能已经正在插入,它们会处于竞争状态。这个版本中我们只是在很少情况下加锁,所以性能提高了很多。
John Potocny 建议移除 defer,因为会延误解锁时间(要在整个函数返回时才解锁),下面给出一个“终极”版本:
var source *memorySource
var present bool
if source, present = p.sources[name]; !present {
// The source wasn't found, so we'll create it.
p.lock.Lock() // lock the mutex
if source, present = p.sources[name]; !present {
source = &memorySource{
name: name,
metrics: map[string]*memoryMetric{},
}
// Insert the newly created *memorySource.
p.sources[name] = source
}
p.lock.Unlock() // unlock the mutex
}
// Note that if the source was present, we avoid the lock completely!
9,800,000 插入/秒!改了 4 行提升到 7 倍啊!!有木有!!!! 更新:(译注:原作者循序渐进非常赞)
上面实现正确么?No!通过 Go Data Race Detector 我们可以很轻松发现竟态条件,我们不能保证 map 在同时读写时的完整性。
下面给出不存在竟态条件、线程安全,应该算是“正确”的版本了。使用了 RWMutex,读操作不会被锁,写操作保持同步。
var source *memorySource
var present bool
p.lock.RLock()
if source, present = p.sources[name]; !present {
// The source wasn't found, so we'll create it.
p.lock.RUnlock()
p.lock.Lock()
if source, present = p.sources[name]; !present {
source = &memorySource{
name: name,
metrics: map[string]*memoryMetric{},
}
// Insert the newly created *memorySource.
p.sources[name] = source
}
p.lock.Unlock()
} else {
p.lock.RUnlock()
}
经测试,该版本性能为其之前版本的 93.8%,在保证正确性的前提先能到达这样已经很不错了。也许我们可以认为它们之间根本没有可比性,因为之前的版本是错的。
参考资料:
Golang的锁和线程安全的Map: http://www.java123.net/404333.html
[Golang]Map的一个绝妙特性: http://studygolang.com/articles/2494
如何证明 go map 不是并发安全的: https://segmentfault.com/q/1010000006259232
go语言映射map的线程协程安全问题: http://blog.csdn.net/htyu_0203_39/article/details/50979992
优化 Go 中的 map 并发存取: http://studygolang.com/articles/2775
扩展:
优化 Go 中的 map 并发存取 | Go语言中文网 | Golang中文社区 | Golang中国Data Race Detector - The Go Programming Languagegolang map 安全_百度搜索[Golang]Map的一个绝妙特性 | Go语言中文网 | Golang中文社区 | Golang中国Go语言map是怎么比较key是否存在的? - Go 语言 - 知乎Map线程安全几种实现方法 - 雲端之風 - 博客园golang 中map并发读写操作 | Go语言中文网 | Golang中文社区 | Golang中国go语言映射map的线程协程安全问题 - - 博客频道 - CSDN.NETgolang - 如何证明 go map 不是并发安全的 - SegmentFaultGo Commons Pool发布以及Golang多线程编程问题总结 - OPEN 开发经验库golang sync.RWMutex | Go语言中文网 | Golang中文社区 | Golang中国[Golang]互斥到底该谁做?channel还是Mutex - Sunface - 博客频道 - CSDN.NETgolang中sync.RWMutex和sync.Mutex区别 | Go语言中文网 | Golang中文社区 | Golang中国GO语言并发编程之互斥锁、读写锁详解_Golang_脚本之家go - How to use RWMutex in Golang? - Stack OverflowGolang同步:锁的使用案例详解 - 综合编程类其他综合 - 红黑联盟golang读写锁RWMutex_Go语言_第七城市