并发安全且高效的sync.Map详情 有必要了解一下
Go Map在并发读写场景下经常会遇到panic的情况。为什么呢?因为在并发读写的情况下,map 里的数据会被写乱,map非并发安全的。
要解决并发读写map问题,现在主要有两种解决方法:
1:一般采用map + 互斥锁 或者 读写锁的方式实现。
2:用标准库 sync.Map (Go 1.9 及之后),这是并发安全的Map。主要适用于读多写少的场景。
一、sync.Map是什么?
官方接口文档地址:https://pkg.go.dev/sync#Map
sync.map 是线性安全的,读取、插入、删除都保持常数级的时间复杂度。
sync.map 的零值是有效的,并且零值是一个空的 map。在第一次使用之后,不允许被拷贝。
二、sync.Map有什么用?
使用sync.map,多个 goroutine 的并发使用是安全的,对 map 的读写,不需要加锁。并且它通过空间换时间的方式,使用 read 和 dirty 两个 map 来进行读写分离,降低锁时间来提高效率。使用 sync.Map 类型可以大大减少锁的争夺。
sync.map 适用于读多写少的场景。对于写多的场景,会导致 read map 缓存失效,需要加锁,导致冲突变多;而且由于未命中 read map 次数过多,导致 dirty map 提升为 read map,这是一个 O(N) 的操作,会进一步降低性能。
三、sync.Map使用示例代码
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 1. 写入
m.Store("xiao6", 25)
m.Store("superl", 26)
// 2. 读取
age, _ := m.Load("xiao6")
fmt.Println(age.(int))
// 3. 遍历
m.Range(func(key, value interface{}) bool {
name := key.(string)
age := value.(int)
fmt.Println(name, age)
return true
})
// 4. 删除
m.Delete("xiao6")
age, ok := m.Load("xiao6")
fmt.Println(age, ok)
// 5. 读取或写入
m.LoadOrStore("superl", 100)
age, _ = m.Load("superl")
fmt.Println(age)
}
第 1 步,写入两个 k-v 对;
第 2 步,使用 Load 方法读取其中的一个 key;
第 3 步,遍历所有的 k-v 对,并打印出来;
第 4 步,删除其中的一个 key,再读这个 key,得到的就是 nil;
第 5 步,使用 LoadOrStore,尝试读取或写入key,因为这个 key 已经存在,因此写入不成功,并且读出原值。
四、关于sync.Map与Map的性能对比
sync.Map的性能高体现在读操作远多于写操作的时候。
极端情况下,只有读操作时,是普通map的性能的44.3倍。
反过来,如果是全写,没有读,那么sync.Map还不如加普通map+mutex锁呢。只有普通map性能的一半。
建议使用sync.Map时一定要考虑读定比例。当写操作只占总操作的<=1/10的时候,使用sync.Map性能会明显高很多。
如有转载,请注明出处!《并发安全且高效的sync.Map详情 有必要了解一下》的原文地址:http://www.xiao6.net/post/249.html