Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >go: 当我们在使用sync.Map时,发生了什么

go: 当我们在使用sync.Map时,发生了什么

作者头像
超级大猪
发布于 2020-07-06 02:53:56
发布于 2020-07-06 02:53:56
1K00
代码可运行
举报
文章被收录于专栏:大猪的笔记大猪的笔记
运行总次数:0
代码可运行

sync.Map是我比较喜欢的一个库,用了非常久,今天突发奇想瞧瞧它的实现。又一次被宇宙中第二NB的语言--go 折服了。 这里准备写一篇文章,讨论下当使用sync.Map执行操作的时候,会发生什么。

map结构

代码很简单,sync/map.go中一百多行。总体讲一讲Load, Store, Delete三个接口发生了什么。 首先是sync.Map的结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sync.Map {
        read ,一个真实的map ]
        amended,  bool变量;
        dirty , 一个真实的map
        misses , int 记录读取的时候,在read map中miss的次数
}

操作一下

Store key:1

此时会初始化dirty map,初始化read map,并把amended设为true,这个key存到dirty map中。这里加锁。

执行的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        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})
        }
        m.dirty[key] = newEntry(value)

Store key 2,3

此时直接存dirty map,上面的if !read.amended不会执行了。这里加锁。 只有store 已存在的key(修改操作),可以无锁执行。使用的是atomic.Value结构的功能。

Load key 1

load命令首先会从read map查,如果查不到,amended又是true,那就尝试从dirty map中查,并且记miss。

再Load key:1 两次

当miss的数量等于dirty map的长度的时候,dirty map将直接升级为read map。并且dirty map置为nil。这里需要加锁。

参考代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (m *Map) missLocked() {
    m.misses++
    if m.misses < len(m.dirty) {
        return
    }
    m.read.Store(readOnly{m: m.dirty})
    m.dirty = nil
    m.misses = 0
}

store key: 4

存入一个不存在的key,非常有意思的事情会发生。 此时,dirty map还是nil,它会进行初始化。将read map 拷贝一份过来。然后,将新值存在dirty map,并标记read map amended 为 true。这里加锁。

参考代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        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()  //  就是在这里初始化dirty map
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        m.dirty[key] = newEntry(value)

删除 key: 4

因为key:4 不在read中,在dirty map中且amended为true。所以,直接在dirty map中把key:4 删除。这里加锁。

删除 key: 3

key 3在read map中,直接将key:3 指向nil,注意不是(expunge)。这里无锁。

Store key:3 val: 1234

store一个已存在的key。 如果在read map中,直接修改val为1234。 这里值得一提的是,无论是read map还是dirty map,同一个key,指向的是一个val。这里无锁。

Delete key:3; Store key: 4 ; Load key:4 4次;Store key:5

Delete key:3, 此时,key:3 指向nil。这里无锁。

store key 4; Load key:4 4次。按照上面的情况分析,此时,dirty map被升级为read map,dirty map=nil 。

此时再store key:5, amended 被标记为true,dirty 复制read的数据。 在复制过程中,会判定 3的值是不是nil,如果是,则将值设置为 expunged。并且,不再复制到dirty。 如果一直没有人再执行Store key:3 。在下次dirty 升级的时候,key:3 就会被丢弃。

参考代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func (e *entry) tryExpungeLocked() (isExpunged bool) {
    p := atomic.LoadPointer(&e.p)
    for p == nil {
        if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {  // 这个原子操作将nil的值改为 expunged值。return true使这个key不会被添加到dirty
            return true
        }
        p = atomic.LoadPointer(&e.p)
    }
    return p == expunged
}

Store key:3 val: 1234

如果在read map中,key:3 存在,且被标记为删除(expunged),那么,把这个key添加到dirty map中。并修改值为1234。这里有锁。

参考代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        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.
            m.dirty[key] = e
        }

