go语言的原生map引发的一个坑
时间:2022-07-25
本文章向大家介绍go语言的原生map引发的一个坑,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
go语言原生map引发的一个坑
总所周知,go语言原生的map并不是并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加一个锁。
年初写项目的时候,刚接触go语言,冒冒失失的就写出了类似下面这样的代码:
package problem
import (
"fmt"
"sync"
)
type dict struct {
m map[int]string
lock sync.RWMutex
}
func NewDict() *dict {
return &dict{m: map[int]string{}}
}
func (d dict) Set(key int, value string) {
d.lock.Lock()
defer d.lock.Unlock()
d.m[key] = value
}
func (d dict) Get(key int) (string, bool) {
d.lock.RLock()
defer d.lock.RUnlock()
value, ok := d.m[key]
return value, ok
}
func (d dict) Del(key int) {
d.lock.Lock()
defer d.lock.Unlock()
delete(d.m, key)
}
当时也没有用go语言的race检测,就直接用了。相信学过go语言的人一眼就能看出这个代码的问题,是的,是非常基础的指针接收者和值接收者的问题。
指针接收者在协程并发读写的时候,确实只有一个dict指针指向那个dict地址,然后lock锁也是最初定义的那个锁,所以不会出现concurrent map read and write
的问题。
但是如果是值接收者,协程在并发读写的时候,实际是对这个dict对象的一个拷贝。
这时候map在go语言里,创建出来的时候就是往外传递的一个指向map的指针,拷贝出来的指针,是指向同一个map,所以常规的插入查询删除数据是不会出现异常。只不过是线程不安全的。因为拷贝的lock是一个新锁,这样每个协程有一个锁,虽然写的有个lock但是就跟没加锁一样。
加上指针打印变量地址就很清楚了:
package problem
import (
"fmt"
"sync"
)
type dict struct {
m map[int]string
lock sync.RWMutex
}
func NewDict() *dict {
return &dict{m: map[int]string{}}
}
func (d dict) Set(key int, value string) {
d.lock.Lock()
defer d.lock.Unlock()
fmt.Printf("Set %p, %pn", &d.lock, d.m)
d.m[key] = value
}
func (d dict) Get(key int) (string, bool) {
d.lock.RLock()
defer d.lock.RUnlock()
fmt.Printf("Get %p, %pn", &d.lock, d.m)
value, ok := d.m[key]
return value, ok
}
func (d dict) Del(key int) {
d.lock.Lock()
defer d.lock.Unlock()
fmt.Printf("Del %p, %pn", &d.lock, d.m)
delete(d.m, key)
}
func main() {
d := problem.NewDict()
go func() {
for {
d.Set(1, "one")
d.Del(1)
}
}()
go func() {
for {
fmt.Println(d.Get(1))
}
}()
select {}
}
加race检测运行结果:
Del 0xc00041dac8, 0xc00006c300
Set 0xc00041dae8, 0xc00006c300
Del 0xc00041db08, 0xc00006c300
Set 0xc00041db28, 0xc00006c300
Del 0xc00041db48, 0xc00006c300
Set 0xc00041db68, 0xc00006c300
Del 0xc00041db88, 0xc00006c300
Get 0xc000416008, 0xc00006c300
Set 0xc00041dba8, 0xc00006c300
Del 0xc00041dbc8, 0xc00006c300
fatal error: concurrent map read and map write
可以看出每一次操作,锁的地址都发生了改变,是新的变量。
但是改成指针接收者之后:
one true
Del 0xc000004468, 0xc00006c300
Get 0xc000004468, 0xc00006c300
false
Set 0xc000004468, 0xc00006c300
Get 0xc000004468, 0xc00006c300
one true
Del 0xc000004468, 0xc00006c300
Get 0xc000004468, 0xc00006c300
false
Set 0xc000004468, 0xc00006c300
Get 0xc000004468, 0xc00006c300
可以看出每一次操作,锁的地址都是同一个。
虽然解决了并发的问题,但是这种加一个大锁锁住整个map的方式,在map存储的数据很多的时候,性能肯定不高,在go1.9引入sync.Map之前,比较流行的做法就是使用分段锁。具体项目中使用哪种,也是根据实际需求决定。
后来就把项目中的map改用分段锁来保证线程安全,分段锁:color{#00BFFF}{concurrentMap}
- 让WordPress 在RSS 中Feed 输出支持“More”标签
- WordPress文章版权保护:复制文字自动添加版权信息
- 替换WordPress 自带默认的 jQuery库, jQuery库页脚加载
- Enterprise Library 4 数据访问应用程序块
- 替换EnterPrise Library 4.0 缓存应用程序块的CacheManager
- Enterprise Library 4.0缓存应用程序块
- 通过.htaccess 让WordPress 的上传文件夹更安全
- asp.net 性能调较
- 零基础学习大数据,搭建Hadoop处理环境
- 为你的WordPress 博客开启两步验证功能(技术支持:谷歌)
- 为你的WordPress 博客开启两步验证功能(技术支持:谷歌)
- WordPress 注册页面显示自定义提示信息
- Windows Server 2008密码重设盘
- Dynamite动态排序库
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Ubuntu18.04系统安装、配置Redis及phpredis扩展操作详解
- Android电话拨号器实例详解
- Android获取App内存使用情况的方法
- linux后台运行的几种方式(小结)
- Android 实现ListView的点击变色的实例
- Android播放音乐案例分享
- linux crm部署代码详解
- Android自定义WaveProgressView实现水波纹加载需求
- CentOS8下的root密码快速修改方法
- Android开发之自定义刮刮卡实现代码
- Android ScrollView无法填充满屏幕的解决办法
- Android 监听屏幕是否锁屏的实例代码
- Android实现水波纹控件的方法
- Android中GridView布局实现整体居中方法示例
- Android SharedPreferences四种操作模式使用详解