golang系列之-sync.Map
map不支持并发读写,但我们可以转变下思路,将value改为一个指向结构体entry的指针,结构体内部的字段我们是可以随意修改的,如下,将并发读写map改为并发读map,读写转移到entry
1 | type entry struct { |
上面的方法看起来解决了并发读写的问题,但还不够,当有新的key写入时,还是变回了原来的map并发读写。sync.Map提供了一种思路,使用两个map,read负责已有key的并发读写,dirty负责新key的读写,只有当read找不到key,才去找dirty。
现在还剩最后一个问题,read和dirty如何保证数据一致/同步,我们可以改造entry,使其指向value的指针,如此一来,read和dirty的entry可以指向同一个value,如下,这就是sync.Map的大致思路
1 | type entry struct { |
注意:尽管如此,sync.Map并不完美,以上设计导致我们无法直接计算出哈希表的元素数量,需要遍历进行统计,而且还不一定准确
当前go版本:1.23,1.24版本改为HashTrieMap实现
快速上手
1 | package main |
上述代码运行结果如下
1 | go run ./main.go |
数据结构
todo:文章图片待补充
1 | // src/sync/map.go |
在这里值得注意的是entry的p指针,有三个状态
p | 说明 |
---|---|
&value | 正常状态 |
nil | 已删除,可以当作是墓碑来理解 |
expunged | dirty替代read时,从nil改为expunged,在下一轮替换中,移除该key |
三种状态转移路线:
- &value -> nil -> expunged
- expunged -> nil -> &value
核心方法
Load
根据key获取value,具体逻辑如下
- 根据key在read中寻找entry,如果找到,返回
- 加锁,再找一遍read,如果找到,返回(double-check)
- amended为true表示dirty有新的key,在dirty找
- 更新misses计数器,如果misses==len(dirty),用dirty替换掉read
1 | func (m *Map) Load(key any) (value any, ok bool) { |
Store
存储key/value。本质就是Swap方法,但丢弃其返回值,具体看Swap
1 | func (m *Map) Store(key, value any) { |
LoadOrStore
根据key获取value,如果没有该key/value,则改为写入。该方法逻辑与Swap方法十分相似
具体逻辑如下
- 根据key在read中寻找entry,如果找到,返回
- 加锁,再找一遍read,如果找到,返回(double-check)
- 如果在dirty找到,获取/更新key,同时更新misses计数器,返回
- 都没找到说明是新key,写入dirty,返回
1 | func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool) { |
Delete
删除指定key,具体看LoadAndDelete
1 | // Delete deletes the value for a key. |
LoadAndDelete
逻辑与Load相似,具体如下
- 根据key在read中寻找entry,如果找到,将entry置为nil
- 加锁,再找一遍read,如果找到,将entry置为nil(double-check)
- amended为true表示dirty有新的key,在dirty找
- 更新misses计数器,如果misses==len(dirty),将dirty迁移到read
1 | func (m *Map) LoadAndDelete(key any) (value any, loaded bool) { |
CompareAndDelete
具体逻辑如下
- 根据key在read中寻找entry,如果找到,纪录e
- 加锁,再找一遍read,如果找到,纪录e(double-check)
- amended为true表示dirty有新的key,在dirty找
- 更新misses计数器,如果misses==len(dirty),用dirty替换掉read
- 已删除或比对失败返回false,否则将e.p置为nil,返回true
1 | func (m *Map) CompareAndDelete(key, old any) (deleted bool) { |
Swap
使用value替换key当前存储的数据。具体逻辑如下
- 根据key在read中寻找entry,如果找到,替换并返回
- 加锁,再找一遍read,如果找到,替换并返回(double-check)
- 如果在dirty找到,替换并返回
- 都没找到说明是新key,写入dirty,返回
1 | func (m *Map) Swap(key, value any) (previous any, loaded bool) { |
CompareAndSwap
具体逻辑如下
- 根据key在read中寻找entry,如果找到,替换并返回
- 如果dirty没有新key,到此为止,返回
- 加锁,再找一遍read,如果找到,替换并返回(double-check)
- 如果在dirty找到,替换并返回
1 | func (m *Map) CompareAndSwap(key, old, new any) (swapped bool) { |
Range
具体逻辑如下
- 复制read,如果dirty有新key写入,则复制dirty并用dirty替换read
- 遍历哈希表
1 | func (m *Map) Range(f func(key, value any) bool) { |
Clear
具体逻辑如下
- read为空且dirty也为空,返回
- read不为空或者dirty有新数据
- 复制read并将read置空
- 将dirty清空
1 | func (m *Map) Clear() { |