Go 语言 Sync 包深入解析之Map | 青训营笔记

174 阅读3分钟

Go 语言 Sync 包深入解析

1. sync.Map 原理

sync.Map 是 Go 语言提供的内置并发安全的 map,开箱即用。不过需要注意的是,sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用。

1.1 sync.Map 的内部结构

sync.Map 的内部结构如下:

type Map struct {
    mu Mutex
    read atomic.Value // readOnly
    dirty map[interface{}]*entry
    misses int
}

sync.Map 内部有一个读写锁 mu,同时有两个只读结构 read、dirty。read 是原子值,dirty 是一个 map。当我们需要修改 sync.Map 时,会将 read 中的数据转移到 dirty 中,然后在 dirty 中进行修改。当需要读取时,会优先读取 read 中的数据,如果 read 中不存在,则从 dirty 中读取。

1.2 sync.Map 的读取

sync.Map 的读取操作非常简单,直接从 read 中读取即可。但是需要注意的是,如果 read 中不存在,则需要从 dirty 中读取。如果 dirty 也不存在,则说明数据不存在。

// Load returns the value stored in the map for a key, or nil if no
// value is present.
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
    read, _ := m.read.Load().(readOnly)
    e, ok := read.m[key]
    if !ok && read.amended {
        // Slow-path: unguarded access to m.dirty.
        m.mu.Lock()
        read, _ = m.read.Load().(readOnly)
        e, ok = read.m[key]
        if !ok && read.amended {
            e, ok = m.dirty[key]
            m.mu.Unlock()
            return e.load()
        }
        m.mu.Unlock()
    }
    return e.load()
}

1.3 sync.Map 的存储

sync.Map 的存储操作稍微复杂一些,需要注意的是,存储操作需要加锁,因为存储操作需要修改 dirty 中的数据。存储操作的步骤如下:

  1. 加锁
  2. 从 read 中读取数据,如果存在,则直接修改 read 中的数据
  3. 如果 read 中不存在,则从 dirty 中读取数据,如果存在,则修改 dirty 中的数据
  4. 如果 dirty 中也不存在,则新增数据到 dirty 中
  5. 解锁
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok {
        if e.unexpungeLocked() {
            m.dirty[key] = e
        }
        e.storeLocked(&value)
    } else if e, ok := m.dirty[key]; ok {
        e.storeLocked(&value)
    } else {
        if !read.amended {
            m.dirtyLocked()
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value)
    }
    m.mu.Unlock()
}

1.4 sync.Map 的删除

sync.Map 的删除操作也比较复杂,需要注意的是,删除操作需要加锁,因为删除操作需要修改 dirty 中的数据。删除操作的步骤如下:

  1. 加锁
  2. 从 read 中读取数据,如果存在,则直接删除 read 中的数据
  3. 如果 read 中不存在,则从 dirty 中读取数据,如果存在,则删除 dirty 中的数据
  4. 解锁
// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryDelete() {
        return
    }
    m.mu.Lock()
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok {
        if e.unexpungeLocked() {
            m.dirty[key] = e
        }
        e.deleteLocked()
    } else if e, ok := m.dirty[key]; ok {
        e.deleteLocked()
    }
    m.mu.Unlock()
}

1.5 sync.Map 的遍历

sync.Map 的遍历操作也比较复杂,需要注意的是,遍历操作需要加锁,因为遍历操作需要读取 dirty 中的数据。遍历操作的步骤如下:

  1. 加锁
  2. 从 read 中读取数据,遍历 read 中的数据
  3. 从 dirty 中读取数据,遍历 dirty 中的数据
  4. 解锁

2. sync.Map 的应用场景

sync.Map 的应用场景非常广泛,比如:

  • 用于存储全局变量
  • 用于存储全局配置
  • 用于存储全局对象

3. sync.Map 的使用

sync.Map 的使用非常简单,直接声明变量即可,不需要初始化。sync.Map 的声明如下:

var m = sync.Map{}

sync.Map 的操作如下:

m.Store(key, value)    // 设置键值对
m.Load(key)            // 获取键值对
m.Delete(key)          // 删除键值对
m.Range(func(key, value interface{}) bool {
    // 遍历所有键值对
    return true
})

4. sync.Map 的注意事项

  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用。
  • sync.Map 无法在创建后再进行复制操作。
  • sync.Map 与其他结构不同,它不是一个结构体,因此我们在声明的时候只需要声明变量即可,不需要初始化。