• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Go语言:sync.Map

武飞扬头像
狗熊冬天不睡觉
帮助1

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

1. 数据结构

  1.  
    type Map struct {
  2.  
    mu Mutex // 内部互斥锁,增,改数据会用到,删除可能会用到
  3.  
     
  4.  
    read atomic.Value // readOnly 包含部分数据,但是多线程读安全
  5.  
     
  6.  
    dirty map[interface{}]*entry // 存放新增数据
  7.  
     
  8.  
    misses int // 多次从read中读取失败会增加此计数,大于等于dirty后会出发missLocked,即拷贝全量dirty覆盖read
  9.  
    }
  1.  
    type readOnly struct {
  2.  
    m map[interface{}]*entry // 多线程读安全
  3.  
    amended bool // 为true时,表明dirty map中新插入的key-value,未同步到readonly
  4.  
    }

2.接口 

  •  Load(key interface{}) (value interface{}, ok bool)
  1.  
    func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
  2.  
    read, _ := m.read.Load().(readOnly)
  3.  
    e, ok := read.m[key]
  4.  
     
  5.  
    // 从read中读取失败,并且dirty有更新,则从dirty加锁再读取一次
  6.  
    if !ok && read.amended {
  7.  
    m.mu.Lock()
  8.  
    read, _ = m.read.Load().(readOnly)
  9.  
    e, ok = read.m[key]
  10.  
    if !ok && read.amended {
  11.  
    e, ok = m.dirty[key]
  12.  
    // 从read中read miss后会出发missLocked,记录miss次数,超过dirty大小,就会强制更新read
  13.  
    m.missLocked()
  14.  
    }
  15.  
    m.mu.Unlock()
  16.  
    }
  17.  
    if !ok {
  18.  
    return nil, false
  19.  
    }
  20.  
    // load 原子操作
  21.  
    return e.load()
  22.  
    }
学新通
  • Store(key, value interface{})
  1.  
    func (m *Map) Store(key, value interface{}) {
  2.  
    // key存在于read中,即修改其值,tryStore是原子操作
  3.  
    read, _ := m.read.Load().(readOnly)
  4.  
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
  5.  
    return
  6.  
    }
  7.  
     
  8.  
    // 不存在read,或者原子修改失败即表明此时有其他线程同时在删除该key
  9.  
    // 加锁重新从read读取,不信邪?
  10.  
    m.mu.Lock()
  11.  
    read, _ = m.read.Load().(readOnly)
  12.  
    if e, ok := read.m[key]; ok {
  13.  
    // update
  14.  
    if e.unexpungeLocked() {
  15.  
    // 如果已被清除,则直接修改dirty
  16.  
    m.dirty[key] = e
  17.  
    }
  18.  
    // 再修改read
  19.  
    e.storeLocked(&value)
  20.  
    } else if e, ok := m.dirty[key]; ok {
  21.  
    // 如果该key不在read而在dirty中,则直接修改dirty
  22.  
    // update
  23.  
    e.storeLocked(&value)
  24.  
    } else {
  25.  
    // insert
  26.  
    // amended为false,表示read是全量数据,还未进行任何修改
  27.  
    // amended为true,表示dirty有新增key-value
  28.  
    if !read.amended {
  29.  
    // dirtylock
  30.  
    // 1、 dirty map的初始化
  31.  
    // 2、并且将read中的值更新到dirty
  32.  
    // 3、将删除的key置为expunged
  33.  
    m.dirtyLocked()
  34.  
    // 单纯修改amended变量,
  35.  
    m.read.Store(readOnly{m: read.m, amended: true})
  36.  
    }
  37.  
    // dirty插入新值
  38.  
    m.dirty[key] = newEntry(value)
  39.  
    }
  40.  
    m.mu.Unlock()
  41.  
    }
学新通
  • Delete(key interface{})

  1.  
    func (m *Map) Delete(key interface{}) {
  2.  
    read, _ := m.read.Load().(readOnly)
  3.  
    e, ok := read.m[key]
  4.  
    // read中不存在,amended为true表示有新值 那么就去dirty看看
  5.  
    if !ok && read.amended {
  6.  
    m.mu.Lock()
  7.  
    // 不信邪 再读一次
  8.  
    read, _ = m.read.Load().(readOnly)
  9.  
    e, ok = read.m[key]
  10.  
    if !ok && read.amended {
  11.  
    // read中还是没有,就直接删除dirty map吧,有没有已经不重要了,反正加锁了
  12.  
    delete(m.dirty, key)
  13.  
    }
  14.  
    m.mu.Unlock()
  15.  
    }
  16.  
    if ok {
  17.  
    // 并没有真正删除,只是把key对应value置为nil
  18.  
    e.delete()
  19.  
    }
  20.  
    }
学新通

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcbjja
系列文章
更多 icon
同类精品
更多 icon
继续加载