我们经常会碰到并发读写 map 而造成 panic 的情况,因为在并发读写的情况下,map 里的数据会被混乱(只读是线程安全的,同时读写是线程不安全的),对 map 并发读写时需要加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
sync.Map 特性:
无须初始化,直接声明即可。
对键值对的类型没有限制,同一个sync.map中可存储多种类型的键值。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
sync.Map 优点:
是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度。
零值是有效的,并且零值是一个空的 map。在第一次使用之后,不允许被拷贝。
通过读写分离,降低锁时间来提高效率,适用于读多写少的场景。
package main
import (
"sync"
"fmt"
)
func main() {
var s sync.Map
// 将键值对保存到sync.Map
s.Store("k", "1")
s.Store(1, 100)
// 键值不存在则创建
s.LoadOrStore("a", "2")
// 从sync.Map中根据键取值
fmt.Println(s.Load("k"))
// 根据键删除对应的键值对
s.Delete("k")
// 遍历所有sync.Map中的键值对
s.Range(func(k, v interface{}) bool {
fmt.Println("iterate:", k, v)
return true
})
}
sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。