golang中map的并发问题(golang中的sync.map)

 

        在这个程序中,我们对map进行了并发的读和写,并且读和写的值并不一样,按照我们正常的逻辑来说,这个并发程序并没有问题,但是运行后却会发现出错了。 错误显示的是对map进行了并发的读和写。

         为什么会出现这种问题呢?在map的扩容(具体的扩容过程自行百度)中,会产生新桶并且会逐步的删除旧桶,在并发的读写map时极易导致程序读到已删除的旧桶,go的创始人预见了这种情况,所以严格的限制了对map的并发读写。那么有没有办法解决这种情况呢?也有,最简单的办法就是在读写的时候加锁,但是这样在并发量大的情况下会严重影响运行速度。

        这时就可以用到sync.map了。这边我们来看一下sync.map的源码,在sync包中的map.go中。

type Map struct {
   mu Mutex

   // read contains the portion of the map's contents that are safe for
   // concurrent access (with or without mu held).
   //
   // The read field itself is always safe to load, but must only be stored with
   // mu held.
   //
   // Entries stored in read may be updated concurrently without mu, but updating
   // a previously-expunged entry requires that the entry be copied to the dirty
   // map and unexpunged with mu held.
   read atomic.Value // readOnly

   // dirty contains the portion of the map's contents that require mu to be
   // held. To ensure that the dirty map can be promoted to the read map quickly,
   // it also includes all of the non-expunged entries in the read map.
   //
   // Expunged entries are not stored in the dirty map. An expunged entry in the
   // clean map must be unexpunged and added to the dirty map before a new value
   // can be stored to it.
   //
   // If the dirty map is nil, the next write to the map will initialize it by
   // making a shallow copy of the clean map, omitting stale entries.
   dirty map[any]*entry

   // misses counts the number of loads since the read map was last updated that
   // needed to lock mu to determine whether the key was present.
   //
   // Once enough misses have occurred to cover the cost of copying the dirty
   // map, the dirty map will be promoted to the read map (in the unamended
   // state) and the next store to the map will make a new dirty copy.
   misses int
}

        其中mu是互斥锁,read其实是个结构体,其源码为

 m是map[any]*entry类型,any其实是个空接口,entry是个含有unsafe.pointer的结构体。所以它其实就是个能够存放任意类型key-value的map,amended就是修正的意思,如果脏映射包含一些不在m中的键,则为true。

        回到sync.map中,dirty也是个能够存放任意类型key-value的map,而misses就是代表有没有命中的意思。

        sync.map的整体结构就差不多是这个样子的

        这里显示的是 m和dirty是共用一套key-value值的,那么有什么用呢?接下来我们在好好盘盘。

        当我们要正常的读和修改时,我们只是走上面的这个m表。如图

         当我们要追加数据时,会先正常的走一遍上面的表,然后发现表中没有这个key值,之后ameneded为true,再加上锁,这个锁是锁下面这个表的,上面这个表这时是可以正常访问的。之后走下面这个表,把键值对加上。

        这时上面这个表是不完整的,但是读写时还是会先访问上面这个表,上面这个表的读和修改还是能做到并发的,只要你不涉及扩容就可以,扩容走下面这个表。

         之后就是追加后读写的问题了,当上面这个表未命中,就会去读下面这个表(注意这是个互斥锁,当有其他人读写这个表时你就只能是等待了),如果下面这个表命中了,那么misses就会加1。

        那你也不能总读下面这个表吧,因为上面表才是让你高频并发读写用的,当misses的值为len(dirty)时,就会触发dirty 提升,就会把上面这个表干掉,然后把下面这个表给提上去。然后amended改为false,misses改为0.

         

         一开始并不会重建下面这个表,等到后来需要追加数据的时候才会重建下面这个表。这时就又开始新一轮的循环了。

        接下来,我们来说说删除的问题,删除又分正常删除和追加后删除。

        在正常删除的情况下,不删除key,就是把后面的Pointer置为nil,后面的value会因为gc过段时间就会被自动删除。

         追加后删除:还是先从上面表开始找起,没找到之后将amended置为true,mu加锁。之后跟前面的一样,不删除key值,将Pointer置为nil。​​​​​​​

         当dirty提升的时候,这里的nil值也是跟着上去的。        但是在之后的重建dirty表时,并不会把nil值给复制下去,并把nil值置为expunged(也是已删除的意思)。

        sync.map这里就介绍完毕了。sync.map解决了map扩容时的并发问题,做到了读写和扩容分离。

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值