golang map是非goroutine安全,如果多个goroutine使用map需要加锁。但在高并发场景下,锁的争用会造成系统性能的下降。为了解决这种问题,go 1.9之后提供了线程安全:sync.map。sync.map引入了两个数据结构read,dirty来存储,他们的底层都是用map来实现。
Golang选择了 CAS 这种不用加锁的方案来更新值,实现并发安全的map。
下面例举了三个结构体
map: sync.map的结构体,包含read和dirty,read和dirty存储了map中真实存储的key/value值。misses表示从dirty读取值的次数
readonly:Map.read值的结构体类型,m存储key/value真实值。readOnly.amended表示read是否创建了dirty副本
entry: read和dirty中存储value的指针
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int
}
type readOnly struct {
m map[interface{}]*entry
amended bool
}
type entry struct {
p unsafe.Pointer // *interface{}
}
read是readOnly结构体,真实数据存储在readOnly.m。
read和dirty的关联:
1: read相当于cache,读取数据时,先从read读取,没有取到,从dirty读取,Map.misses++。
当Map.misses达到dirty长度时,把dirty里面的数据全部copy到read中,并且dirty置为nil。
2: read和dirty map存储的元素值是放在entry结构体中。read和dirty中相同key值指向同一个entry地址,所以当对read的key对应的value值进行修改,dirty中的值也会相应的被修改。
entry.p 的状态:
1: nil表示entry被删除,并且Map.dirty = nil
2: expunged(初始化的entry.p)表示entry被删除,但是Map.dirty != nil
3: 其他情况表示值存在
snyc.Map主要提供了插入,查找,删除操作,接下来会主要会讲这三个方法的实现
插入流程
插入key, value
1: 先从read中获取key,如果存在,并且这个key没有被删除,则直接更新read[key] = entry{p: value}返回
2: 否则,key存在但是被删除了,在dirty中插入这个key,value值。dirty[key] = entry{p: value}返回
3: 如果dirty为nil,则将read map的key,entry 添加到新创建的dirty map中;不为nil,则跳过第3步
4: 将key, value插入dirty map中。dirty[key] = entry{p: value}
插入总结:
新加入的key值,会插入dirty中
以前存在,但是删除过的key,会插入dirty中
以前存在,但是没被删除的key,read会更新这个key对应的value值,
所以 dirty不为nil的时候,会全量保存key值。
查找流程
查找key
1: 从read中读取到,直接返回
2: 没有读取到,并且dirty不为nil,对map加锁,然后再读取一遍read map中内容(主要防止在加锁的过程中,有可能dirty map全部copy到read map,dirty置为nil),如果read存在key,直接返回
3: read不存在,从dirty中读取key对应的value值返回,并且map.misses++。当map.misses达到一定dirty长度,将dirty map全部copy到read map,dirty置为nil。
查找总结:
读read没读到,会从dirty中读取,并且misses 次数+1,当次数达到一定dirty长度,会把dirty map全部copy到read map,dirty置为nil。
删除流程
1: 从read中去读key,如果存在,直接将从read[key]获取到entry.p 置为nil
2: 否则,从dirty中删除这个key值
所以可以得出,read删除是直接把entry的p置为nil,key保留。从dirty中删除是直接删除这个key