为什么read中存在值为expunged的key时,这个时候dirty map一定不为nil呢。 1. 因为 expunged 的设置命令出现在dirtyLocked -> tryExpungeLocked这个调用的原子操作中(详细见上面一节),执行时,dirty 已经存在。所以,如果read中有值为 expunged 的key。那一定在dirtyLocked执行之后。 2. 因为dirty 中不可能存在值为expunged的key。dirty如果升级,read中一定不会有值为 expunged 的key。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-07-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
源码解读 sync.Map 实现原理
Go 的内建 map 是不支持并发写操作的,原因是 map 写操作不是并发安全的,当你尝试多个 Goroutine 操作同一个 map,会产生报错:fatal error: concurrent map writes。
张凯强
2020/03/25
9791
手摸手Go 深入浅出sync.Map
日常开发过程中,map结构应该登场率是较为频繁的。但是Go的内建map类型并不是协程安全的。如下面这个栗子,如果业务开发过程中不注意很容易中招。
用户3904122
2022/06/29
3360
手摸手Go 深入浅出sync.Map
go 中没怎么用过的 sync.Map
我们知道 golang 的 map 并发会有问题,所以 go 官方在 sync 包中加入了一个 sync.map 来作为一个官方的并发安全的 map 实现。
LinkinStar
2022/09/01
6460
Go 1.9 sync.Map揭秘
目录 [−] 有并发问题的map Go 1.9之前的解决方案 sync.Map Load Store Delete Range sync.Map的性能 其它 在Go 1.6之前, 内置的map类型是部分goroutine安全的,并发的读没有问题,并发的写可能有问题。自go 1.6之后, 并发地读写map会报错,这在一些知名的开源库中都存在这个问题,所以go 1.9之前的解决方案是额外绑定一个锁,封装成一个新的struct或者单独使用锁都可以。 本文带你深入到sync.Map的具体实现中,看看为了增加一个功能
李海彬
2018/03/27
9310
大白话讲讲 Go 语言的 sync.Map(二)
上一篇文章 《大白话讲讲 Go 语言的 sync.Map(一)》 讲到 entry 数据结构,原因是 Go 语言标准库的 map 不是线程安全的,通过加一层抽象回避这个问题。
仁扬
2023/07/20
3960
深入理解Golang sync.Map设计与实现
Golang为了支持读多写少的场景,提供了sync.Map并发原语,由普通map、Mutex与原子变量组合而成,作为一个并发安全的map,部分情况下读、写数据通过原子操作避免加锁,从而提高临界区访问的性能,同时在高并发的情况下仍能保证数据的准确性,支持Load、Store、 Delete、 Range等操作,可以实现对sync.Map的遍历以及根据key获取value;sync.Map可以有效地替代锁的使用,提高程序的执行效率。
路之遥
2023/09/14
7530
【Golang语言社区】源码篇--sync包map
早晨看到知乎上一篇介绍Go1.9X版本部分功能,特产关注了一下;把源码想给大家呈现下,实际测试请看下一篇文章:Go语言sync.map 实际测试 package sync import ( "sync/atomic" "unsafe" ) // Map is a concurrent map with amortized-constant-time loads, stores, and deletes. // It is safe for multiple
李海彬
2018/03/27
8290
golang源码分析(30)sync.Map
    在日常开发中, 上述这种数据结构肯定不少见,因为golang的原生map是非并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加锁。     之前使用过两个本地内存缓存的开源库, gcache, cache2go,其中存储缓存对象的结构都是这样,对于轻量级的缓存库,为了设计简洁(包含清理过期对象等 ) 再加上当需要缓存大量数据时有redis,memcache等明星项目解决。但是如果抛开这些因素遇到真正数量巨大的数据量时,直接对一个map加锁,当map中的值越来越多,访问map的请求越来越多,大家都竞争这一把锁显得并发访问控制变重。在go1.9引入sync.Map 之前,比较流行的做法就是使用分段锁,顾名思义就是将锁分段,将锁的粒度变小,将存储的对象分散到各个分片中,每个分片由一把锁控制,这样使得当需要对在A分片上的数据进行读写时不会影响B分片的读写。
