当前位置: 首页 > news >正文

Go基础学习07-map注意事项;多协程对map的资源竞争;sync.Mutex避免竟态条件

文章目录

  • Go中map使用以及注意事项
  • map使用时的并发安全问题

Go中map使用以及注意事项

Go语言中map使用简单示例:

func main() {var mp map[string]int// mp := map[string]int{}val, ok := mp["one"]if ok {fmt.Println(val)} else {fmt.Println(val)}mp["two"] = 10
}

思考一:Go语言的map的键类型为什么不能是函数类型、字典类型、切片类型

相信学习过其他语言比如Java语言的朋友对于Java中的HashMap都比较熟悉,对于所有键值对元素的数据结构来说,存储过程先计算key的哈希值,随后使用哈希值定位到对应的bucket,将key和value作为元素存储到对应的bucket中。在这个过程中可能会发生哈希碰撞,在Java中对于哈希碰撞常见的解决方案有拉链法,由于哈希碰撞的存在对于哈希表的键的要求,不仅仅能够计算哈希值,同时能够对键进行判等操作(解决哈希碰撞时key重复的问题)。对于Go语言的map的实现仍然如此,需要考虑到哈希碰撞的出现,所以就要求key必须能够进行判等操作
如果go的map的键为上述三种类型,在运行时就会发生panic。

思考二:Go语言中应该优先考虑那些类型作为map的键的类型

只从性能的角度出发,而不考虑上下文时:在map中比较耗时的操作主要出现在连个地方:

  • 把键值转换为哈希值
  • 把要查找的键值与哈希桶中的键值做对比

对于所有的基本类型、指针类型,以及数组类型、结构体类型和接口类型,Go 语言都有一套算法与之对应。这套算法中就包含了哈希和判等

  • 以求哈希的操作为例,宽度越小的类型速度通常越快。对于布尔类型、整数类型、浮点数类型、复数类型和指针类型来说都是如此。对于字符串类型,由于它的宽度是不定的,所以要看它的值的具体长度,长度越短求哈希越快。
  • 类型的宽度是指它的单个值需要占用的字节数。比如,bool、int8和uint8类型的一个值需要占用的字节数都是1,因此这些类型的宽度就都是1。
  • 高级类型如:数组类型的值求哈希实际上是依次求得它的每个元素的哈希值并进行合并,所以速度就取决于它的元素类型以及它的长度。细则同上。
  • 结构体类型:哈希实际上就是对它的所有字段值求哈希并进行合并,关键在于它的各个字段的类型以及字段的数量。

优先选用数值类型和指针类型,通常情况下类型的宽度越小越好。如果非要选择字符串类型的话,最好对键值的长度进行额外的约束。

map使用时的并发安全问题

考虑如下代码:

func main() {m := map[int]string{1: "haha",}go read(m)time.Sleep(time.Second)go write(m)time.Sleep(30 * time.Second)fmt.Println(m)
}
func read(m map[int]string) {for {_ = m[1]time.Sleep(1)}
}
func write(m map[int]string) {for {m[1] = "write"time.Sleep(1)}
}

开启两个协程并发对map进行读写操作,上述代码运行直接报如下错误:

goroutine 6 [running]:
main.read(0x0?)/home/wt/Backend/go/goprojects/src/golearndetail/go36/learn09/demo02.go:20 +0x2d
created by main.main/home/wt/Backend/go/goprojects/src/golearndetail/go36/learn09/demo02.go:12 +0xa5goroutine 1 [sleep]:
time.Sleep(0x6fc23ac00)/usr/local/go/src/runtime/time.go:195 +0x135
main.main()/home/wt/Backend/go/goprojects/src/golearndetail/go36/learn09/demo02.go:15 +0xfbgoroutine 7 [sleep]:
time.Sleep(0x1)/usr/local/go/src/runtime/time.go:195 +0x135
main.write(0x0?)/home/wt/Backend/go/goprojects/src/golearndetail/go36/learn09/demo02.go:27 +0x25
created by main.main/home/wt/Backend/go/goprojects/src/golearndetail/go36/learn09/demo02.go:14 +0xecProcess finished with the exit code 2

