1. 并发不安全的 map

Go 语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

换句话说,在同一时间段内,让不同 goroutine 中的代码,对同一个字典进行读写操作是不安全的。字典值本身可能会因这些操作而产生混乱,相关的程序也可能会因此发生不可预知的问题。

package mainimport ("fmt""time"
)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)}
}

执行一段时间后会报错:

fatal error: concurrent map read and map write

2. 并发安全字典 sync.Map

需要并发读写时,一般的做法是加锁,但这样性能并不高, Go 语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Mapsync.Mapmap 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。

sync.Map 有以下特性:

  • 无须初始化,直接声明即可。
  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用, Store 表示存储, Load 表示获取, Delete 表示删除。
  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值, Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true ,终止迭代遍历时,返回 false
  • Store:存储一对 key-value 值。
  • Load:根据 key 获取对应的 value 值,并且可以判断 key 是否存在。

  • LoadOrStore:如果 key 对应的 value 存在,则返回该 value;如果不存在,存储相应的 value。

  • Delete:删除一个 key-value 键值对。

  • Range:循环迭代 sync.Map,效果与 for range 一样。

它所有的方法涉及的键和值的类型都是 interface{} ,也就是空接口,这意味着可以包罗万象。所以,我们必须在程序中自行保证它的键类型和值类型的正确性。

并发安全的 sync.Map 演示代码如下:

