sync.map

1.map并发问题

1.1map线程安全

map不是线程安全的,在查找、赋值、遍历、删除的过程中都会先检查写标志,一旦发现写标志位等于1,则直接panic。赋值和删除函数在检查完写标志位是复位状态(等于0)之后,先将写标志位置位(置为1)才会进行赋值和删除的操作。

为什么会panic:并发读写的情况下,map里面的数据会被写乱,会对gc造成问题,还有扩容时候出现新旧桶数据不一致导致的并发问题。

所以在并发读写操作map时,需要加锁,但是会降低很大的性能,所以这个时候sync.map横空出世!

1.2sync.map

  1. 使用场景:适用于读多写少的场景。对于写多的场景,会导致 read map 缓存失效,需要加锁,导致冲突变多;而且由于未命中 read map 次数过多,导致 dirty map 提升为 read map,这是一个 O(N) 的操作,会进一步降低性能。

2.数据结构

    type Map struct {
    	mu Mutex
    	read atomic.Value // readOnly
    	dirty map[interface{}]*entry
    	misses int
    }

		// Map.read 属性实际存储的是 readOnly。
		type readOnly struct {
		 m       map[interface{}]*entry
		 amended bool
		}

mu:互斥锁,保护read和dirty字段

read:只读数据,可以并发的读—atomic.Value类型,如果需要更新read就需要加锁保护。

amended字段:标记read和dirty的数据是否一致。

dirty:读写数据,是一个非线程安全的原始map,包含新写入的key和read中未删除的key。

misses:每次从read中读取失败,就会misses+1,当加到一定的阈值就会将dirty提升为read。

2.sync.map的整体结构:

1.结构如下所示:
sync.map
下面详细介绍一下在sync.map中的增删改查操作:

2.1.查询具体流程

sync.Map 的两个 map,当从 sync.Map 类型中读取数据时,其会先查看 read 中是否包含所需的元素:

若有,则通过 atomic 原子操作读取数据并返回。
若无,则会判断 read.readOnly 中的 amended 属性,他会告诉程序 dirty 是否包含 read.readOnly.m 中没有的数据;因此若存在,也就是 amended 为 true,将会进一步到 dirty 中查找数据。

2.2.sync.map添加元素

例如sync.map追加—例增加“d”:”D”

map会先去m中查询,如果m中不存在相应的d键值对,就加锁 mu,去dirty中增加新的键值对

增加方法:在dirty中加入d,值为指向一个结构体的指针,pointer为万能指针,结果为“D”,并且将amended置为TRUE。

2.3.sync.map追加后读写

1.追加后读写,查询先去m中查找,如果没找到并且amended为true,则去dirty中查找,并且将misses+1。当增加到misses==length(dirty)时候,实现dirty提升。

2.dirty提升,misses=0,amended=false,底下的dirty表初始为nil,如果有追加操作在像里面赋值,重建dirty。

2.4.sync.map删除问题

因为涉及到两个表的并发性和数据一致性问题,还有dirty表提升的问题,删除问题相比而言比较复杂。先介绍一下p指针,就是上图中的pointer万能指针。
p指针

1.当 p == nil 时,说明这个键值对已被删除,并且 m.dirty == nil,或 m.dirty[k] 指向该 entry。

2.当 p == expunged 时,说明这条键值对已被删除,并且 m.dirty != nil,且 m.dirty 中没有这个 key。

其他情况,p 指向一个正常的值,表示实际 interface{} 的地址,并且被记录在 m.read.m[key] 中。如果这时 m.dirty 不为 nil,那么它也被记录在 m.dirty[key] 中。两者实际上指向的是同一个值。

当删除 key 时,并不实际删除。一个 entry 可以通过原子地(CAS 操作)设置 p 为 nil 被删除。如果之后创建 m.dirty,nil 又会被原子地设置为 expunged,且不会拷贝到 dirty 中。

如果 p 不为 expunged,和 entry 相关联的这个 value 可以被原子地更新;如果 p == expunged,那么仅当它初次被设置到 m.dirty 之后,才可以被更新。

Q:为什么要设置nil和expunged两种状态:

expunged是在dirty 时增加并且删除了数据,当dirty提升为map的时候,新的dirty中并不存在该数据,为了保证删除操作的正确性和实现读写与追加分离设置了这个状态。

3.总结

总结:sync.map使用了两个map,分离了扩容问题,实现map的并发性。

在不会引发扩容问题的操作(查改)使用readmap

在会引发扩容问题的操作(新增)使用dirtymap

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔可南-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值