sync.Map低层工作原理详解

sync.Map低层工作原理详解


目录

  1. 为什么需要sync.Map?适合什么场景?
  2. sync.Map内部实现基本原理及结构体分析
  3. sync.Map低层工作原理

1. 为什么需要sync.Map?适合什么场景?

  1. map在并发读写的情况下是不安全,会触发panic。Go 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,目的是为了改善多核高读取低写入时候的性能,即适合写特别少而并发读特别多的场景。

2. sync.Map内部实现基本原理及结构体分析

  1. sync.Map通过内部存储的两个map来实现了优化:分别是键(key) 固定的read表和包含所有键值对的dirty。所有对read上已有的键值对的增删改查操作都是无锁实现,考虑到的是“写特别少几乎固定”的场景,因为基本用不上锁,从而大大提高了性能。
  2. 无论是read还是dirty,存储的都是值的地址,而且是共享地址的。也就是说所有对read的无锁增删改查都会同步反馈在dirty上。所以对read增删改查没有经过dirty而dirty却始终反映最新值。
1. sync.Map结构体
  1. sync.Map结构体
type Map struct {
	mu Mutex // 互斥锁,用以处理并发读写的问题
	read atomic.Value // 包含可以并发访问的map部分,可以直接Load,但Store必须先持有mu
	dirty map[interface{}]*entry //包含需要持有mu才能访问的部分,为确保dirty快速升级为read,dirty中还包含read中未删除的部分
	misses int //记录从dirty表查询的次数。misses大到超过dirty拷贝的消耗时,会直接将dirty提升至read,后续的store操作会生成新的dirty。 
}
  1. read的类型为readOnly,其结构如下:
type readOnly struct {
	m       map[interface{}]*entry
	amended bool // true if the dirty map contains some key not in m. dirty表有没有新数据的标记 amended
}

type entry struct {
	p unsafe.Pointer // *interface{}
}
  1. readOnly包含一个map m及一个dirty表有没有新数据的标记 amended(false表示dirty没有新数据),最终的数据存储在entry中,entry存储的是数据的指针。

3. sync.Map低层工作原理

1. Store处理过程
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
	read, _ := m.read.Load().(readOnly)				// 获取read表
	if e, ok := read.m[key]; ok && e.tryStore(&value) { // 如果read表中能找到对应key,尝试插入read表对应的key中
		return
	}

	m.mu.Lock()
	read, _ = m.read.Load().(readOnly)	// 对m加锁,重新获取read表,避免虚假报告
	if e, ok := read.m[key]; ok {	// 在read表能查询到,但标记为expunged,则dirty表现创建entry,再存入数据到dirty表
		if e.unexpungeLocked() {
			// The entry was previously expunged, which implies that there is a
			// non-nil dirty map and this entry is not in it.
			m.dirty[key] = e
		}
		e.storeLocked(&value)
	} else if e, ok := m.dirty[key]; ok {	// read表不存在对应key,则判断dirty表是否存在,存在则进行store操作
		e.storeLocked(&value)
	} else {
		if !read.amended {	// 如果
			// We're adding the first new key to the dirty map.
			// Make sure it is allocated and mark the read-only map as incomplete.
			m.dirtyLocked()
			m.read.Store(readOnly{m: read.m, amended: true})
		}
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}

func (e *entry) tryStore(i *interface{}) bool {
	for {
		p := atomic.LoadPointer(&e.p)
		if p == expunged {
			return false
		}
		if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
			return true
		}
	}
}
  1. 比如:key为“hello”,value为“world”,则read和dirty分别存储的内容如下图所示
    4.
  2. 调用Store(“hello”,“not world”)。如果read中有对应的键,那么不会上锁访问dirty,而是直接无锁替换,变成下图所示。dirty始终反映着最新值。
    在这里插入图片描述
  3. 一直访问read上的键值对,由于不用加锁,性能可以大大提高。但是read表是不会更新的,如果后面新增了键值对写入dirty,然后频繁访问新的键值对,这时就需要加锁访问dirty表。
  4. 如果Load方法调用需要加锁的次数达到一定次数,表示read表的数据太少了,那么sync.Map会将dirty表设置为read表,dirty表为nil。
    用上图的例子,比方说一直频繁调用Load(key4),那么sync.Map就会更换read表
    在这里插入图片描述
  5. 当要求新增read表中没有的键值对的时候,sync.Map会重建新的dirty表。需要复制read表中的数据,复制的是地址,所以dirty表和read表对同一个键仍然共享地址。
    在这里插入图片描述
  6. 由于在没有新增键值对以前,所有的增删改查都在read表中实现。假如说在新增键值对之前我们删除掉了key5,从而使其地址为nil,当发现read表中有指向nil的,则将其修改为一个特殊标记expunged,然后跳过该键值,如下图所示。
    在这里插入图片描述
  7. 如果key5再也没有出现,这样不仅dirty表节省了空间,而且随着read表转换成dirty表,read表也少了消耗。但是之后出现了Store(key5,“another value”),sync.Map会检查是否有着"expunged"标记,如果有的话,会加锁然后让dirty表先创建对应的键值对(如下图所示),然后更新值
    在这里插入图片描述
    在这里插入图片描述
2. Load获取过程
  1. load获取过程,见注释
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
	if !ok && read.amended {	// 如果read表没有找到则判断amended为true,表示dirty有比read表多新元素
		m.mu.Lock()
		// Avoid reporting a spurious miss if m.dirty got promoted while we were
		// blocked on m.mu. (If further loads of the same key will not miss, it's
		// not worth copying the dirty map for this key.)	
		// 锁住m,再去read表查询是否有想要的数据,再查一遍是为了防止dirty转为read表,避免虚假报告
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {	// 再查询一次还是没有查询到,那么从dirty表中查找
			e, ok = m.dirty[key]
			// Regardless of whether the entry was present, record a miss: this key
			// will take the slow path until the dirty map is promoted to the read
			// map.
			m.missLocked()	// 判断messes次数是否小于len(dirty),大于时,将dirty表转成read表,dirty表置为nil
		}
		m.mu.Unlock()
	}
	if !ok {	// 如果read表和dirty都没有查询到,返回nil
		return nil, false
	}
	return e.load()	// 找到了,返回数据
}

func (m *Map) missLocked() {
	m.misses++
	if m.misses < len(m.dirty) {	// 如果misses小于len(dirty),继续从dirty查询,大于时将dirty转为read表且dirty置为nil
		return
	}
	m.read.Store(readOnly{m: m.dirty})	// 将dirty转为read表且dirty
	m.dirty = nil						// dirty表置为nil
	m.misses = 0
}

func (e *entry) load() (value interface{}, ok bool) {
	p := atomic.LoadPointer(&e.p)
	if p == nil || p == expunged {
		return nil, false
	}
	return *(*interface{})(p), true
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值