一、sync.map介绍
Go语言提倡“不要通过共享内存来通信,而应该通过通信来共享内存”。
Go语言在提供CSP并发模型原语的同时,还通过标准库的sync包提供了针对传统基于共享内存并发模型的基本同步原语,包括互斥锁(sync.Mutex)、读写锁(sync.RWMutex)、条件变量(sync.Cond)哈希表sync.map等。
sync.map是基于传统基于共享内存并发模型的基本同步原语。
二、sync.Map提供的字段和方法
我们在命令行中输入:go doc sync.map
返回如下:
type Map struct {
mu Mutex
read atomic.Value // readOnly
dirty map[interface{}]*entry
misses int
}
func (m *Map) Delete(key interface{})
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
func (m *Map) Range(f func(key, value interface{}) bool)
func (m *Map) Store(key, value interface{})
1.Store 存储一个key的值。
func TestStore(t *testing.T) {
m := sync.Map{}
m.Store("k1", "v1")
m.Store("k2", "v2")
m.Store("k3", "v3")
m.Store("k4", "v4")
m.Store("k5", "v5")
t.Log(m)
}
2.Load 获取一个key的值
func TestLoad(t *testing.T) {
m := sync.Map{}
m.Store("k1", "v1")
v1, ok1 := m.Load("k1")
t.Log(v1, ok1) //v1 true 有值的时候,会返回值以及true
v2, ok2 := m.Load("k2")
t.Log(v2, ok2) //<nil> false 无值的时候,会返回nil以及false
}
3.LoadOrStore 取出一个值,如果没有的话,就store
func TestLoadOrStore(t *testing.T) {
m := sync.Map{}
m.Store("k1", "v1")
v1, ok1 := m.LoadOrStore("k1", "v1new")
t.Log(v1, ok1) //v1 true 已经有的值,没有存储新值,只是获取老的值
v2, ok2 := m.LoadOrStore("k2", "v2")
t.Log(v2, ok2) //v2 false
}
4.Range 遍历map所有的值
func TestRange(t *testing.T) {
m := sync.Map{}
m.Store("k1", "v1")
m.Store("k2", "v2")
m.Store("k3", "v3")
m.Range(func(key, value interface{}) bool {
t.Log(key, value)
return true
})
//k3 v3
//k1 v1
//k2 v2
}
5.Delete 删除key的值
func TestDelete(t *testing.T) {
m := sync.Map{}
m.Store("k1", "v1")
m.Delete("k1") //删除存储的值
m.Delete("k2") //删除未知的,不在的值
t.Log(m.Load("k1"))
t.Log(m.Load("k2"))
t.Log(m.Load("k3"))
}
三、自制 sync.Map
1.简单共享内存的map1
sync.map是基于传统基于共享内存并发模型的基本同步原语。
核心主要是 1.共享map内存变量 2.锁
map1实现代码
package _map
import "sync"
type Map1 struct {
m map[interface{}]interface{}
mu sync.Mutex
}
func (m *Map1) Store(key, value interface{}) {
m.mu.Lock()
defer m.mu.Unlock()
if len(m.m) == 0 {
m.m = make(map[interface{}]interface{}, 0)
}
m.m[key] = value
}
func (m *Map1) Load(key interface{}) (value interface{}, ok bool) {
m.mu.Lock()
defer m.mu.Unlock()
value, ok = m.m[key]
return value, ok
}
func (m *Map1) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
m.mu.Lock()
defer m.mu.Unlock()
actual, loaded = m.m[key]
if !loaded {
m.Store(key, value)
}
return actual, loaded
}
func (m *Map1) Delete(key interface{}) {
m.mu.Lock()
defer m.mu.Unlock()
_, ok := m.m[key]
if ok {
delete(m.m, key)
}
}
map1的锁操作问题以及优化
问题1:每次读或者写,删除等操作都有锁,能否读写分离,提升速度?
问题2:每次写的话,有可能会map扩容,此时扩容时候,读取不可用,怎么办?
方案:用2个map,一个map主读,一个map写,这样读写分离的话,且读的map不加锁。
这样的好处就是读map,不加锁,且不扩容。会非常友好大量读的场景。
2.读写分离共享内存的map2-全量方案
map2代码实现
实现方案:
用两个map,一个存储全量的数据dirty(所有的写,在这个map)
一个只读read,每过一阵时间从dirty同步一次,优先读read,没有的话再去读dirty
package _map
import "sync"
type Map2 struct {
mu sync.Mutex
read map[interface{}]interface{} //只读
dirty map[interface{}]interface{} //写入,包含全量数据
misses int //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
}
func (m *Map2) Load(key interface{}) (value interface{}, ok bool) {
//1先从read里面读取 (无锁)
value, ok = m.read[key]
if ok {
return value, ok
}
//2再从 dirty全量数据读取(有锁)
m.mu.Lock()
defer m.mu.Unlock()
value, ok = m.dirty[key]
m.misses++ //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
return value, ok
}
func (m *Map2) Store(key, value interface{}) {
//1 写入
m.mu.Lock()
defer m.mu.Unlock()
if len(m.dirty) == 0 {
m.dirty = make(map[interface{}]interface{}, len(m.read))
}
m.dirty[key] = value
//2 重置read的值
if m.misses > len(m.dirty) {
newMap := make(map[interface{}]interface{}, len(m.dirty))
for k, v := range m.dirty {
newMap[k] = v
}
m.read = newMap
m.misses = 0
}
}
map2的问题
1、关于同步dirty到read里,代码能否写成如下,为什么?
//2 重置read的值
if m.misses > len(m.dirty) {
m.read = m.dirty
m.misses = 0
}
答案是不能,因为如果这样写,那么m.read = m.dirty
因为dirty是map,而用=赋值,在map中是赋值的引用地址。
所以m.read和d.dirty 还是引用的同一个map,没法做到两个不同的map,起到读写分离的作用。
2、如果一个key在read里面没有,则永远会访问dirty,读两次
方案:当dirty里面有不存在read里面的值,再去访问dirty
把read改造一下
type readOnly struct {
m map[interface{}]interface{}
amended bool //当dirty里面有不存在m里面的值是为true
}
则新的map2代码如下
package _map
import "sync"
type Map2 struct {
mu sync.Mutex
read readOnly //只读
dirty map[interface{}]interface{} //写入,包含全量数据
misses int //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
}
type readOnly struct {
m map[interface{}]interface{}
amended bool //当dirty里面有不存在m里面的值是为true
}
func (m *Map2) Load(key interface{}) (value interface{}, ok bool) {
//1先从read里面读取 (无锁)
value, ok = m.read.m[key]
if ok {
return value, ok
}
//2再从 dirty全量数据读取(有锁)
if m.read.amended {
m.mu.Lock()
defer m.mu.Unlock()
value, ok = m.dirty[key]
m.misses++ //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
}
return value, ok
}
func (m *Map2) Store(key, value interface{}) {
//1 写入
m.mu.Lock()
defer m.mu.Unlock()
if len(m.dirty) == 0 {
m.dirty = make(map[interface{}]interface{}, len(m.read.m))
}
m.dirty[key] = value
if m.read.amended == false {
m.read.amended = true
}
//2 重置read的值
if m.misses > len(m.dirty) {
newMap := make(map[interface{}]interface{}, len(m.dirty))
for k, v := range m.dirty {
newMap[k] = v
}
m.read.m = newMap
m.read.amended = false
m.misses = 0
}
}
3、如果一个key存在read里面,而read是只读,如果此时key更新了,只更新了dirty,那么两边值不同步怎么办?
原因:因为read和dirty存储的是两个不同的map地址,修改了dirty里面的地址,只影响dirty里面的值,read还是原来的值,会导致dirty和read里面的值不同步。
方案:read和dirty里面存储的值,用一个指针地址来代替,这样会同步更新。
原来是 map[interface{}]interface{} 替换成成 map[interface{}]*entry
//值元素
type entry struct {
P interface{}
}
这样,read和dirty里面存储都是同一个entry地址引用,当修改dirty的时候,read里面的值也会被修改
package _map
import "sync"
type Map2 struct {
mu sync.Mutex
read readOnly //只读
dirty map[interface{}]*entry //写入,包含全量数据
misses int //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
}
type readOnly struct {
m map[interface{}]*entry
amended bool //当dirty里面有不存在m里面的值是为true
}
type entry struct {
P interface{}
}
func (m *Map2) Load(key interface{}) (value interface{}, ok bool) {
//1先从read里面读取 (无锁)
entryV, ok := m.read.m[key]
if ok && entryV.P != nil {
return entryV.P, true
}
//2再从 dirty全量数据读取(有锁)
if m.read.amended {
m.mu.Lock()
defer m.mu.Unlock()
entryV, ok = m.dirty[key]
m.misses++ //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
if ok && entryV.P != nil {
return entryV.P, true
}
}
return nil, false
}
func (m *Map2) Store(key, value interface{}) {
//1 写入
m.mu.Lock()
defer m.mu.Unlock()
if len(m.dirty) == 0 {
m.dirty = make(map[interface{}]*entry, len(m.read.m))
}
entryV, ok := m.dirty[key]
if ok {
entryV.P = value
} else {
m.dirty[key] = &entry{P: value}
}
if m.read.amended == false {
m.read.amended = true
}
//2 重置read的值
if m.misses > len(m.dirty) {
newMap := make(map[interface{}]*entry, len(m.dirty))
for k, v := range m.dirty {
newMap[k] = v
}
m.read.m = newMap
m.read.amended = false
m.misses = 0
}
}
4、删除一个key是物理删除还是软删除?
简单说,物理删除就是 delete(map, key),软删除就是 map[key] = nil
物理删除 | 软删除 | |
---|---|---|
速度 | 慢 | 快 |
内存 | 小 | 大 |
长期来看,肯定是物理删除好,因为如果不物理删除,那么多余的key一直存在,那么会内存会越来越多,无止境。
在读写分离的场景下,加快删除速度,如何实践?
方案:1.先在dirty把key的值变成nil(因为read和dirty是同一个地址引用,这样read里面也变成nil了),然后再dirty上执行物理删除.
2.然后在dirty同步到read的,此时的read就没有多余的key了
代码如下:
func (m *Map2) Delete(key interface{}) {
if len(m.dirty) == 0 {
return
}
m.mu.Lock()
defer m.mu.Unlock()
entryV, ok := m.dirty[key]
if ok {
entryV.P = nil
delete(m.dirty, key)
}
}
5、全量方案虽然解决了,读的效率问题,但带来的是内存占用扩大了一倍,要存储两个map内存,
能否再优化下,还是只占用一个map内存,又能解决读的效率问题?
答案:读增分离,还是两个map。
一个只读read map
一个新增的map,dirty(只保留增量)
修改的情况,是如果在read里,就修改read,如果在dirty里,就修改dirty。
这个方案比纯读的情况,在修改的时候,有可能会修改到read,但修改不会引起map的扩容,
效率依旧是O(1),和查询效率一样都是O(1),整个read的效率都是O(1)
好处是减少了一半的内存使用量。而且dirty里的新增引起的map扩容也比全量的要小,
因为dirty只存了新增的数据,因为会定时的把新增同步到read里,
所以dirty里面的数据量非常小,
新增的效率会非常高,虽然还是O(n),但这个n非常小。
3.读增分离共享内存的map3-增量方案
增量方案和全量方案主要的区别在于
1.dirty的数据问题
全量方案,当dirty同步到read后,还保留原来的数据。
增量方案,当dirty同步到read户,dirty情况为nil
2.read的修改问题
全量方案,修改在dirty上
增量方案,修改的key在哪,就在哪个map修改
3.删除的问题
全量方案:物理删除在dirty,read变成nil,然后同步的时候,read=dirty
增量方案:dirty物理删除,read变成nil,然后同步的时候,把read写入到dirty(此时nil的不增加),然后再把dirty写入到read,此时的read也没有扩容,read还是修改操作
package map3
import "sync"
type Map3 struct {
mu sync.Mutex
read readOnly //只读
dirty map[interface{}]*entry //写入,包含增量数据
misses int //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
}
type readOnly struct {
m map[interface{}]*entry
amended bool //当dirty里面有不存在m里面的值是为true
}
type entry struct {
P interface{}
}
func (m *Map3) Load(key interface{}) (value interface{}, ok bool) {
//1先从read里面读取 (无锁)
entryV, ok := m.read.m[key]
if ok && entryV.P != nil {
return entryV.P, true
}
//2再从 dirty增量数据读取(有锁)
if m.read.amended {
m.mu.Lock()
defer m.mu.Unlock()
entryV, ok = m.dirty[key]
m.misses++ //记录穿透到dirty读取的次数,当超过一定的数据,就把dirty的数据 覆盖 read
if ok && entryV.P != nil {
return entryV.P, true
}
}
return nil, false
}
func (m *Map3) Store(key, value interface{}) {
//1 写入
m.mu.Lock()
defer m.mu.Unlock()
if len(m.dirty) == 0 {
m.dirty = make(map[interface{}]*entry, len(m.read.m))
}
entryV, ok := m.dirty[key]
if ok {
entryV.P = value
} else {
m.dirty[key] = &entry{P: value}
}
if m.read.amended == false {
m.read.amended = true
}
//2 重置read的值
if m.misses > len(m.dirty) {
newMap := make(map[interface{}]*entry, len(m.dirty))
for k, v := range m.read.m {
if v != nil {
newMap[k] = v
}
}
for k, v := range m.dirty {
newMap[k] = v
}
m.read.m = newMap
m.dirty = nil
m.read.amended = false
m.misses = 0
}
}
func (m *Map3) Delete(key interface{}) {
if len(m.dirty) == 0 {
return
}
m.mu.Lock()
defer m.mu.Unlock()
entryV, ok := m.dirty[key]
if ok {
entryV.P = nil
delete(m.dirty, key)
}
}
问题1:当dirty同步到read后,dirty为空,此时修改,或者删除一个key,实际的情况是dirty里面没有了这个值,而如果此时修改一个值或者删除一个值,都会有问题。
修改一个值 dirty里面相当于是新增一个新的值,而原来的key在read里面
删除的话,因为dirty里面没有了这个key,会不生效。
方案:
在删除和修改之前,查询一下read
删除操作:当read存在,read为ni,没有物理删除dirty
修改操作:当read存在,dirty里面复制一下这个key,然后再操作dirty
删除代码:
func (m *Map3) Delete(key interface{}) {
if len(m.dirty) == 0 {
return
}
//1 read里的值变成nil
entryV, ok := m.read.m[key]
if ok && entryV.P != nil {
entryV.P = nil
return
}
//2 删除dirty
m.mu.Lock()
defer m.mu.Unlock()
_, ok = m.dirty[key]
if ok {
delete(m.dirty, key)
}
}
Store代码如下(新增/修改)
func (m *Map3) Store(key, value interface{}) {
//read里是否有key
entryV, ok := m.read.m[key]
m.mu.Lock()
defer m.mu.Unlock()
if len(m.dirty) == 0 {
m.dirty = make(map[interface{}]*entry, len(m.read.m))
}
if ok {
m.dirty[key] = entryV
entryV.P = value
} else {
entryDV, ok2 := m.dirty[key]
if ok2 {
entryDV.P = value
} else {
m.dirty[key] = &entry{P: value}
}
}
if m.read.amended == false {
m.read.amended = true
}
//2 重置read的值
if m.misses > len(m.dirty) {
newMap := make(map[interface{}]*entry, len(m.dirty))
for k, v := range m.read.m {
if v != nil {
newMap[k] = v
}
}
for k, v := range m.dirty {
newMap[k] = v
}
m.read.m = newMap
m.dirty = nil
m.read.amended = false
m.misses = 0
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。