sync.map原理解析

最新一个高并发项目中需要大量使用sync.map,为了更好的评估sync.map对的cpu和内存影响,深入探究一下sync.map的原理,最好总结,为后面的选型做好准备.

1、sync.map的数据结构

相比与Java中的CurrentHashMap来说sync.map的实现简单了许多.一句话总结sync.map使用了写时复制的技术实现了高并发的map.
我们先来看看sync.map的数据结构类图.这里就不贴代码,最好结合代码再来看这张图.
sync.map原理解析_第1张图片
从在这里,我们可以看到sync.map封装了真正value的指针.当key存在的时候,那么可以直接通过value的unsafe指针做原子替换,做到无锁替换值的效果.当key不存在的时候,就相对比较复杂.我们再来看看Stroe函数的流程.

2、Store函数

sync.map原理解析_第2张图片

  • 从图中我们可以看到蓝色的路径是无锁路径,即当key在写表与读表同时存在时,那么直接拿到entry然后替换真正value的指针即可
  • 当key的值在读表中不存在、或者key的值只在读表中存在(即entry的指针指向expunge)时.那么就会开始加锁,并把记录往写表的里面写.
  • 这里用到我们熟悉的双重判断
  • 如果在最开始的时候写入一大波不同key,那么会一直加锁,但只要key被插入过一次,且没被删除,那么就一直不用加锁.这个跟java有一定的区别
  • 通过expunge不用加锁就可以知道写表中是否存在这个key,这个点记住,因为后面的函数会多次用这个字段来判断key在写表中是否存在
  • 可以理解读表是作为一层缓存的存在,写的时候加锁写真正的map.读的时候从缓存读,缓存没有再击穿进去读.如果一直在写新的key,但一直在读sync.map中不存在的key,也会导致一直加锁

3、Delete函数

sync.map原理解析_第3张图片

  • 删除的关键动作就是找到key,并把entry中指向的值置为nil,所以这里也存在找key的动作
  • 在找key的时候,如果key不存在,那么一定要加锁去写表中找
  • 这里有两个关键信息,就是读表删除的时候,是将entry中的value指向nil.到写表中删除的时候,是直接删除这个key

4、Load函数

sync.map原理解析_第4张图片

  • 读出的流程跟删除的流程差不多,这里就不再累述

5、总结

  • go的sync.map的实现主要是用了读写两张表的方式实现的,在实现上面比java的简单很多.对于那种不是很频繁插入key的场景比较合适.不像java是使用分桶key实现
  • 在这个意义上,可以理解读表是写表的一个缓存,当读取的次数达到len(写表)的数量时,那么就刷一次缓存到读表.当写表为空的时候,需要复制一份写表的数据出来.如果此时数据比较多的话,那么也会消费比较多的时间片.

你可能感兴趣的:(学习日记,缓存,开发语言,golang)