Golang sync.Map大白话解析

建议对照参考链接代码食用

结构体

可以简单理解为:sync包中的Map结构体里面有两个map,分别是readdirtyreaddirty的在结构上的最大不同点,就是readdirty的基础上多了一个amended字段,用来表示dirty中是否存在read没有的数据。

其中readdirty中的value值都是一个entry结构体,结构体中存放着指向该值的指针pointer,pointer有三种值,分别是nilexpunged,真正指向值的指针。nil是真正删除了,expunged是软删除。

另外还有一个misses字段和互斥锁mumisses表示穿透了read直接命中dirty的次数。

总结:

sync.Map其实是把数据分成了读和写两个区域,从而减少了每次获取都要加锁的额外开销。

函数介绍

Load方法

 Load(key interface{}) (value interface{}, ok bool)
  1. readMap,如果读到了直接返回结果
  2. 获取不到,判断readMapamended字段(用来表示dirtyMap中是否含有readMap没有的数据),如果amendedtrue,说明dirtyMap中含有readMap没有的数据,如果为false,说明不存在此数据,返回nil和false
  3. 加锁(全局锁),再读readMap(双重校验),读到了entry就返回结果
  4. 再次就那些2的判断,如果为false,说明真的没有数据,返回nil和false。如果为true,再去读dirtyMap
  5. dirtyMap获取entry,获取不到,返回nil和false,获取到了则返回对应的value和true,并且会对misses字段进行+1操作,如果misses字段大于等于dirtyMap长度,则把dirtyMap置换为read的map(相当于把dirtyMap赋值给readMap),并且重置dirtyMap,把misses设置为0,把amended字段设置为false,表示dirtyMap中不存在readMap没有的数据
  6. (上述的返回数据只是用临时变量去存放数据,并没有真正返回,最后才真正返回)释放锁,返回数据。

总结:

  1. 先读readMap,获取不到再加锁。然后双重校验再次读readMap,读不到再去访问dirtyMap
  2. 访问readMap不存在但dirtyMap存在的数据,会带来加锁的额外开销。

Store方法

  1. readMap,如果读到了entry,并且值的指针不是expunged(软删除),则更新值,返回数据
  2. 如果读不到entry,或者值已经被软删除,则加锁,再次读readMap,双重校验。
  3. 如果在readMap读到了entry,并且值已经被软删除,则把entry.p的expunged替换为nil,并且在dirtyMap中添加此key和entry,然后更新entry的值,释放锁,返回。
  4. 如果在readMap读不到entry,则去读dirtyMap,如果在dirtyMap中读到了entry,则执行更新值的操作,并释放锁返回。
  5. 如果dirtyMap中也不存在此值,并且readMap的amended字段为false(dirtyMap中不含有readMap没有的数据),(如果dirtyMap等于nil,则把readMap不为expunged和不为nil的元素添加到dirty中),把amended设置为true,因为现在dirtyMap中有readMap不存在的数据,把新值添加到dirty中,释放锁,返回。
  6. 如果dirtyMap不存在此值,并且amended为true,则把新值添加到dirtyMap中,释放锁,返回。相比于步骤5,减少了把readMap中entry.p != expunged&&entry.p != nil的元素添加到dirtyMap中的步骤。

总结:

  1. Store方法优先无锁访问readMap,未命中会加锁访问dirtyMap
  2. 加锁访问后,会把新的元素添加到dirtyMap中,并把readMap的ammend元素设置为true,用Load函数去获取该元素,会导致加锁访问dirtyMap。并且只有到了未命中次数等于dirtyMap长度以后(Load和Delete方法都会有此检测),才会把dirtyMap升级为readMap,此后Load函数才会直接访问readMap
  3. 所以说,sync.Map不适合频繁插入新元素的场景,这会导致频繁加锁访问dirtyMap,带来额外的性能开销。

Delete方法

  1. readMap,如果读不到entry并且amended为true(说明dirtyMap存在),加锁,再次读readMap,双重校验,如果还是读不到entry并且amended为true,则去读dirtyMap,把dirtyMap中存在的值删掉,misses字段加一,如果misses字段大于等于dirtyMap长度,把dirtyMap升级为readMapdirtyMap设为nil,miss设为0,返回
  2. readMap,如果读到了entry或者amended为false,如果entry.p为nil或者expunged,则直接返回,否则把entry.p设为expunged(软删除),返回。

总结:

  1. 删除readOnly中存在的key,可以不用加锁
  2. 如果删除readOnly中不存在的或者Map中不存在的key,都需要加锁