golang中sync.Map解析

sync.Map是一个并发安全的map,它是通过双层的数据来存储的,第一层read,可以实现无锁的读取,因此sync.Map适合用于读多写少的场景,结构如下


1650986247922-ee66b16b-f812-49c7-b232-e9d7fc3719c2.png

个人理解sync.Map的结构有点像常用的缓存设计,read就是缓存层,它可以进行无锁的读取;dirty就是数据库层,它存储了所有的数据,需要加锁进行操作。dirty在一定情况下会提升到read,同时dirty数据会从read中进行拷贝

dirty和read的转换

read可以理解为缓存,可以无锁进行操作,所以很快,dirty即为全量数据

  1. dirty提升为read

Map中有一个变量为misses,它记录了读取read没命中的次数,当misses次数超过了dirty中数据个数的时候,就会将dirty提升为read,同时dirty置为nil。每次读取read失败的时候都会进行判断是否需要将dirty提升到read,升级结果如下图所示。其实就是将dirty赋值到read.m


1650990276796-2e4117a6-55df-4326-9a74-2055ae997dea.png
  1. 从read复制数据到dirty

在写入数据的时候,如果read未命中,会判断dirty是否为nil,如果是,则会从read中复制数据到dirty中。注意这里expunged就会产生作用了,在复制的过程中,如果read中某个entry存储的value的数据为nil,说明这个数据被删除了,次数dirty不会复制这个entry,同时会将这个entry.p置为expunged。如下图所示。至于key4和key6为什么为nil,可以看删除操作


1650990647868-a82c555a-4b49-4d10-bdc2-9072597512db.png

样例图

1650989663922-b0e05bf3-97e0-49c1-9860-35cccc553b95.png

读取操作

由此可以发现,sync.Map的读取很简单,首先无锁读取read,如果没有再去dirty中读取,其中包括了double check,即加锁后再check一边read的数据

写入操作

对于写入,需要保证read和dirty数据一致性,这里有两种情况,一种是read中有相应的key,一种是read中没有相应key
如果read中没有相应的键,只需要在dirty中写入相应的key/value就行了,当然这里需要加锁进行写入即可。例如,如果我们此时需要插入key5,此时read中并没有key5,则加锁后往dirty中设置key5即可,同样的,key3也是这种情况
如果read中有相应的键,这里需要对key对应的entry中保存的指针进行判断:

  1. 如果entry.p不为expunged,可以将其直接指向value即可,因为dirty和read.m的结构都是map[interface{}]*entry,它们都是指针结构,因此只需要替换掉entry中的指针,就可以实现dirty和read同时更新。对应上图中key1和key4情况
  2. 如果entry.p为expunged,那说明这个key在read中存在,但是在dirty中不存在。这种情况就不能直接更新entry.p了,这样会导致数据不同步。这种情况下,就需要加锁更新dirty和read了,对应上图中key2的情况

删除操作

对于删除操作,也需要保证数据一致性,同样分为read中有相应的key和read中没有相应的key两种情况
如果read中没有相应的key,则直接加锁后从dirty中删除key即可
如果read中有相应的key,则通过将entry.p置为nil即可

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容