golangLeetcode
2022/08/02
3710
golang源码分析(30)sync.Map
深度解密Go语言之sync.map
工作中,经常会碰到并发读写 map 而造成 panic 的情况,为什么在并发读写的时候,会 panic 呢?因为在并发读写的情况下,map 里的数据会被写乱,之后就是 Garbage in, garbage out,还不如直接 panic 了。
梦醒人间
2020/05/18
2.1K0
深度解密Go语言之sync.map
不得不知道的Golang之sync.Map解读!
导语 | 本文结合源码,分析sync.Map的实现思路和原理,希望为更多感兴趣的开发者提供一些经验和帮助。 一、背景 项目中遇到了需要使用高并发的map的场景,众所周知golang官方的原生map是不支持并发读写的,直接并发的读写很容易触发panic。 解决的办法有两个: 自己配一把锁(sync.Mutex),或者更加考究一点配一把读写锁(sync.RWMutex)。这种方案简约直接,但是缺点也明显,就是性能不会太高。 使用Go语言在2017年发布的Go 1.9中正式加入了并发安全的字典类型sync.M
腾讯云开发者
2022/06/14
1.7K0
不得不知道的Golang之sync.Map解读!
深入理解Go语音的sync.Map
Go语言的并发编程是其最核心的特性之一。Go的并发模型通过goroutine和channels让并发编程变得简单而高效。然而,在并发环境下共享数据仍然是一个挑战,尤其是当涉及到共享状态的同步时。
windealli
2024/02/28
4940
深入理解Go语音的sync.Map
深入Go:sync.Map
我们在使用Go的项目中需要有并发读写的map时,我们了解到Go提供sync.Map这一数据结构;通过对其简单了解,发现它正好适合我们需要的场景。随着了解的深入,我们又有了疑惑:为什么不像Java SE 8之前的ConcurrentHashMap一样,使用分段锁?为什么在内部需要一个哨兵指针expunged?这两个问题我们简单Google后都没有找到解析和讨论,因此我们决定深入sync.Map的源代码,尝试回答这两个问题。
wenxing
2021/12/14
1.5K0
浅谈Golang两种线程安全的map
在golang中原生map 在并发场景下,同时读写是线程不安全的,无论key是否一样。以下是测试代码
小梁编程汇
2021/12/08
3.4K0
浅谈Golang两种线程安全的map
Go 并发实战 -- sync Map
Java中线程安全的map主要有HashTable、ConcurrentHashMap,go中线程安全的Map就是sync.Map。在单协程访问时我们使用map就可以了,但是在多个协程并发访问时要使用协程安全的sync.Map,原生的map会报错。 常见的并发安全的map实现思路有HashTable那种直接锁死函数,性能比较差,或者说老ConcurrentHashMap那样分段加锁,亦或者像是像是新concurrenthashmap那种cas操作。 实际上sync.Map这三种思路都没选(因为多核情况下cache contention,reflect.New、sync.RWMutex都比较慢),sync.Map追求更好的性能和稳定性,实现思路主要面向多读少写的情况,所以写性能其实比较一般。
邹志全
2019/07/31
6080
看过这篇剖析,你还不懂 Go sync.Map 吗?
本篇文章会从使用方式和源码角度剖析 sync.Map。不过不管是日常开发还是开源项目中,好像 sync.Map 并没有得到很好的利用,大家还是习惯使用 Mutex + Map 来使用。
haohongfan
2021/04/26
7930
看过这篇剖析,你还不懂 Go sync.Map 吗?
真希望你也明白runtime.Map和sync.Map
One of the most useful data structures in computer science is the hash table. Many hash table implementations exist with varying properties, but in general they offer fast lookups, adds, and deletes. Go provides a built-in map type that implements a hash table.
面向加薪学习
2022/12/13
3890
真希望你也明白runtime.Map和sync.Map
go map 原理与并发安全map
go map 整体和 java hashmap 差不多, 只是源码阅读的位置不太方便
leobhao
2024/11/29
1150
go map 原理与并发安全map
如何设计并实现一个线程安全的 Map ?(下篇)
在上篇中,我们已经讨论过如何去实现一个 Map 了,并且也讨论了诸多优化点。在下篇中,我们将继续讨论如何实现一个线程安全的 Map。说到线程安全,需要从概念开始说起。
一缕殇流化隐半边冰霜
2018/08/30
2.2K0
如何设计并实现一个线程安全的 Map ?(下篇)
golang 源码分析(32)sync.map
这里要重点关注readOnly.amended、Map.misses和entry.p的数值状态, 拓扑图中,多处用于走势判断. 接下来详细列出结构体的代码和注释, 方便阅读理解拓扑图.
golangLeetcode
2022/08/02
2630
golang 源码分析(32)sync.map
灰子的Go笔记:sync.Map
在golang中map不是并发安全的,所有才有了sync.Map的实现,尽管sync.Map的引入确实从性能上面解决了map的并发安全问题,不过sync.Map却没有实现len()函数,这导致了在使用sync.Map的时候,一旦需要计算长度,就比较麻烦,一定要在Range函数中去计算长度(备注:这个后面会有例子给出)。
灰子学技术
2020/08/11
5K0
灰子的Go笔记:sync.Map
相关推荐
源码解读 sync.Map 实现原理
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验