go sync.map实现

golang map是非goroutine安全,如果多个goroutine使用map需要加锁。go 1.9之后提供了线程安全:sync.map。sync.map引入了两个数据结构来存储,他们的底层都是用map来实现。

Golang选择了 CAS 这种不用加锁的方案来更新值,实现并发安全的map。

下面会从源码说明一下他们的实现

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。

readOnly.amended表示read是否创建了dirty副本

read和dirty的关联:

1: read相当于cache,读取数据时,先从read读取,没有取到,从dirty读取,Map.misses++。

当Map.misses达到一定值,把dirty里面的数据全部copy到read中,并且dirty置为nil。

2: dirty不为nil时,会保存全量的数据;dirty为nil时,read保存全量的数据

3: 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