七叶笔记 » golang编程 » golang系列:sync同步

golang系列:sync同步

sync.WaitGroup

  • add和wait要在同一个 go routine中调用
  • 在sub goroutine中调用done
  • WaitGroup可以在wait后继续复用

sync.Once

  • do方法调用后,无论是否成功都不能再使用该Once了
  • 如果有多个函数需要调用一次,那么每个对应生成一个Once结构

sync.Mutex and sync.RWMutex

  • 实现经典的临界区访问同步
  • 都实现了 lock er interface: Lock() UnLock()
  • sync.RWMutex.RLocker()返回实现了Locker interface的读锁
  • 开箱即用

sync.Cond

  • 用于协调访问临界资源的线程
  • 基于互斥锁,每个Cond初始都需要一个互斥锁来完成初始化

send

 lock.Lock()
for mailbox == 1 {
 sendCond.Wait()
}
mailbox = 1
lock.Unlock()
 recv Cond.Signal()  

recv

 lock.RLock()
for mailbox == 0 {
 recvCond.Wait()
}
mailbox = 0
lock.RUnlock()
sendCond.Signal()  

wait的操作

  1. 将goroutine加入等待通知队列
  2. 释放lock,这样允许其他goroutine操作临界资源或者更新状态到满足条件,否则永远无法满足条件
  3. 进入条件等待,goroutine阻塞
  4. 被唤醒后重新lock,抢到锁继续执行,没抢到进入锁等待;可能出现被唤醒后有一个goroutine执行了,其他goroutine又不满足条件了,所以外层要有for循环配合wait保证每次被唤醒后要检查条件是否符合要求,如果不符合继续进入wait

sync.Pool

  • 临时对象池,用于存储临时对象,主要用于缓存数据
  • put方法放入一个元素(interface{})
  • get方法获取一个元素(interface{}),同时会从pool中删除该值
  • gc会自动清理pool

sync.Map

key and value都是interface{},但对key有要求,需要是Comparable,不支持map, slice, func,在使用sync.Map时必须自己进行类型检查

方案一

建议将sync.Map放到一个结构体中,然后为 结构体 提供多个方法,在方法的参数中明确参数类型,这样go编译器就可以帮助类型检测,确保类型ok

简单,但需要为每个使用到的类型定义方法,比较麻烦

方案二

也是将sync.Map放到一个结构体中,但使用reflect.Type类规定keyType和valueType,初始化结构体时指定

 type ConcurrentMap  struct  {
 m         sync.Map
 keyType   reflect.Type
 valueType reflect.Type
}

func (cMap *ConcurrentMap) Load(key interface{}) (value interface{}, ok  bool ) {
 if reflect.TypeOf(key) != cMap.keyType {
  return
 }
 return cMap.m.Load(key)
}

func (cMap *ConcurrentMap) Store(key, value interface{}) {
 if reflect.TypeOf(key) != cMap.keyType {
  panic(fmt.Errorf("wrong key type: %v", reflect.TypeOf(key)))
 }
 if reflect.TypeOf(value) != cMap.valueType {
  panic(fmt.Errorf("wrong value type: %v", reflect.TypeOf(value)))
 }
 cMap.m.Store(key, value)
}  

sync.Map并发原理

 type Map struct {
mu Mutex

// read contains the portion of the map's contents that are safe for
// concurrent access (with or without mu held).
//
// The read field itself is always safe to load, but must only be stored with
// mu held.
//
// Entries stored in read may be updated concurrently without mu, but updating
// a previously-expunged entry requires that the entry be copied to the dirty
// map and unexpunged with mu held.
read atomic.Value // readOnly

// dirty contains the portion of the map's contents that require mu to be
// held. To ensure that the dirty map can be promoted to the read map quickly,
// it also includes all of the non-expunged entries in the read map.
//
// Expunged entries are not stored in the dirty map. An expunged entry in the
// clean map must be unexpunged and added to the dirty map before a new value
// can be stored to it.
//
// If the dirty map is nil, the next write to the map will initialize it by
// making a shallow copy of the clean map, omitting stale entries.
dirty map[interface{}]*entry

// misses counts the number of loads since the read map was last updated that
// needed to lock mu to determine whether the key was present.
//
// Once enough misses have occurred to cover the cost of copying the dirty
// map, the dirty map will be promoted to the read map (in the unamended
// state) and the next store to the map will make a new dirty copy.
misses int
}  
  • 有两个字典:read和dirty,其中read是atomic.Value类型,存取都是 原子操作 不需要锁
  • 两个字典中存的key和value都是*interface{}类型,这样任何一个字典的值update都是更新指针地址,都是可以是原子操作(atomic中有相应的unsafe.Pointer操作)
  • 相同的key指向相同的value(*entry {p unsafe.Pointer})
  • read中的key是只读的,value可以直接udpate,删除就是entry.p=nil
  • dirty中就是一个普通的map,访问需要加锁,新增和删除和普通map操作类似
  1. 访问是先从read中找,如果没有就去dirty中找,misses记录read没有命中的次数
  2. 更新是先从read中找,如果没有就去dirty中找,不论在哪个中找到,直接update entry.p
  3. 删除是先从read中找,如果没有就去dirty中找,如果在read中entry.p=nil,如果read中没有但在dirty中,那就加锁然后delete(map, key)

相关文章