go map 总结

122 阅读2分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

基本罗列一下 go 原生提供最基本的数据机构。面试向供。建议有看过源码的看此篇文章,达到快速记忆,没看过的建议去看源码。

map

  1. 查找过程?
  2. delete map key 会引发什么性能问题?
  3. sync.Map 是怎么保证并发安全的?

查找过程

  1. 在初始化时,会选定一个 hasher 函数。t.hasher(key, uintptr(h.hash0)) 获取到 hash 值

  2. hash 按位与 → 拿到低位的 B 位数 (和容量有关) 的值 ⇒ bucket,所在的桶

  3. 如果 oldbuckets 不为空,先在 old 里面查找:

    1. 先会确定 key 在 oldbuckets 中的位置
    2. 如果 oldbucket 还没有搬迁到新的 bucket,那就在 oldbuckets 中查找
    3. 否则就在 bucket 中查找
  4. bucket 中的查询 (不管是 oldbuckets 还是 bucket,均是如此):

    1. 按位与取出 高8位tophash 值 → 可以快速查找 bucket 中有没有 key 的存在
    2. 如果存在,就去找到 tophashindex 位置 (向后偏移 i 个 keysize) → key 的位置
    3. 判断 key 是否相等 (hash 冲突校验) → 相等就按照同样的偏移找到 value 的位置
    4. 找到就返回
    5. 如果在当前的 bucket 中没有找到,则去 overflow(t) 中寻找 (溢出桶中寻找)

delete key

  1. 删除依然还是先要找到这个 key 的位置 → 查找过程
  2. 找到位置 → 对 key/value 清零
  3. count - 1,总数 - 1 这个很正常
  4. 对应的 tophash → Empty

sync.Map

map 是没有保证并发读写的,会直接 panic。这个在源码级别已经控制住了,如果发现正在被读,你进行写操作,直接 panic。

先说说 sync.Map 使用场景:

  1. 只会增长的缓存系统中,一个 key 只写入一次而被读很多次
  2. 多个 goroutine 为不相交的键集读、写和重写键值对

说白了就是尽量不要并发对一个 key 进行频繁的读写。

然后看看实现:

  1. 只读的 read 字段,可写的 dirty → 对 read 是不需要加锁的
  2. 优先从 read 字段读取、更新、删除
  3. miss 次数多了之后,将 dirty 数据提升为 read,避免总是从 dirty 中加锁读取
  4. double-checking。加锁之后先还要再检查 read 字段,确定真的不存在才操作 dirty 字段
  5. 删除一个键值只是打标记,只有在提升 dirty 字段为 read 字段的时候才清理删除的数据