不得不知道的Golang之sync.Map源码分析

本文深入分析了Golang 1.10中的sync.Map,揭示其如何通过空间换时间策略实现并发读写,避免加锁带来的性能影响。sync.Map包含read和dirty两个数据结构,读写分离以提高效率。文章详细讲解了Map结构、工作原理、优化点以及Load、LoadOrStore、Delete等方法的源码分析,指出sync.Map适用于大量读、少量写场景。
摘要由CSDN通过智能技术生成

背景

众所周知,go普通的map是不支持并发的,换而言之,不是线程(goroutine)安全的。博主是从golang 1.4开始使用的,那时候map的并发读是没有支持,但是并发写会出现脏数据。golang 1.6之后,并发地读写会直接panic:

fatal error: concurrent map read and map write 
package main
func main() {
	m := make(map[int]int)
	go func() {
		for {
			_ = m[1]
		}
	}()
	go func() {
		for {
			m[2] = 2
		}
	}()
	select {}
} 

所以需要支持对map的并发读写时候,博主使用两种方法:

1.第三方类库 concurrent-map

2.map加上sync.RWMutex来保障线程(goroutine)安全的。

golang 1.9之后,go 在sync包下引入了并发安全的map,也为博主提供了第三种方法。本文重点也在此,为了时效性,本文基于golang 1.10源码进行分析。

sync.Map

结构体

Map
type Map struct {
	mu Mutex//互斥锁,用于锁定dirty map

	read atomic.Value //优先读map,支持原子操作,注释中有readOnly不是说read是只读,而是它的结构体。read实际上有写的操作

	dirty map[interface{}]*entry // dirty是一个当前最新的map,允许读写

	misses int // 主要记录read读取不到数据加锁读取read map以及dirty map的次数,当misses等于dirty的长度时,会将dirty复制到read
} 
readOnly

readOnly 主要用于存储,通过原子操作存储在Map.read中元素。

type readOnly struct {
	m map[interface{}]*entry
	amended bool // 如果数据在dirty中但没有在read中,该值为true,作为修改标识
} 
entry
type entry struct {
	// nil: 表示为被删除,调用Delete()可以将read map中的元素置为nil
	// expunged: 也是表示被删除,但是该键只在read而没有在dirty中,这种情况出现在将read复制到dirty中,即复制的过程会先将nil标记为expunged,然后不将其复制到dirty
	//其他: 表示存着真正的数据
	p unsafe.Pointer // *interface{}
} 

原理

如果你接触过大Java,那你一定对CocurrentHashMap利用锁分段技术增加了锁的数目,从而使争夺同一把锁的线程的数目得到控制的原理记忆深刻。那么Golang的sync.Map是否也是使用了相同的原理呢?sync.Map的原理很简单,使用了空间换时间策略,通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。 通过引入两个map将读写分离到不同的map,其中read map提供并发读和已存元素原子写,而dirty map则负责读写。 这样read map就可以在不加锁的情况下进行并发读取,当read map中没有读取到值时,再加锁进行后续读取,并累加未命中数,当未命中数大于等于dirty map长度,将dirty map上升为read map。从之前的结构体的定义可以发现,虽然引入了两个m

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值