Go Map并发冲突预防与解决
作者:小马别过河 发布时间:2024-02-19 00:25:24
背景
关于 Go 语言的 Map,有两个需要注意的特性:
Map 是并发读写不安全的,这是出于性能的考虑;
Map 并发读写导致的错误,无法使用
recover
捕获。
后者意味着,只有出现并发读写的问题,服务就会挂掉。
这两个特性可能大家都知道,可即使有这个共识,我还是见过这个问题导致的事故。
事故的大致情况是,一个人封装了map的读写,没有使用锁。另一个人开协程读写 map。而测试环境请求量小,不一定会导致崩溃,于是,这个问题就留到生产环境才出现了。
除了靠开发者自觉和 code review,还能怎么预防这种情况呢?我觉得在单元测试加入并行测试也很重要。
并行单元测试
单元测试默认不是并发的,比如下面的单测,是可以通过的:
func TestConcurrent(t *testing.T) {
var m = map[string]int{}
// 写 map
t.Run("write", func(t *testing.T) {
for i := 0; i < 10000; i++ {
m["a"] = 1
}
})
// 读 map
t.Run("read", func(t *testing.T) {
for i := 0; i < 10000; i++ {
_ = m["a"]
}
})
}
但是我们的期望是,上面的单测不通过,该如何解决呢?
testing.T
有一个 Parallel
方法,它表示当前测试会和其他测试并行运行。 如果参数有-test.count
或-test.cpu
,一个测试可能运行多次,同个测试的多个运行实例,不会并行运行。
我们给上面的单测,加上t.Parallel()
:
func TestConcurrent(t *testing.T) {
var m = map[string]int{}
t.Run("write", func(t *testing.T) {
// 加上并行
t.Parallel()
for i := 0; i < 10000; i++ {
m["a"] = 1
}
})
t.Run("read", func(t *testing.T) {
// 加上并行
t.Parallel()
for i := 0; i < 10000; i++ {
_ = m["a"]
}
})
}
这次执行就会报错:
fatal error: concurrent map read and map write
支持并发的 Map
让 Map 支持并发读写并不麻烦,常见的做法有:
操作 map 的时候,加上读写锁
sync.RWMutex
;使用 sync.Map。
sync.RWMutex
大家用得可能比较多。这里简单给个demo。
sync.RWMutex
我们给上面的单测加上锁,这次运行就能通过了。
func TestConcurrent(t *testing.T) {
var m = map[string]int{}
// 定义锁,零值就可以使用
var mu sync.RWMutex
t.Run("write", func(t *testing.T) {
t.Parallel()
for i := 0; i < 10000; i++ {
// 锁
mu.Lock()
m["a"] = 1
// 解锁
mu.Unlock()
}
})
t.Run("read", func(t *testing.T) {
t.Parallel()
for i := 0; i < 10000; i++ {
// 锁
mu.Lock()
_ = m["a"]
// 解锁
mu.Unlock()
}
})
}
本文的重点介绍一下Go标准库自带的,支持并发读写的 map:sync.Map
。
sync.Map
sync.Map 就是线程安全版的 map[interface{}]interface{}
,零值可以直接使用,值不能复制。它主要用于以下场景:
当同一个 key 的值,写少读多的时候;
但多个 goroutines 读写或修改一系列不同的key的时候。
上面两种场景中,比起带Mutex
(或RWMutex
)的map,sync.Map 会大大减少锁的竞争。
sync.Map 提供的方法不多,这里列出一些。注意的是,any 是 go 1.18 中 interface{}的别名。
Store,设置 key-value。
func (m *Map) Store(key, value any)
Load, 根据 key 读取 value。
func (m *Map) Load(key any) (value any, ok bool)
Delete,删除某个key。
func (m *Map) Delete(key any)
Range,遍历所有key, 如果f
返回false,会停止遍历。
func (m *Map) Range(f func(key, value any) bool)
还有 LoadAndDelete(读后删除)、LoadOrStore(读key,不存在时设置)。
我们给上面的单测,使用sync.Map
,测试也可以通过。
func TestConcurrent(t *testing.T) {
// 可以使用零值
var m sync.Map
t.Run("write", func(t *testing.T) {
t.Parallel()
for i := 0; i < 10000; i++ {
// 写
m.Store("a", 1)
}
})
t.Run("read", func(t *testing.T) {
t.Parallel()
for i := 0; i < 10000; i++ {
// 读
v, ok := m.Load("a")
if ok {
_ = v.(int)
}
}
})
}
参考
pkg.go.dev/sync#Map
来源:https://juejin.cn/post/7174771881911222327
猜你喜欢
- 一、概述SQL SERVER2012 之前版本,一般采用GUID或者IDENTITY来作为标示符。在2012中,微软终于增加了 SEQUEN
- 用下列代码即可:<%On error resume nextSet session=Creat
- 一、学习目标:学会利用python的GUI做界面布局手写计算器代码熟悉控件的使用方法优化计算器代码,解决 获取按钮文本 的方法了解lambd
- 基本介绍约束用于确保数据库的数据满足特定的商业规则在mysql中,约束包括:not null,unique,primary key,fore
- 升序import pandas as pdimport numpy as npdata = np.random.randint(low=2,
- 根据本人的学习经验,我总结了以下十点和大家分享: 1)学好python的第一步,就是马上到www.python.org网站上下载一个pyth
- 本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下一.知识点总结1. 卷积神经网络出
- PyQt5 QtChart-散点图QScatterSeries类将数据以散点图显示import sysimport randomfrom P
- 可能由于操作系统不同,或者在安装SQL 2008的时候已经安装SQL其他版本,因此可能会遇到问题,那么这时我们的实际经验和动手测试的能力也是
- 本文实例为大家分享了Python基于OpenCV实现人脸检测,并保存的具体代码,供大家参考,具体内容如下安装opencv如果安装了pip的话
- 系统:ubuntu18.04 x64GitHub:https://github.com/xingjidemimi/DjangoAPI.git
- Mysql慢查询解释MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过
- Base64编码的深入认识与理解 之前在很多业务中都有见过或者用到过Base64编码,但一直一知半解,没有对它有一个深入的认识和
- 开发环境说明:Python 35Pytorch 0.2CPU/GPU均可1、LSTM简介人类在进行学习时,往往不总是零开始,学习物理你会有数
- 将Django与其他现有认证系统的用户名和密码或者认证方法进行整合是可以办到的。例如,你所在的公司也许已经安装了LDAP,并且为每一个员工都
- 我就废话不多说了,直接上代码吧!import mathimport numpy as npimport matplotlib.pyplot
- 无法打开用户默认数据库,登录失败,这也是SQL Server使用者熟悉的问题之一。在使用企业管理器、查询分析器、各类工具和应用软件的时候,只
- GO的条件变量一、条件变量与互斥锁条件变量是基于互斥锁的,它必须基于互斥锁才能发挥作用;条件变量并不是用来保护临界区和共享资源的,它是用来协
- 这可能是一个非常简单的问题,但是今天花一点点时间把这个简单的问题在说清晰一点,相信大家对CSS的学习和认识会很有帮助,强化一些概念的东西,对
- 对以下数据画图结果图不显示,修改过程如下df3 = {'chinese':109, 'American':8