golang传输技巧(golang系列sync同步)

golang传输技巧(golang系列sync同步)(1)

sync.WaitGroup
  • add和wait要在同一个goroutine中调用
  • 在sub goroutine中调用done
  • WaitGroup可以在wait后继续复用
sync.Once
  • do方法调用后,无论是否成功都不能再使用该Once了
  • 如果有多个函数需要调用一次,那么每个对应生成一个Once结构
sync.Mutex and sync.RWMutex
  • 实现经典的临界区访问同步
  • 都实现了locker interface: Lock() UnLock()
  • sync.RWMutex.RLocker()返回实现了Locker interface的读锁
  • 开箱即用
sync.Cond
  • 用于协调访问临界资源的线程
  • 基于互斥锁,每个Cond初始都需要一个互斥锁来完成初始化

send

lock.Lock() for mailbox == 1 { sendCond.Wait() } mailbox = 1 lock.Unlock() recvCond.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)
,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页