Go语言之sync.Map

Go语言的普通map由于不是线程安全的,所以很多时候也会使用sync包的Map来代替。sync.Map是线程安全的,但是也必须使用其提供的接口,接口不多,光看名字就知道其用途。先看下其中基本的结构

1. 数据结构

type Map struct {
	mu Mutex          // 内部互斥锁,增,改数据会用到,删除可能会用到

	read atomic.Value // readOnly 包含部分数据,但是多线程读安全

	dirty map[interface{}]*entry // 存放新增数据

	misses int        // 多次从read中读取失败会增加此计数,大于等于dirty后会出发missLocked,即拷贝全量dirty覆盖read
}
type readOnly struct {
	m       map[interface{}]*entry  // 多线程读安全
	amended bool // 为true时,表明dirty map中新插入的key-value,未同步到readonly
}

2.接口 

  •  Load(key interface{}) (value interface{}, ok bool)
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]

    // 从read中读取失败,并且dirty有更新,则从dirty加锁再读取一次
	if !ok && read.amended {
		m.mu.Lock()
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
			e, ok = m.dirty[key]
		    // 从read中read miss后会出发missLocked,记录miss次数,超过dirty大小,就会强制更新read
			m.missLocked()
		}
		m.mu.Unlock()
	}
	if !ok {
		return nil, false
	}
    // load 原子操作
	return e.load()
}
  • Store(key, value interface{})
func (m *Map) Store(key, value interface{}) {
    // key存在于read中,即修改其值,tryStore是原子操作
	read, _ := m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok && e.tryStore(&value) {
		return
	}
    
    // 不存在read,或者原子修改失败即表明此时有其他线程同时在删除该key
    // 加锁重新从read读取,不信邪?
	m.mu.Lock()
	read, _ = m.read.Load().(readOnly)
	if e, ok := read.m[key]; ok {
        // update
		if e.unexpungeLocked() {
            // 如果已被清除,则直接修改dirty
			m.dirty[key] = e
		}
        // 再修改read
		e.storeLocked(&value)
	} else if e, ok := m.dirty[key]; ok {
        // 如果该key不在read而在dirty中,则直接修改dirty
        // update
		e.storeLocked(&value)
	} else { 
        // insert
        // amended为false,表示read是全量数据,还未进行任何修改
        // amended为true,表示dirty有新增key-value
		if !read.amended {
		    // dirtylock
            // 1、 dirty map的初始化
            // 2、并且将read中的值更新到dirty
            // 3、将删除的key置为expunged
			m.dirtyLocked()
            // 单纯修改amended变量,
			m.read.Store(readOnly{m: read.m, amended: true})
		}
        // dirty插入新值
		m.dirty[key] = newEntry(value)
	}
	m.mu.Unlock()
}
  • Delete(key interface{})

func (m *Map) Delete(key interface{}) {
	read, _ := m.read.Load().(readOnly)
	e, ok := read.m[key]
    // read中不存在,amended为true表示有新值 那么就去dirty看看
	if !ok && read.amended {
		m.mu.Lock()
        // 不信邪 再读一次
		read, _ = m.read.Load().(readOnly)
		e, ok = read.m[key]
		if !ok && read.amended {
            // read中还是没有,就直接删除dirty map吧,有没有已经不重要了,反正加锁了
			delete(m.dirty, key)
		}
		m.mu.Unlock()
	}
	if ok {
        // 并没有真正删除,只是把key对应value置为nil
		e.delete()
	}
}

3. 想到几个问题

  • 为什么多线程是安全

  1. 内部分为两个map,read和dirty,读写分离,读取时原子操作;即使在读取时有删除操作也不影响;
  2. 更改和插入数据时,在内部会加锁;
  • 删除key分为三步

  1. 如果该key存在read,则直接置为nil,不管dirty,否则直接从dirty删除(删除key和value);
  2. 将来触发dirtyLocked时(即插入新key-value),即从read更新dirty时,将value为nil的键对应的值修改expunged;
  3. 将来触发missLocked(即多次从read读取失败,必须去dirty查找的次数)时,将dirty直接拷贝覆盖read,这时才会真正释放删除的key-value;

      步骤1 释放value,步骤3释放key,如果key是一些较大或重要的内存的引用,那么就可能要很久才能释放key对应的内存

  • 修改可能不需要锁

  1. 如果该key在read中,则会直接尝试原子操作修改read中key-value;
  2. 否则就要加锁进行判断,去dirty中修改了;
  • 插入新值肯定要加锁了

  • 用什么姿势操作最合理

  1. 内部是读写分离,所以只读不写不改那就最好了,内部都是原子操作,贼快;
  2. 如果用sync.Map频繁读取一些不存在的键,但是修改比较少的话也贼快
  3. 内部存储的是value对应的指针,删除的时候,将read中对应value置为nil,但是这并妨碍我们使用value来存nil

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值