Golang从入门到放弃200712--SyncMap

  • 说明
    这篇文章非原创,因为是直接复制到本地最近才开始整理,
    本文的原作者与我联系,若有侵权,立即删除。

  • 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源码

    • 数据结构

      1. 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
      }
      
      1. read的数据结构是:
      type readOnly struct{
      	m map[interface{}]*entry
      	// 如果Map.dirty的数据和m中的数据不一样是true
      	amended bool
      }
      
      1. 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),实现性能的提升。

    1. 为了提升性能,load、delete、store等操作尽量使用只读的read
    2. 为了提高read的key击中概率,采用动态调整,将dirty数据提升为read
    3. 对于数据的删除,采用延迟标记删除法,只有在提升dirty的时候才删除

    源码不是太清晰,埋个坑深挖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值