深入浅出 Go - sync.Map 源码分析
Go 的 map 在并发场景下,只读是线程安全的,读写则是线程不安全的。Go1.9 提供了并发安全的 sync.Map
,通过阅读源码我们知道 snyc.Map
通过读写分离的机制,降低了锁的粒度,以此来提高并发性能
并发不安全的 map
执行程序会报如下错误
并发安全的 sync.Map
对于并发不安全的 map,一般这种情况我们可以通过加锁的方式来实现并发安全的 hashmap,但是锁本身也会带来额外的性能开销,所以 Go1.9 开始标准库提供了并发安全的 sync.Map
,使用起来也很简单,如下
sync.Map
直接进入主题,看源码
read
只读数据readOnly
dirty
读写数据,操作dirty
需要用mu
进行加锁来保证并发安全misses
用于统计有多少次读取read
没有命中amended
用于标记read
和dirty
的数据是否一致
Load
从 Load
的源码可以看出读取数据时,首先是从 read
读,没有命中的话会到 dirty
读取数据,同时调用 missLocked()
增加 misses
可以看到当 misses
大于 len(dirty)
时则表示 read
和 dirty
的数据相差太大,sync.Map
会将 dirty
的数据赋值给 read
,而 dirty
会被置空
Store
Store
首先会直接到 read
修改数据,修改成功则直接返回,如果 key
不存在那么就表示要到 dirty
找数据,如果 dirty
存在 key
则修改,如果不存在则新增,同时还要将 read
中的 amended
标记为 true,表示 read
和 dirty
的数据已经不一致了
Range
Range
的源码没太多可以说的,有两点需要关注,一个是 Range
会保证 read
和 dirty
是数据同步的,另一个是回调函数返回 false 会导致迭代中断
Delete
Delete
采用的是延迟删除
的机制,首先会到 read
查找是否存在 key
,如果存在则执行 entry.delete
进行软删除,通过 CAS 将指针 entry.p (存放数据的指针)
置为 nil,减少锁开销提高并发性能。只有当 read
找不到 key
且 amended
为 true 才会通过 delete
进行硬删除,记住这个阶段是会加锁的
版权声明: 本文为 InfoQ 作者【哈希说】的原创文章。
原文链接:【http://xie.infoq.cn/article/ebcb070ee7fd0e273ca53b64f】。文章转载请联系作者。
评论