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的操作
- 将goroutine加入等待通知队列
- 释放lock,这样允许其他goroutine操作临界资源或者更新状态到满足条件,否则永远无法满足条件
- 进入条件等待,goroutine阻塞
- 被唤醒后重新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操作类似
- 访问是先从read中找,如果没有就去dirty中找,misses记录read没有命中的次数
- 更新是先从read中找,如果没有就去dirty中找,不论在哪个中找到,直接update entry.p
- 删除是先从read中找,如果没有就去dirty中找,如果在read中entry.p=nil,如果read中没有但在dirty中,那就加锁然后delete(map, key)