go sync.map源码解析
go中的map是并发不安全的,同时多个协程读取不会出现问题,但是多个协程 同时读写就会出现 fatal error:concurrent map read and map write的错误。通用的解决办法如下:
1. 加锁
1.1 通用锁
import "sync"
type SafeMap struct {
data map[string]string
lock sync.Mutex
}
func (this *SafeMap) get(key string) string{
this.lock.Lock()
defer this.lock.Unlock()
return this.data[key]
}
func (this *SafeMap) set(key, value string) {
this.lock.Lock()
defer this.lock.Unlock()
this.data[key] = value
}
1.2 读写锁
import "sync"
type SafeMap struct {
data map[string]string
lock sync.RWMutex
}
func (this *SafeMap) get(key string) string{
this.lock.RLock()
defer this.lock.RUnlock()
return this.data[key]
}
func (this *SafeMap) set(key, value string) {
this.lock.Lock()
defer this.lock.Unlock()
this.data[key] = value
}
1.3 在go1.9之后,go引入了并发安全的map: sync.map
sync.map的原理可以概括为:
1. 通过read和dirty两个字段将读写分离,读的数据存在于read字段的,最新写的数据位于dirty字段上。
2. 读取时先查询read,不存在时查询dirty,写入时只写入dirty
3. 读取read不需要加锁,而读或写dirty需要加锁
4. 使用misses字段来统计read被穿透的次数,超过一定次数将数据从dirty同步到read上
5. 删除数据通过标记来延迟删除
sync.Map结构如下所示:
type Map struct {
mu Mutex //加锁,宝座dirty字段
read atomic.Value // 只读数据,实例类型为 readOnly
dirty map[interface{}]*entry //最新写入的数据
misses int //read被穿透的次数
}
readOnly结构
type readOnly struct {
m map[interface{}]*entry
amended bool // true if the dirty map contains some key not in m.
}
entery结构
type entry struct {
// p == nil entry已经被删除且 dirty == nil
// p == expunged entry已经被删除,但是dirty != nil且dirty中不存在该元素,这种情况出现于重建dirty时,将read复制到dirty中,复制的过程中将nil标记为expunged,不将其复制到dirty
// 除此之外,entry存在于read中,如果dirty != nil则也存在于dirty中
p unsafe.Pointer // *interface{} }
Load()方法
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
//首先尝试从read中读取 readOnly对象
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
//如果不存在则尝试从dirty中读取
if !ok && read.amended {
m.mu.Lock()
//再读取一次read中内容,主要是用于防止上一步加锁过程中dirty map转换为read map导致dirty中读取不到数据
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
//如果确实不存在,则从dirty中读取
if !ok && read.amended {
e, ok = m.dirty[key]
// 不管dirty中存不存在,都将miss + 1, 如果misses值等于dirty中元素个数,就会把dirty中元素迁移到read中
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
Store()方法
// Store sets the value for a key.
func (m *Map) Store(key, value interface{}) {
//直接再read中查找
read, _ := m.read.Load().(readOnly)
//如果找到了,直接更新read中值,返回
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
//如不存在,去dirty中读
m.mu.Lock()
//二次检测
read, _ = m.read.Load().(readOnly)
//如果此时读到,read中不允许直接的添加删除值,此种情况说明加锁之前存在dirty升级为read的操作
if e, ok := read.m[key]; ok {
//如果读到的值为expunged, 说明生成dirty时,复制read中的元素,对于nil的元素,搞成了expunged,所以意味着dirty不为nil,且dirty中没有该元素
if e.unexpungeLocked() {
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
//更新dirty中的值
m.dirty[key] = e
}
//更新read中的值
e.storeLocked(&value)
//此时,read中没有该元素,需要更新dirty中的值
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
} else {
// 如果 !read.amended, 说明dirty为nil, 需要将read map复制一份到dirty map
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
//设置read.amended == true
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
LoadOrStoce()
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored.
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
// Avoid locking if it's a clean hit.
//读取read中是否存在该key
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
//如果存在(是否标识为删除由tryLoadOrStore处理),尝试获取该元素的值,或者将值写入
actual, loaded, ok := e.tryLoadOrStore(value)
if ok {
return actual, loaded
}
}
m.mu.Lock()
//二次检测
read, _ = m.read.Load().(readOnly)
//如果此时读到,read中不允许直接的添加删除值,此种情况说明加锁之前存在dirty升级为read的操作
if e, ok := read.m[key]; ok {
//如果读到的值为expunged, 说明生成dirty时,复制read中的元素,对于nil的元素,搞成了expunged,所以意味着dirty不为nil,且dirty中没有该元素
if e.unexpungeLocked() {
m.dirty[key] = e
}
//如果存在(是否标识为删除由tryLoadOrStore处理),尝试获取该元素的值,或者将值写入
actual, loaded, _ = e.tryLoadOrStore(value)
// 此时,read中没有元素,需要 tryLoadOrStore dirty中值
} else if e, ok := m.dirty[key]; ok {
actual, loaded, _ = e.tryLoadOrStore(value)
m.missLocked()
} else {
// 如果 !read.amended, 说明dirty为nil, 需要将read map复制一份到dirty map
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
// 将值写入dirty中
m.dirty[key] = newEntry(value)
actual, loaded = value, false
}
m.mu.Unlock() return actual, loaded
}
// tryLoadOrStore atomically loads or stores a value if the entry is not
// expunged.
//
// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
// returns with ok==false.
// 如果元素是 expunged, tryLoadOrStore 保持entry不变并直接返回false
func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, ok bool) {
p := atomic.LoadPointer(&e.p)
// 标识删除,直接返回
if p == expunged {
return nil, false, false
}
// 如果元素存在真实值,则直接返回该真实值
if p != nil {
return *(*interface{})(p), true, true
} // Copy the interface after the first load to make this method more amenable
// to escape analysis: if we hit the "load" path or the entry is expunged, we
// shouldn't bother heap-allocating.
// 如果 p == nil, 则更新该元素值
ic := i
for {
if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
return i, false, true
}
p = atomic.LoadPointer(&e.p)
if p == expunged {
return nil, false, false
}
if p != nil {
return *(*interface{})(p), true, true
}
}
}
Delete()方法
// Delete deletes the value for a key.
func (m *Map) Delete(key interface{}) {
// 检查read中是否存在
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果不存在,并且dirty中存在元素
if !ok && read.amended {
m.mu.Lock()
// 二次检测
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
// dirty中删除
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
// 如果存在,直接删除
e.delete()
}
} func (e *entry) delete() (hadValue bool) {
for {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return true
}
}
}
Range()方法
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
//
// Range does not necessarily correspond to any consistent snapshot of the Map's
// contents: no key will be visited more than once, but if the value for any key
// is stored or deleted concurrently, Range may reflect any mapping for that key
// from any point during the Range call.
//
// Range may be O(N) with the number of elements in the map even if f returns
// false after a constant number of calls.
func (m *Map) Range(f func(key, value interface{}) bool) {
// We need to be able to iterate over all of the keys that were already
// present at the start of the call to Range.
// If read.amended is false, then read.m satisfies that property without
// requiring us to hold m.mu for a long time.
read, _ := m.read.Load().(readOnly)
// 如果 amended == true, 说明dirty中存在元素,且包含所有有效元素,此时,使用dirty map
if read.amended {
// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
// (assuming the caller does not break out early), so a call to Range
// amortizes an entire copy of the map: we can promote the dirty copy
// immediately!
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
if read.amended {
//使用dirty map并将其升级为 read map
read = readOnly{m: m.dirty}
m.read.Store(read)
m.dirty = nil
m.misses = 0
}
m.mu.Unlock()
}
// 使用read map读
for k, e := range read.m {
v, ok := e.load()
// 被删除的不计入
if !ok {
continue
}
if !f(k, v) {
break
}
}
}
当sync.Map中存在大量写操作的情况下,会导致read中读不到数据,依然会频繁加锁,同时dirty升级为read,整体性能就会很低,所以sync.Map更加适合大量读、少量写的场景。
go sync.map源码解析的更多相关文章
- 深入浅出ReentrantLock源码解析
ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助. 前提条件 在理解ReentrantLock时需要具备一些基本 ...
- 深入浅出Semaphore源码解析
Semaphore通过permits的值来限制线程访问临界资源的总数,属于有限制次数的共享锁,不支持重入. 前提条件 在理解Semaphore时需要具备一些基本的知识: 理解AQS的实现原理 之前有写 ...
- 给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器
今天的主角是HashSet,Set是什么东东,当然也是一种java容器了. 现在再看到Hash心底里有没有会心一笑呢,这里不再赘述hash的概念原理等一大堆东西了(不懂得需要先回去看下Has ...
- ReactiveCocoa源码解析(五) SignalProtocol的observe()、Map、Filter延展实现
上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...
- ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现
上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...
- React源码解析之React.Children.map()(五)
一,React.Children是什么? 是为了处理this.props.children(this.props.children表示所有组件的子节点)这个属性提供的工具,是顶层的api之一 二,Re ...
- [源码解析]为什么mapPartition比map更高效
[源码解析]为什么mapPartition比map更高效 目录 [源码解析]为什么mapPartition比map更高效 0x00 摘要 0x01 map vs mapPartition 1.1 ma ...
- Netty 4源码解析:服务端启动
Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...
- 【原创】backbone1.1.0源码解析之Collection
晚上躺在床上,继续完成对Backbone.Collection的源码解析. 首先讲讲它用来干嘛? Backbone.Collection的实例表示一个集合,是很多model组成的,如果用model比喻 ...
随机推荐
- Java案例——冒泡排序
排序:将一组数据按照固定的规则进行排列 冒泡排序:一种排序方式,对将要排序的相邻的数据进行两两比较,将较大的放在后面,依次对所有的数据进行操作,直到所有的数据按要求完成排序 冒泡排序原理: 1.如果有 ...
- 和风天气WebApi使用教程
1.首先进入和风天气开发平台,点击右上角的注册进行注册 和风天气开发平台 2.填写注册用的邮箱和密码完成注册,可能还需要手机号,按提示注册完成即可. 3.从和风天气开发平台右上角进入控制台,输入你刚刚 ...
- Java并发编程虚假唤醒问题(生产者和消费者关系)
何为虚假唤醒: 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功:比如买货:如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买, ...
- 关于List、Set、Map接口讲解
概述 List.Set接口都是继承于Collection主接口,而Map为独立接口 1.List接口下有ArrayList.Vector.LinkedList实现类 2.Set接口下有HashSet. ...
- CF932G Palindrome Partition(回文自动机)
CF932G Palindrome Partition(回文自动机) Luogu 题解时间 首先将字符串 $ s[1...n] $ 变成 $ s[1]s[n]s[2]s[n-1]... $ 就变成了求 ...
- CSC3100
其实是存一下代码 1. AVL的java实现 维护一下每个点左右子树深度差,差绝对值大于2就转,转的方式和treap, splay转的方式差不多.旋转操作可以使两端差归零变得更平衡. 虽然平衡但转的次 ...
- 论文解读(MVGRL)Contrastive Multi-View Representation Learning on Graphs
Paper Information 论文标题:Contrastive Multi-View Representation Learning on Graphs论文作者:Kaveh Hassani .A ...
- Java 中 ++ 操作符是线程安全的吗?
不是线程安全的操作.它涉及到多个指令,如读取变量值,增加,然后存 储回内存,这个过程可能会出现多个线程交差.
- 学习RabbitMQ(一)
消息中间件 一.简介 消息中间件就是在消息的传输过程中保存消息的容器.消息中间件再将消息从它的源中继到它的目标时充当中间人的作用.队列的主要目的是提供路由并保证消息的传递:如果发送消息时接收者不可用, ...
- python中模块制作、发布、安装
模块的发布 需要在当前目录下 模块的安装 真实制作发布一个包 包的制作 (1)将写好的包放在moudelTest目录下 (2)moudelTest目录下创建一个setup.py文件(格式上面有介绍) ...