对于map的读写是非原子性操作,存在资源竞争,不是现成安全的,可以使用如下命令检测:

go run race ...

为了将非并发安全的读取操作更改为并发安全的,可以引入sync.Mutex,在读、写操作前进行加锁操作;操作后进行解锁,保证并发安全。

func main() {m := map[int]string{1: "haha",}var mutex sync.Mutex // 创建一个互斥锁// 启动读协程go read(m, &mutex)// 等待一秒钟,确保读协程已经开始运行time.Sleep(time.Second)// 启动写协程go write(m, &mutex)// 等待足够长的时间,以便读写协程可以运行time.Sleep(30 * time.Second)// 打印最终的mapfmt.Println(m)
}func read(m map[int]string, mutex *sync.Mutex) {for {mutex.Lock() // 在读取之前加锁value := m[1]mutex.Unlock() // 读取完毕后解锁fmt.Println("Read:", value)time.Sleep(1 * time.Second)}
}func write(m map[int]string, mutex *sync.Mutex) {for {mutex.Lock() // 在写入之前加锁m[1] = "write"mutex.Unlock() // 写入完毕后解锁fmt.Println("Write:", m[1])time.Sleep(1 * time.Second)}
}

后续会将sync.Mutex的底层原理进行总结展示。

相关文章:

  • 数据清洗第1篇章 - 处理缺失值和重复值
  • 代码训练营 day17|LeetCode 235,LeetCode 701,LeetCode 450
  • [每周一更]-(第117期):硬盘分区表类型:MBR和GPT区别
  • 开源节流计划:数字化学习创业提升
  • 【2025】基于Spring Boot的智慧农业小程序(源码+文档+调试+答疑)
  • 【vs code(cursor) ssh连不上服务器(2)】但是 Terminal 可以连上,问题解决 ✅
  • windows 桌面采集音频
  • 为什么 Kubernetes 的设计中有 Pod,而不是直接使用容器
  • sysbench 命令:跨平台的基准测试工具
  • 算法训练营打卡Day19
  • 66 使用注意力机制的seq2seq_by《李沐:动手学深度学习v2》pytorch版
  • python如何判断图片路径是否存在
  • Ubuntu网卡配置
  • 微信小程序 图片的上传
  • KKT实际运用 -MATLAB
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • 03Go 类型总结
  • Cumulo 的 ClojureScript 模块已经成型
  • extract-text-webpack-plugin用法
  • Java-详解HashMap
  • Mocha测试初探
  • NSTimer学习笔记
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Python语法速览与机器学习开发环境搭建
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • 闭包,sync使用细节
  • 大数据与云计算学习:数据分析(二)
  • 反思总结然后整装待发
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 开源地图数据可视化库——mapnik
  • 扑朔迷离的属性和特性【彻底弄清】
  • 如何设计一个比特币钱包服务
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 一些关于Rust在2019年的思考
  • 1.Ext JS 建立web开发工程
  • ​iOS安全加固方法及实现
  • #### go map 底层结构 ####
  • #define与typedef区别
  • #微信小程序:微信小程序常见的配置传旨
  • (web自动化测试+python)1
  • (二)换源+apt-get基础配置+搜狗拼音
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • (四)图像的%2线性拉伸
  • (一)面试需要掌握的技巧
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)程序员疫苗:代码注入
  • .chm格式文件如何阅读
  • .equals()到底是什么意思?
  • .Net 执行Linux下多行shell命令方法
  • .NET/C# 检测电脑上安装的 .NET Framework 的版本
  • .net6 当连接用户的shell断掉后,dotnet会自动关闭,达不到长期运行的效果。.NET 进程守护