大白话讲讲 Go 语言的 sync.Map(一)

简介: 在讲 sync.Map 之前,我们先说说什么是 map(映射)。我们每个人都有身份证号码,如果我需要从身份证号码查到对应的姓名,用 map 存储是非常合适的……

阅读本文大约需要 4.25 分钟。

程序是枯燥乏味的。

在讲 sync.Map 之前,我们先说说什么是 map(映射)。

我们每个人都有身份证号码,如果我需要从身份证号码查到对应的姓名,用 map 存储是非常合适的。

map[000...001] = 张三
map[000...002] = 李四
...
map[999...993] = 钱五

身份证号码有 18 位,如果要知道 111...002 这个人叫什么名字,没有 map 我只能从 000...001 一个一个往下查找,效率是非常低的。

咦,那 map 不就是在查字典嘛?根据拼音、笔画、部首,可以查到某个字的具体含义!

没错!Go 语言中的 map 在 Python 语言称之为 dict(字典),意思是完全一样的。

再设想另一个场景,

如果 map 存储的是每个人银行卡里的余额(同一所银行),那就是这样子的形式(账本):

map[张三] = 100.00
map[李四] = 600.00
map[钱五] = 800.00

某一天,李四要转账给张三和钱五,各 100 元,银行为了提高转账速度,安排了两名交易员同时处理。

交易员 A 和交易员 B 瞄了一眼账本,开始操作:

交易员 A:李四的余额是 600 元,张三的余额是 100 元,转账后李四的余额是 500 元,张三的余额是 200 元。

交易员 B:李四的余额是 600 元,钱五的余额是 800 元,转账后李四的余额是 500 元,钱五的余额是 900 元。

账本变成这个样子:

map[张三] = 200.00
map[李四] = 500.00
map[钱五] = 900.00

账本出问题了!银行凭空多出 100 元!

一个一个来不就完了?可是你别忘了,我们是为了提高转账速度,才这样做的。

在 Go 1.9 之前,大部分人还真的就是这么干的!

type Name        string
type Money       string
type AccountBook struct {
    lock sync.RWMutex
    m    map[Name]Money
}

sync.RWMutex 是一个读写锁,在写入数据的时候,阻止其他人写入、读取,让其他人处于等待的状态,直到操作完再释放锁。

本质上,上面的例子,就是读取到了脏数据,如果能等待交易员 A 把账本改完,交易员 B 再去操作,账本就不会乱了。

如果你不知道锁是什么,我再给你讲一个例子:

张三和李四两个人,需要打印不同的文档,

打印机只有一台,放在打印室里,打印室有钥匙,

钥匙只有一把,谁拿到打印室的钥匙,谁就能进去打印。

打印室的钥匙,就是锁。

张三拿了钥匙,进去打印室,打印完了,就出来后把钥匙给了李四,李四打印完了把钥匙还回打印室(真是有条不紊)。

我花费这么多笔墨说 map,也是真的希望,就算你不是程序员,不是 Go 语言后端工程师,也可以看懂我的文章。

不得不承认,把复杂琐碎的东西,讲通透、讲明白是一种本事。

教科书讲 if...else、switch、while (true) 、异常和捕获,

如果有下面的图片这么形象生动就好了:

图

看到图片的那一瞬间,真的把我逗乐了。

多么形象生动啊!

回头想想,大学的 C 语言课程是多少人的噩梦,老师都是照书念的,完全听不进去。

我也不感慨了,咱们还是回归正题。

刚刚讲了 map,接着往下讲 sync.Map,它用来解决什么问题?

我们知道 map + 锁的形式,还是有等待的现象出现,不符合我们提高转账速度的初衷。

而 sync.Map 有一个非常巧妙的抽象(entry 的 p 指向具体数据的位置):

var m map[key]*entry

type entry struct {
    p unsafe.Pointer
}

还是看回上面的例子,做个小修改——原先的 map 是一个小账本,我们又做了一个大账本,原先的账本变成:

map[张三] = 记录在大账本第 6 页(翻开第 6 页,内容是:100.00)
map[李四] = 记录在大账本第 7 页(翻开第 7 页,内容是:600.00)
map[钱五] = 记录在大账本第 8 页(翻开第 8 页,内容是:800.00)

