欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

sync.Map低层工作原理详解

发布时间:2024/4/11 编程问答 50 豆豆
生活随笔 收集整理的这篇文章主要介绍了 sync.Map低层工作原理详解 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

sync.Map低层工作原理详解


目录

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

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

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

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

  • sync.Map通过内部存储的两个map来实现了优化:分别是键(key) 固定的read表和包含所有键值对的dirty。所有对read上已有的键值对的增删改查操作都是无锁实现,考虑到的是“写特别少几乎固定”的场景,因为基本用不上锁,从而大大提高了性能。
  • 无论是read还是dirty,存储的都是值的地址,而且是共享地址的。也就是说所有对read的无锁增删改查都会同步反馈在dirty上。所以对read增删改查没有经过dirty而dirty却始终反映最新值。
  • 1. sync.Map结构体

  • sync.Map结构体
  • type Map struct {mu Mutex // 互斥锁,用以处理并发读写的问题read atomic.Value // 包含可以并发访问的map部分,可以直接Load,但Store必须先持有mudirty map[interface{}]*entry //包含需要持有mu才能访问的部分,为确保dirty快速升级为read,dirty中还包含read中未删除的部分misses int //记录从dirty表查询的次数。misses大到超过dirty拷贝的消耗时,会直接将dirty提升至read,后续的store操作会生成新的dirty。 }
  • read的类型为readOnly,其结构如下:
  • type readOnly struct {m map[interface{}]*entryamended bool // true if the dirty map contains some key not in m. dirty表有没有新数据的标记 amended }type entry struct {p unsafe.Pointer // *interface{} }
  • 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}} }
  • 比如:key为“hello”,value为“world”,则read和dirty分别存储的内容如下图所示
  • 调用Store(“hello”,“not world”)。如果read中有对应的键,那么不会上锁访问dirty,而是直接无锁替换,变成下图所示。dirty始终反映着最新值。
  • 一直访问read上的键值对,由于不用加锁,性能可以大大提高。但是read表是不会更新的,如果后面新增了键值对写入dirty,然后频繁访问新的键值对,这时就需要加锁访问dirty表。
  • 如果Load方法调用需要加锁的次数达到一定次数,表示read表的数据太少了,那么sync.Map会将dirty表设置为read表,dirty表为nil。
    用上图的例子,比方说一直频繁调用Load(key4),那么sync.Map就会更换read表
  • 当要求新增read表中没有的键值对的时候,sync.Map会重建新的dirty表。需要复制read表中的数据,复制的是地址,所以dirty表和read表对同一个键仍然共享地址。
  • 由于在没有新增键值对以前,所有的增删改查都在read表中实现。假如说在新增键值对之前我们删除掉了key5,从而使其地址为nil,当发现read表中有指向nil的,则将其修改为一个特殊标记expunged,然后跳过该键值,如下图所示。
  • 如果key5再也没有出现,这样不仅dirty表节省了空间,而且随着read表转换成dirty表,read表也少了消耗。但是之后出现了Store(key5,“another value”),sync.Map会检查是否有着"expunged"标记,如果有的话,会加锁然后让dirty表先创建对应的键值对(如下图所示),然后更新值

  • 2. Load获取过程

  • 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都没有查询到,返回nilreturn nil, false}return e.load() // 找到了,返回数据 }func (m *Map) missLocked() {m.misses++if m.misses < len(m.dirty) { // 如果misses小于len(dirty),继续从dirty查询,大于时将dirty转为read表且dirty置为nilreturn}m.read.Store(readOnly{m: m.dirty}) // 将dirty转为read表且dirtym.dirty = nil // dirty表置为nilm.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 }

    总结

    以上是生活随笔为你收集整理的sync.Map低层工作原理详解的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。