1

一、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
    }
}

谢谢您的观看,欢迎关注我的公众号。

image.png


海生
104 声望32 粉丝

与黑夜里,追求那一抹萤火。