2025年Go语言之sync.Map

Go语言之sync.MapGo 语言的普通 map 由于不是线程安全的 所以很多时候也会使用 sync 包的 Map 来代替 sync Map 是线程安全的 但是也必须使用其提供的接口 接口不多 光看名字就知道其用途 先看下其中基本的结构 1 数据结构 type Map struct mu Mutex 内部互斥锁 增

大家好,我是讯享网,很高兴认识大家。

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

小讯
上一篇 2025-04-04 20:44
下一篇 2025-03-23 15:45

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/69763.html