Go 语言中的 map 在并发状况下,只读是线程平安的,同时读写是线程不平安的。
如果想实现并发线程平安有两种办法:
- map 加互斥锁或读写锁
- 规范库 sync.map(Go1.19+ 新个性)
sync.map 源码
https://github.com/golang/go/…
sync.map 实现原理及优化
- 利用 map 只读不必锁,通过冗余 read 和 dirty 两个字段将读写拆散,读的数据存在只读字段 read 上,将最新写入的数据则存在 dirty 字段上,只在 dirty 读写上加锁,进步程序只读效率。
- 读取时会先查问 read,不存在再查问 dirty,写入时则只写入 dirty
- 读取 read 并不需要加锁,而读或写 dirty 都须要加锁
- 另外有 misses 字段来统计 read 被穿透的次数(被穿透指须要读 dirty 的状况),超过肯定次数则将 dirty 数据同步到 read 上
- 对于删除数据则间接通过标记来提早删除
具体数据结构可参考:
https://blog.csdn.net/u010853…
https://www.haohongfan.com/do…
sync.map 应用场景
map+Mutex:通过 Mutex 互斥锁来实现多个 goroutine 对 map 的串行化拜访,读写都须要通过 Mutex 加锁和开释锁,实用于读写比靠近的场景
map+RWMutex:通过 RWMutex 来实现对 map 的读写进行读写锁拆散加锁,从而实现读的并发性能进步,同 Mutex 相比实用于读多写少的场景
sync.Map:底层通拆散读写 map 和原子指令来实现读的近似无锁,并通过提早更新的形式来保障读的无锁化。读多批改少,元素减少删除频率不高的状况,在大多数状况下代替上述两种实现
sync.map 应用办法如下
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 增加一个元素
m.Store(1, "a")
m.Store("a", 2)
// 读取一个元素
fmt.Println(m.Load(1)) //a true
fmt.Println(m.Load("a")) //2 true
// 读取不存在的元素
fmt.Println(m.Load(2)) //<nil> false
// 存在就返回,不存在就插入
fmt.Println(m.LoadOrStore("3", 33)) //33 false
fmt.Println(m.Load("3")) //33 true
// 如果存在的话,同时删除这个 key
fmt.Println(m.LoadAndDelete("3")) // 33 true
// 删除某个元素
m.Delete("3")
// 遍历所有 sync.Map 中的键值对
m.Range(func(k, v interface{}) bool {fmt.Println("iterate:", k, v)
return true
})
}
通过以上示例能够看到 sync.map 具备以下个性:
- 能够存储不同的数据类型在一起,这有别于 map 只能存储申明好的数据类型,且雷同的。
- 毋庸初始化,间接申明即可。
- sync.Map 不能应用 map 的形式进行取值和设置等操作,而是应用 sync.Map 的办法进行调用,Store 示意存储,Load 示意获取,Delete 示意删除
- 减少了两个非凡办法 LoadOrStore 存在就返回,不存在就插入,LoadAndDelete 如果存在的话,同时删除这个 key
- 应用 Range 配合一个回调函数进行遍历操作,通过回调函数返回外部遍历进去的值,Range 参数中回调函数的返回值在须要持续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
参考资料
https://juejin.cn/post/684490…
https://pkg.go.dev/sync#Map
https://blog.csdn.net/u010853…
https://medium.com/@deckarep/…
https://www.haohongfan.com/do…