返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

map

发布于 2024-10-12 19:15:54 字数 4466 浏览 0 评论 0 收藏 0

只在两种情况下,可减少锁争用,提升性能。

  • 少写多读。
  • 多个并发读写不同项。

源码剖析

快慢分离, read 以 atomic 操作, dirty 需 mutex 锁定。相比读写锁(rwmutex)写操作锁定全局,此设计有更好性能。

查找优先选择 read ,失败后再以慢路径从 dirty 找。不直接存储值,而是以 entry.p 引用,故可用原子操作修改。至于删除也不过是 entry.p = nil ,不影响字典本身。

慢操作累加 misses ,达到阈值时,以 dirty 替换 read 字典。然后按需重建 dirty 字典,并复制 read 里所有非删除项。

// sync/map.go

type Map struct {
	mu Mutex

	read   atomic.Value     // readOnly
	dirty  map[any]*entry
    
	misses int
}
type readOnly struct {
	m map[any]*entry
    
    // true if the dirty map contains some key not in m.
	amended bool 
}

type entry struct {
	p unsafe.Pointer
}

更新

优先从 read 查找,以原子操作修改 entry.p 进行值替换。或加锁,进入慢路径,尝试更新 dirty 字典。

func (m *Map) Store(key, value any) {
        
    // 尝试 read 快速更新。
    read, _ := m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}

    // 更新失败,进入慢路径。
	m.mu.Lock()
    
    // 再次检查 read 字典。
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {
        
        // 如已被标记为删除,在 dirty 更新。
		if e.unexpungeLocked() {
			m.dirty[key] = e
		}
        
        // 更新值引用(e.p = &value)。
		e.storeLocked(&value)
        
	} else if e, ok := m.dirty[key]; ok {
        
        // 更新 dirty 字典。
		e.storeLocked(&value)
        
	} else {
        
        // 在 dirty 字典插入。
        
        // 如果 !amended,表示 read 包含全部数据。
        // 只能是 dirty 不存在,比如替换 read.m 字典。
		if !read.amended {
            
            // 新建 dirty,复制 read 数据。
			m.dirtyLocked()
            
            // 设置 read.amended = true,表示有数据仅存于 dirty。
			m.read.Store(readOnly{m: read.m, amended: true})
		}
        
		m.dirty[key] = newEntry(value)
	}
    
	m.mu.Unlock()
}
func (m *Map) dirtyLocked() {
	if m.dirty != nil {
		return
	}

	read, _ := m.read.Load().(readOnly)
    
    // 新建,复制所有非删除项。
	m.dirty = make(map[any]*entry, len(read.m))
	for k, e := range read.m {
		if !e.tryExpungeLocked() {
			m.dirty[k] = e
		}
	}
}

func (e *entry) tryExpungeLocked() (isExpunged bool) {
	p := atomic.LoadPointer(&e.p)
    
    // 没有 value,删除项。
	for p == nil {
        
        // 将 nil 置为删除标记。
		if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
			return true
		}
        
		p = atomic.LoadPointer(&e.p)
	}
    
	return p == expunged
}

读取

慢操作导致 miss 计数增加。一旦超过阈值,则用 dirty 替换掉 read.m 字典。

func (m *Map) Load(key any) (value any, ok bool) {
    
    // 优先从 read 读。
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
    
    // 失败,且有 dirty 字典。
	if !ok && read.amended {
		m.mu.Lock()
        
        // 再次尝试 read 字典。
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
        
        // 失败,从 dirty 读。
		if !ok && read.amended {
			e, ok = m.dirty[key]
            
            // 累加 miss 计数。
			m.missLocked()
		}
        
		m.mu.Unlock()
	}
    
	if !ok {
		return nil, false
	}
    
	return e.load()
}
func (m *Map) missLocked() {
	m.misses++
    
    // 检查阈值。
	if m.misses < len(m.dirty) {
		return
	}
    
    // 用 dirty 替换 read.m 字典。
	m.read.Store(readOnly{m: m.dirty})
    
    // 重置状态。
	m.dirty = nil
	m.misses = 0
}

删除

删除只是设置 entry.p = nil ,新建 dirty 进行数据复制时会将其排除。

func (m *Map) Delete(key any) {
	m.LoadAndDelete(key)
}

func (m *Map) LoadAndDelete(key any) (value any, loaded bool) {
    
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
    
    // 失败,且 dirty 字典存在。
	if !ok && read.amended {
		m.mu.Lock()
        
        // 重试 read 字典。
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
        
		if !ok && read.amended {
			e, ok = m.dirty[key]
			delete(m.dirty, key)
            
            // 同样会累加 miss 计数。
			m.missLocked()
		}
        
		m.mu.Unlock()
	}
    
    // 删除(e.p = nil)。
	if ok {
		return e.delete()
	}
    
	return nil, false
}
func (e *entry) delete() (value any, ok bool) {
	for {
		p := atomic.LoadPointer(&e.p)
		if p == nil || p == expunged {
			return nil, false
		}
        
        // 设置 e.p = nil,释放 value。
		if atomic.CompareAndSwapPointer(&e.p, p, nil) {
			return *(*any)(p), true
		}
	}
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文