package mainimport ("fmt""sync"
)func main() {// 声明 scene,类型为 sync.Map,注意,sync.Map 不能使用 make 创建。var scene sync.Map// 将键值对保存到sync.Map// sync.Map 将键和值以 interface{} 类型进行保存。scene.Store("greece", 97)scene.Store("london", 100)scene.Store("egypt", 200)// 从sync.Map中根据键取值fmt.Println(scene.Load("london"))// 根据键删除对应的键值对scene.Delete("london")// 遍历所有sync.Map中的键值对// 遍历需要提供一个匿名函数,参数为 k、v,类型为 interface{},// 每次 Range() 在遍历一个元素时,都会调用这个匿名函数把结果返回。scene.Range(func(k, v interface{}) bool {fmt.Println("iterate:", k, v)return true})}

输出结果:

100 true
iterate: greece 97
iterate: egypt 200

sync.Map 键的实际类型不能是函数类型、字典类型和切片类型。由于这些键值的实际类型只有在程序运行期间才能够确定,所以 Go 语言编译器是无法在编译期对它们进行检查的,不正确的键值实际类型肯定会引发 panic

3. 如何保证并发安全字典中的键和值的类型正确性?

3.1 让并发安全字典只能存储某个特定类型的键。

比如指定这里的键只能是 int 类型的,或者只能是字符串,又或是某类结构体。一旦完全确定了键的类型,你就可以在进行存、取、删操作的时候,使用类型断言表达式去对键的类型做检查了。

一般情况下,这种检查并不繁琐。而且,你要是把并发安全字典封装在一个结构体类型里面,那就更加方便了。你这时完全可以让 Go 语言编译器帮助你做类型检查。

package mainimport ("fmt""sync"
)// IntStrMap 代表键类型为int、值类型为string的并发安全字典。
type IntStrMap struct {m sync.Map
}func (iMap *IntStrMap) Delete(key int) {iMap.m.Delete(key)
}func (iMap *IntStrMap) Load(key int) (value string, ok bool) {v, ok := iMap.m.Load(key)if v != nil {value = v.(string)}return
}func (iMap *IntStrMap) LoadOrStore(key int, value string) (actual string, loaded bool) {a, loaded := iMap.m.LoadOrStore(key, value)actual = a.(string)return
}func (iMap *IntStrMap) Range(f func(key int, value string) bool) {f1 := func(key, value interface{}) bool {return f(key.(int), value.(string))}iMap.m.Range(f1)
}func (iMap *IntStrMap) Store(key int, value string) {iMap.m.Store(key, value)
}// pairs 代表测试用的键值对列表。
var pairs = []struct {k intv string
}{{k: 1, v: "a"},{k: 2, v: "b"},{k: 3, v: "c"},{k: 4, v: "d"},
}func main() {var iMap IntStrMapiMap.Store(pairs[0].k, pairs[0].v)iMap.Store(pairs[1].k, pairs[1].v)iMap.Store(pairs[2].k, pairs[2].v)fmt.Println("[Three pairs have been stored in the IntStrMap instance]")iMap.Range(func(key int, value string) bool {fmt.Printf("The result of an iteration in Range: %d, %s\n", key, value)return true})k0 := pairs[0].kv0, ok := iMap.Load(k0)fmt.Printf("The result of Load: %v, %v (key: %v)\n", v0, ok, k0)k3 := pairs[3].kv3, ok := iMap.Load(k3)fmt.Printf("The result of Load: %v, %v (key: %v)\n", v3, ok, k3)k2, v2 := pairs[2].k, pairs[2].vactual2, loaded2 := iMap.LoadOrStore(k2, v2)fmt.Printf("The result of LoadOrStore: %v, %v (key: %v, value: %v)\n",actual2, loaded2, k2, v2)v3 = pairs[3].vactual3, loaded3 := iMap.LoadOrStore(k3, v3)fmt.Printf("The result of LoadOrStore: %v, %v (key: %v, value: %v)\n",actual3, loaded3, k3, v3)k1 := pairs[1].kiMap.Delete(k1)fmt.Printf("[The pair with the key of %v has been removed from the IntStrMap instance]\n", k1)v1, ok := iMap.Load(k1)fmt.Printf("The result of Load: %v, %v (key: %v)\n", v1, ok, k1)v1 = pairs[1].vactual1, loaded1 := iMap.LoadOrStore(k1, v1)fmt.Printf("The result of LoadOrStore: %v, %v (key: %v, value: %v)\n",actual1, loaded1, k1, v1)iMap.Range(func(key int, value string) bool {fmt.Printf("The result of an iteration in Range: %d, %s\n", key, value)return true})fmt.Println()
}

sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量。

sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。

Go 学习笔记(67)— Go 并发安全字典 sync.Map相关推荐

  1. Java学习笔记22:并发(2)

    Java学习笔记22:并发(2) 图源:PHP中文网 终止任务 终止线程有一种非常简单的方式:设置一个多线程共享的标记位,子线程用轮询的方式检查这个标记位,如果该标记位显示取消状态,就让子线程退出执行 ...

  2. 学习笔记:Java 并发编程①_基础知识入门

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 视频下载: ...

  3. 学习笔记:Java 并发编程②_管程

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...

  4. 学习笔记:Java 并发编程④_无锁

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...

  5. 学习笔记:Java 并发编程⑥_并发工具_JUC

    若文章内容或图片失效,请留言反馈. 部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 视频链接:https://www.bilibili.com/video/av81461839 配套资料: ...

  6. 数据结构与算法 学习笔记(8):字典、集合、哈希表

    数据结构与算法 学习笔记(8):字典.集合.哈希表 本次文章记录的是和字典.集合.哈希表等数据结构相关的LeetCode算法题(题号与LeetCode对应),包括其构造和使用,针对每一题或一类题给出了 ...

  7. pythondd_python学习笔记(五)之字典2-阿里云开发者社区

    python学习笔记(五)之字典2 编程实战中经常用到 实例1:copy >> ad = {"name":"wtf","hig" ...

  8. c++ map 多线程同时更新值 崩溃_深入理解并发安全的 sync.Map

    golang中内置了map关键字,但是它是非线程安全的.从go 1.9开始,标准库加入了sync.Map,提供用于并发安全的map. 普通map的并发问题 map的并发读写代码 func main() ...

  9. Python学习笔记:1.2.8 字典

    本文是学习齐伟老师的<python全栈工程师>课程的笔记,欢迎学习交流.同时感谢齐老师的精彩传授! 一.课程目标 掌握字典的定义方法 掌握字典的基本操作 掌握字典的方法 二.详情解读 1. ...

最新文章

  1. 在CentOS 6.3 64bit上安装MySQL for python模块
  2. SAP 调用外部系统
  3. 体验 vue cli 3.0
  4. 主叫号码未显示怎么设置_微信未授权抖音,应该怎么设置?
  5. 软件测试 学习之路 html基础
  6. python中宽度是什么意思_在Python中,高度还是宽度优先?
  7. Codeforces Educational Codeforces Round 3 D. Gadgets for dollars and pounds 二分,贪心
  8. asp oracle 分页显示,asp + oracle 分页方法(不用存储过程)
  9. 理解Ruby的4种闭包:blocks, Procs, lambdas 和 Methods
  10. 更改eclipse字体
  11. uni-app项目利用HBuilder X工具使用命令一键自动编译导出APP资源
  12. 起底 ARM:留给中国队的时间不多了
  13. 定积分之几种常见曲线
  14. 【深度学习】注意力机制
  15. Pytorch——XLNet 预训练模型及命名实体识别
  16. R语言计量(一):一元线性回归与多元线性回归分析
  17. 当在浏览器地址栏输入一个URL后回车,将会发生的事情?
  18. c#语言中怎么实现延时功能,timer-在C#中创建“一次运行”延时功能的最佳方法...
  19. js解决服务器和客户端存在时间差的问题
  20. windows cmd bat获取局域网本机ip

热门文章

  1. 伦理困境:人工智能浪潮与“AI威胁论”之争
  2. 2022-2028年中国抗肿瘤药物行业市场分析调研及发展趋势研究报告
  3. c++中的vector的常见使用
  4. eclipse运行maven web项目
  5. NLP自然语言处理工具小结
  6. sklearn数据处理_one_hot
  7. torch.nn.functional.cross_entropy.ignore_index
  8. 八种基本类型的包装类你真的懂了?
  9. LeetCode简单题之汇总区间
  10. LeetCode简单题之唯一元素的和