假设小账本 map 的张三、李四只能一个一个排队改,没办法做到同时修改,

而我们有了大账本,可以直接同时修改张三、李四纸上的内容(两页纸互不影响了)。

(真实的计算机世界确实如此,具体是怎么样的,留一个思考题,下一篇文章细细解答)

更通俗的讲,sync.Map 通过 entry 这个中间层的抽象,

把最开始整个小账本的冲突(影响所有人),降低到大账本上的某一页纸(只影响某个人),

用计算机术语讲,就是降低锁的粒度,从而提升性能!

另一方面,假设李四销户了,

我可以选择在第 7 页的纸上写,已销户(expunged),

// expunged is an arbitrary pointer that marks
// entries which have been deleted from the 
// dirty map.
var expunged = unsafe.Pointer(new(interface{}))

如果是以前,只能把小账本,李四那一张纸撕掉,

而撕掉小账本的某一页,也会影响所有人使用小账本,

如果下次要把撕掉的那一页放回去,也是非常麻烦,

在计算机的世界里,这是资源的分配和回收的问题,会严重影响程序运行效率。

写了一千七百字,直到现在只是冰山一角,sync.Map 的巧妙之处,远远不止 entry 的抽象。

今天先消化这么多,下一篇文章会更深层次一些,敬请期待!


文章来源于本人博客,发布于 2021-05-04,原文链接:https://imlht.com/archives/234/

目录
相关文章
|
4天前
|
监控 Linux PHP
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
51 20
|
2天前
|
存储 监控 算法
探秘员工泄密行为防线:基于Go语言的布隆过滤器算法解析
在信息爆炸时代,员工泄密行为对企业构成重大威胁。本文聚焦布隆过滤器(Bloom Filter)这一高效数据结构,结合Go语言实现算法,帮助企业识别和预防泄密风险。通过构建正常操作“指纹库”,实时监测员工操作,快速筛查可疑行为。示例代码展示了如何利用布隆过滤器检测异常操作,并提出优化建议,如调整参数、结合日志分析系统等,全方位筑牢企业信息安全防线,守护核心竞争力。
|
10天前
|
Go C语言
Go语言入门:分支结构
本文介绍了Go语言中的条件语句,包括`if...else`、`if...else if`和`switch`结构,并通过多个练习详细解释了它们的用法。`if...else`用于简单的条件判断;`if...else if`处理多条件分支;`switch`则适用于基于不同值的选择逻辑。特别地,文章还介绍了`fallthrough`关键字,用于优化重复代码。通过实例如判断年龄、奇偶数、公交乘车及成绩等级等,帮助读者更好地理解和应用这些结构。
34 14
|
24天前
|
存储 监控 算法
内网监控系统之 Go 语言布隆过滤器算法深度剖析
在数字化时代,内网监控系统对企业和组织的信息安全至关重要。布隆过滤器(Bloom Filter)作为一种高效的数据结构,能够快速判断元素是否存在于集合中,适用于内网监控中的恶意IP和违规域名筛选。本文介绍其原理、优势及Go语言实现,提升系统性能与响应速度,保障信息安全。
28 5
|
1月前
|
存储 安全 Go
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
9天前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
19 0
|
2月前
|
开发框架 Go 计算机视觉
纯Go语言开发人脸检测、瞳孔/眼睛定位与面部特征检测插件-助力GoFly快速开发框架
开发纯go插件的原因是因为目前 Go 生态系统中几乎所有现有的人脸检测解决方案都是纯粹绑定到一些 C/C++ 库,如 OpenCV 或 dlib,但通过 cgo 调用 C 程序会引入巨大的延迟,并在性能方面产生显著的权衡。此外,在许多情况下,在各种平台上安装 OpenCV 是很麻烦的。使用纯Go开发的插件不仅在开发时方便,在项目部署和项目维护也能省很多时间精力。
|
3月前
|
Go 数据安全/隐私保护 开发者
Go语言开发
【10月更文挑战第26天】Go语言开发
54 3
|
3月前
|
Java 程序员 Go
Go语言的开发
【10月更文挑战第25天】Go语言的开发
47 3
|
6月前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
230 1
go语言后端开发学习(四) —— 在go项目中使用Zap日志库