sync.Map

sync.Map 是1.9版本才加入的,是线程并发安全的map,类型可以看做map[interface{}]interface{}。

结构

Map

type Map struct {
    mu Mutex    //互斥锁,用于锁定dirty map
    read atomic.Value //优先读map,支持原子操作,源码注释写了readOnly不是说read是只读,而是它的结构体是readOnly
    dirty map[interface{}]*entry // dirty是一个当前最新的map,允许读写
    misses int // 主要记录read读取不到数据加锁读取read map以及dirty map的次数,当misses等于dirty的长度时,会将dirty复制到read
}

readOnly

type readOnly struct {
    m       map[interface{}]*entry
    amended bool // 如果数据在dirty中但没有在read中,该值为true,作为修改标识
}

entry

type entry struct {
    // nil: 表示为被删除,调用Delete()可以将read map中的元素置为nil
    // expunged: 也是表示被删除,但是该键只在read而没有在dirty中,这种情况出现在将read复制到dirty中,即复制的过程会先将nil标记为expunged,然后不将其复制到dirty
    // 其他: 表示存着真正的数据
    p unsafe.Pointer // *interface{}
}

Load 查找

根据key来查找 value, 函数为 Load(),源码如下:

func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    // 首先从只读ready的map中查找,这时不需要加锁
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    
    // 如果没有找到,并且read.amended为true,说明dirty中有新数据,从dirty中查找,开始加锁了
    if !ok && read.amended {
        m.mu.Lock() // 加锁
        
       // 又在 readonly 中检查一遍,因为在加锁的时候 dirty 的数据可能已经迁移到了read中
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        
        // read 还没有找到,并且dirty中有数据
        if !ok && read.amended {
            e, ok = m.dirty[key] //从 dirty 中查找数据
            
            // 不管m.dirty中存不存在,都将misses + 1
            // missLocked() 中满足条件后就会把m.dirty中数据迁移到m.read中
            m.missLocked()
        }
        m.mu.Unlock()
    }
    if !ok {
        return nil, false
    }
    return e.load()
}

从函数可以看出,如果查询的键值正好在m.read中,不需要加锁,直接返回结果,优化了性能。

即使不在read中,经过几次miss后, m.dirty中的数据也会迁移到m.read中,这时又可以从read中查找。所以对于更新/增加较少,加载存在的key很多的case,性能基本和无锁的map类似。

missLockerd 迁移数据

func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {//misses次数小于 dirty的长度,就不迁移数据,直接返回
        return
    }
    m.read.Store(readOnly{m: m.dirty}) //开始迁移数据
    m.dirty = nil   //迁移完dirty就赋值为nil
    m.misses = 0  //迁移完 misses归0
}

Store 存储

func (m *Map) Store(key, value interface{}) {
   // 直接在read中查找值,找到了,就尝试 tryStore() 更新值
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }
    
    // m.read 中不存在
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok {
        if e.unexpungeLocked() { // 未被标记成删除,前面讲到entry数据结构时,里面的p值有3种。1.nil 2.expunged,这个值含义有点复杂,可以看看前面entry数据结构 3.正常值
            
            m.dirty[key] = e // 加入到dirty里
        }
        e.storeLocked(&value) // 更新值
    } else if e, ok := m.dirty[key]; ok { // 存在于 dirty 中,直接更新
        e.storeLocked(&value)
    } else { // 新的值
        if !read.amended { // m.dirty 中没有新数据,增加到 m.dirty 中
            m.dirtyLocked() // 从 m.read中复制未删除的数据
            m.read.Store(readOnly{m: read.m, amended: true}) 
        }
        m.dirty[key] = newEntry(value) //将这个entry加入到m.dirty中
    }
    m.mu.Unlock()
}

操作都是先从m.read开始,不满足条件再加锁,然后操作m.dirty。

Delete 删除

func (m *Map) Delete(key interface{}) {
    // 从 m.read 中开始查找
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    
    if !ok && read.amended { // m.read中没有找到,并且可能存在于m.dirty中,加锁查找
        m.mu.Lock() // 加锁
        read, _ = m.read.Load().(readOnly) // 再在m.read中查找一次
        e, ok = read.m[key]
        if !ok && read.amended { //m.read中又没找到,amended标志位true,说明在m.dirty中
            delete(m.dirty, key) // 删除
        }
        m.mu.Unlock()
    }
    if ok { // 在 m.ready 中就直接删除
        e.delete()
    }
}

使用

package main

import (
	"fmt"
	"sync"
)

func main() {
	var sm sync.Map

	sm.Store("www", "2343125")
	sm.Store("test", 111)

	var v interface{}

	v, _ = sm.Load("www")
	fmt.Printf("%T", v)
	sm.Delete("test")
	v, _ = sm.Load("test")
	fmt.Printf("%T", v)
}

// string<nil>

参考


sync.Map
https://blog.puresai.com/2021/02/06/syncMap/
作者
puresai
许可协议