usage
package main
import (
"sync"
"fmt"
)
func main() {
//开箱即用
var sm sync.Map
//store 方法,添加元素
sm.Store(1,"a")
//Load 方法,获得value
if v,ok:=sm.Load(1);ok{
fmt.Println(v)
}
//LoadOrStore方法,获取或者保存
//参数是一对key:value,如果该key存在且没有被标记删除则返回原先的value(不更新)和true
//不存在则store,返回该value 和false
if vv,ok:=sm.LoadOrStore(1,"c");ok{
fmt.Println(vv)
}
if vv,ok:=sm.LoadOrStore(2,"c");!ok{
fmt.Println(vv)
}
//遍历该map,参数是个函数,该函数参的两个参数是遍历获得的key和value,返回一个bool值,
//当返回false时,遍历立刻结束。
sm.Range(func(k,v interface{})bool{
fmt.Printf("%v:%v \n",k,v)
return true
})
}
简介
- sync.Map 和普通的 map[interface{}]interfae{} 使用起来很像,但是保证了线程安全。
- sync.Map 是某些场景下专用的,比如:
- 一个 key 只写入一次,但是需要被多次读取。(读多写少)
- 多个 goroutine 同时读取、写入和覆盖一系列不想交的 key 时。
在以上两类情况下,和单独加读写锁的内置 map 相比,sync.Map 可以有效的减少锁竞争。
设计思想
通过冗余的两个 map,牺牲空间换时间。
- 两个 map 的 value 类型都为 *interface{},所以可以同时进行修改。
- read map 是原子类型的,自动更新,用于读操作,无需加锁;dirty map 含有最新数据并且所有操作都要使用 mutex。
- 读取数据首先在 read 上读,如果没读到就去 dirty 上读,记录一个未命中数,当未命中数到达一定数量时,使用 dirty 的数据更新 read。
为什么 read map 是原子类型的?
因为如果并发的
稍加思索可以得出这样设计的好处是:
- 读操作无需获取和释放锁;
-
实现
Map
type Map struct {
mu Mutex
read atomic.Value // 只读类型,自动更新
// dirty 的使用需要加锁
dirty map[interface{}]*entry
// 从 Map 读取 entry 时,如果 read 未命中将会从 dirty 中读取,此时 misses++
// 当 misses 等于 dirty 的长度时,避免命中率过低,dirty 将升级为 read
misses int
}
readOnly
// readOnly 自动存储 dirty map 更新的数据,不能手动改变
type readOnly struct {
m map[interface{}]*entry
amended bool // 如果 dirty 与 read 中的不一致则为 true
}
entry
type entry struct {
p unsafe.Pointer // map 的 value 为指针类型
}
load
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
// 从 read 中读取只读 map
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
// 如果 read 中没有,且 read 和 dirty 不一致
if !ok && read.amended {
m.mu.Lock()
// 双检查,避免获取锁的时候 dirty 被其他线程升级为 read
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
m.missLocked() // miss++,到达限额 dirty 提升为 read
}
m.mu.Unlock()
}
if !ok {
return nil, false
}
return e.load()
}
dirty 升级为 read 过程
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
// misses 长度增长到等于 dirty 时,将 read 中的内容原子更新为 dirty
m.read.Store(readOnly{m: m.dirty})
m.dirty = nil
m.misses = 0
}