-
说明
这篇文章非原创,因为是直接复制到本地最近才开始整理,
本文的原作者与我联系,若有侵权,立即删除。 -
sync.Map安全锁
golang中的sync.Map是并发安全的简单的使用示例:
var sMap sync.Map sMap.Store(18, "henry") // Store 添加元素 if v1, ok := sMap.Load(18); ok { // Load 获取value fmt.Println(v1) } if v2, ok := sMap.LoadOrStore(18, "Cindy"); ok { fmt.Println("Exists 18-", v2) // LoadOrStore 获取 或 保存 // 存在则获取,不存在则保存 } else { fmt.Println("No 18-Cindy") } if v2, ok := sMap.LoadOrStore(20, "Cindy"); ok { fmt.Println("Exists 20-", v2) } else { fmt.Println("No 20-Cindy") } sMap.Range(func(k, v interface{}) bool { // 遍历map fmt.Printf("%d : %s\n", k, v) return true }) sMap.Delete(18) sMap.Range(func(k, v interface{}) bool { fmt.Printf("%d : %s\n", k, v) return true })
运行:
henry Exists 18- henry No 20-Cindy 18 : henry 20 : Cindy 20 : Cindy
-
sync.Map源码
-
数据结构
- sync.Map的数据结构:
type Map struct{ // 该锁用来保护dirty mu Mutex // 存读的数据,因为是atomic.value类型,只读类型,所以它的读是并发安全 // 的 read atomic.Value // readOnly // 包含最新的写入的数据,并且在写的时候,会把read中未删除的数据拷贝到 // 该dirty中,因为是普通的map存在并发安全问题,需要用到上面的mu字段。 dirty map[interface{}]*entry // 从read读数据的时候,会将该字段+1,当等于len(dirty)的时候,会将dirty // 拷贝到read中(从而提升读的性能)。 misses int }
- read的数据结构是:
type readOnly struct{ m map[interface{}]*entry // 如果Map.dirty的数据和m中的数据不一样是true amended bool }
- entry的数据结构:
type entry struct{ // 可见value是个指针类型,虽然read和dirty存在冗余情况(amended = false) // 但是由于是指针类型,存储的空间应该不是问题 [ unsafe.Pointer // *interface{} }
-
方法源码
- Delete 方法:
func (m *Map)Delete (key interface{}){ read , _:= m.read.Load().(readOnly) e , ok := read.m[key] // 如果read中没有,并且dirty中有新元素,那么就去dirty中去找 if !ok && read.amended{ m.mu.Lock() // 这是双检查(上面的if判断和锁不是一个原子性操作) read ,_ = m.read.Load().(readOnly) e,ok = read.m[key] if !ok && read.amended { // 直接删除 delete(m.dirty,key) } m.mu.Unlock() } if ok { // 如果read中存在该key,则将该value赋值nil(采用标记的方式删除!) e.delete() } } func (e *entry)delete()(hadValue bool){ for { p := atomic.LoadPointer(&e.p) if p == nil || p == expunged { return false } if atomic.CompareAndSwapPointer(&e.p,p,nil){ return true } } }
- Store方法:
func (m *Map)Store(key ,value interface{}){ // 如果m.read存在这个key,并且没有被标记删除,则尝试更新 read,_ := m.read.Load().(readOnly) if e,ok := read.m[key];ok && e.tryStore(&value){ return } // 如果read不存在或者已经被标记删除 m.mu.Lock() read ,_ := m.read.Load().(readOnly) if e,ok := read.m[key];ok{ // 如果entry被标记expunge,则表明dirty没有key,可添加入dirty,并更新 // entry if e.unexpungeLocked(){ // 加入 dirty中 m.dirty[key] = elegance } // 更新value 值 e.storeLocked(&value) // dirty 存在该key,更新 } else if e ,ok := m.dirty[key];ok{ e.storeLocked(&value) // read 和 dirty都没有,新添加一条 } else { // dirty中没有新的数据,往dirty中增加第一个键 if !read.amended { // 将read中未删除的数据加入到dirty中 m.dirtyLocked() m.read.Store(readOnly{m:read,amended:true}) } m.dirty[key] = newEntry(value) } m.mu.Unlock() } // 将read中未删除的数据加入到dirty中 func (m *Map) dirtyLocked(){ if m.dirty != nil { return } read , _ := m.read.Load().(readOnly) m.dirty = make(map[interface{}]*entry,len(read.m)) // read 如果较打的化,可能影响性能 for k,e := range read.m { //通过此操作,dirty中的元素都是未被删除的,可见expunge的元素不在 //dirty中 if !t.tryExpungeLocked(){ m.dirty[k] = e } } }
//判断entry是否被标记删除,并且标记为nil的entry更新标记为expunge
func (e *entry)tryExpungeLocked(isxpunged bool){ p := atomic.LoadPointer(&e.p) for p == nil { // 将已经删除标记为nil的数据标记为expunged if atomic.CompareAndSwapPointer(&e.p,nil,expunged){ return true } p = atomic.LoadPointer(&e.p) } return p == expunged }
// entry尝试更新
func (e *entry)tryStore(i interface{})bool{ p := atomic.LoadPointer(&e.p) if p == expunged { return false } for { if atomic.CompareAndSwapPointer(&e.p,p,unsafe.Pointer(i)){ return true } p = atomic.LoadPointer(&e.p) if p == expunged { return false } } }
// read里 将标记为 expunge的更新为nil
func (e *entry) unexpungeLocked()(wasExpunged bool){ return atomic.CompareAndSwapPointer(&e.p,expunged,nil) }
// 更新 entry
func (e *entry)storeLocked(i *interface{}){ atomic.StorePointer(&e.p,unsafe.Pointer(i)) }
因此,每次操作先检查read,因此read并发安全,性能好些;read不满足,则加锁
检查dirty,一旦是新的键值,dirty会被read更新。- Load方法:
Load方法是一个加载方法,查找key。
func (m *Map)Load(key interface{})(value interface{},ok bool){ //因read只读,线程安全,先查看是否满足条件 read,_ := m.read.Load().(readOnly) e,ok := read.m[key] //如果read没有,并且dirty有新数据,那从dirty中查找,由于dirty是普通map //线程不安全,这个时候用到互斥锁了 if !ok && read.amended { m.mu.Lock() // 双重检查 read,_ = m.read.Load().(readOnly) e , ok = read.m[key] // 如果read中还是不存在,并且dirty中有新数据 if !ok && read.amended { e ,ok = m.dirty[key] // mssLocked() 函数是性能是sync.Map性能得以保证的重要函数,目的 // 讲有锁的dirty数据,替换到只读线程安全的read里 m.missLocked() } m.mu.Unlock() } if !ok { return nil ,false } return e.load() }
// dirty 提升至 read 关键函数,当misses经过多次load之后,大小等于 // len(dirty)时候,讲dirty替换到read里,以此达到性能提升。
func (m *Map)missLocked(){
m.misses++
if m.misses < len(m.dirty) {
return
}
// 原子操作,耗时很小
m.read.Store(readOnly{m:m.dirty})
m.dirty = nil
m.misses = 0
} -
-
原理
sync.Map是通过冗余的两个数据结构(read,dirty),实现性能的提升。- 为了提升性能,load、delete、store等操作尽量使用只读的read
- 为了提高read的key击中概率,采用动态调整,将dirty数据提升为read
- 对于数据的删除,采用延迟标记删除法,只有在提升dirty的时候才删除
源码不是太清晰,埋个坑深挖
Golang从入门到放弃200712--SyncMap
最新推荐文章于 2024-10-27 09:15:46 发布