go sync.map实现

golang map是非goroutine安全,若是多个goroutine使用map须要加锁。但在高并发场景下,锁的争用会形成系统性能的降低。为了解决这种问题,go 1.9以后提供了线程安全:sync.map。sync.map引入了两个数据结构read,dirty来存储,他们的底层都是用map来实现。golang

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的关联:
image.png高并发

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中的值也会相应的被修改。atom

entry.p 的状态:
1: nil表示entry被删除,而且Map.dirty = nil
2: expunged(初始化的entry.p)表示entry被删除,可是Map.dirty != nil
3: 其余状况表示值存在spa

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 置为nil2: 不然,从dirty中删除这个key值因此能够得出,read删除是直接把entry的p置为nil,key保留。从dirty中删除是直接删除这个key

相关文章
相关标签/搜索