sync.Map源码分析

概述

go语言中的map并不是并发安全的,在Go 1.6之前,并发读写map会导致读取到脏数据,在1.6之后则程序直接panic,所以go 1.9之前的解决方案是额外绑定一个锁,封装成一个新的struct或者单独使用锁都可以。直到sync.Map出现提供了一种空间换时间有效减少锁的实现方法。

原理

为了减少并发抢锁导致的阻塞,sync.Map分出了read和dirty两个map,里面存的都是指针。存、删和查都先操作read,并用atomic进行并发保护,速度较快,直到read不能满足需求才去操作dirty,操作dirty的时用Mutex锁进行并发保护,速度较慢。

源码分析

主要结构

主要方法

特别提醒

sync.Map在初始化时会将read中所有未删除的数据复制到dirty,而频繁往map中插入新数据会导致dirty中有大量read中没有的数据,从而导致read的命中率过低,需要频繁调用锁进行操作,并且未命中次数达到len(dirty)后,dirty会被升级为read,再次有新数据插入的时候,又会重复dirty初始化的过程。这一系列流程均会造成较大的开销影响整体性能。

适用场景

综上sync.Map适用于读多更新多,新增少的场景。

注:这里更新多特指当read中存在该键且未被标记删除时更新操作,此场景可直接原子更新无需加锁。


欢迎订阅我的公众号,文章更新早知道

sync.Map源码分析》有2个想法

  1. 注:这里更新多特指当read中存在该键且未被标记删除时更新操作,此场景可直接原子更新无需加锁。

    ——

    虽然说操作是原子的, 但是它前面还是有锁保持着. 可能锁的开销较小, 很快就释放.
    我的理解正确吗

    1. read, _ := m.read.Load().(readOnly)
      // 如果m.read存在这个键,并且这个entry没有被标记删除,尝试直接存储。
      // 因为m.dirty也指向这个entry,所以m.dirty也保持最新的entry。
      if e, ok := read.m[key]; ok && e.tryStore(&value) {
      return
      }
      你跟下e.tryStore(&value)的代码
      atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i))
      这里是原子操作并没有锁的

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据