golang底层 sync
sync.Cond
type Cond struct { L Locker notify notifyList } func NewCond(l Locker) *Cond { return &Cond{L: l} } func (c *Cond) Wait() { t := runtime_notifyListAdd(&c.notify) // t是一个序号 c.L.Unlock() // 先解锁 runtime_notifyListWait(&c.notify, t) // 挂起当前 goroutine,等待 t c.L.Lock() // 恢复执行时,再加锁 } func (c *Cond) Signal() { // 不要求调用方一定要持有c.L runtime_notifyListNotifyOne(&c.notify) } func (c *Cond) Broadcast() { // 不要求调用方一定要持有c.L runtime_notifyListNotifyAll(&c.notify) } type notifyList struct { // 本质上是个队列 wait uint32 // 下一个 waiter 的 ticket 编号,原子自增 notify uint32 // 下一个被通知的 waiter 的 ticket 编号 lock mutex head *sudog // waiter 队列 tail *sudog }
runtime_notifyListAdd 将wait加1并返回旧的wait值,
当前goroutine调用runtime_notifyListWait,传入wait的旧值,然后将当前g包装成sudog加入到队列里,挂起自己,等待通知
notifyListNotifyOne 会从notifyList队列里取出一个g放到等待队列里
notifyListNotifyAll 会从notifyList队列里取出所有的g放到等待队列里
sync.Mutex
type Mutex struct { state int32 // 表示 mutex 锁当前的状态 sema uint32 // 信号量,用于唤醒 goroutine } const ( // 状态 mutexLocked = 1 << iota mutexWoken mutexStarving mutexWaiterShift = iota starvationThresholdNs = 1e6 )
Mutex 可能处于两种不同的模式:正常模式和饥饿模式。
在正常模式中,等待者按照 FIFO 的顺序排队获取锁,但是一个被唤醒的等待者有时候并不能获取 mutex, 它还需要和新到来的 goroutine 们竞争 mutex 的使用权。 如果一个 goroutine 等待 mutex 释放的时间超过 1ms,它就会将 mutex 切换到饥饿模式
在饥饿模式中,mutex 的所有权直接从解锁的 goroutine 递交到等待队列中排在最前方的 goroutine。 新到达的 goroutine 不会尝试获取 mutex,而是排到等待队列的尾部。
如果一个等待者获得了 mutex,并且它是等待队列中的最后一个,或者它等待的时间少于 1ms,它便将 mutex 切换到正常模式
正常模式下的性能会更好,因为一个 goroutine 能在即使有很多阻塞的等待者时多次连续的获得一个 mutex,饥饿模式的重要性则在于避免了病态情况下的尾部延迟。
Lock():
无冲突,通过 atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) 操作把当前状态设置为加锁状态
有冲突且为正常模式,自旋一段时间,如果其他 goroutine 在这段时间内释放该锁,直接获得该锁。超过了这段时间,通过调用 runtime_SemacquireMutex 让 goroutine 进入等待状态
饥饿模式不会自旋,而是进入队列等待
Unlock()
修改锁的状态,调用runtime_Semrelease 唤醒等待goroutine
sync.Once
type Once struct { m Mutex done uint32 } func (o *Once) Do(f func()) { // if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() } // 上面这样是错的,Do()保证返回时,f已经执行结束了, // 而CompareAndSwapUint32失败后Do会立即返回,此时f可能还没执行 // 所以对o.done的赋值,要写在defer里, // 即使 f 触发了panic,未来也不会再次执行 if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } } func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }
sync.WaitGroup
type WaitGroup struct { state1 [3]uint32 } // 信号量、计数器、等待数 func (wg *WaitGroup) Done() { wg.Add(-1) } func (wg *WaitGroup) Add(delta int) { statep, semap := wg.state() // 获取状态指针和信号量 state := atomic.AddUint64(statep, uint64(delta)<<32) // 将 delta加到计数器上 v := int32(state >> 32) // 计数器的值 w := uint32(state) // 等待数 if v < 0 { panic("sync: negative WaitGroup counter") } // 计数器为负 panic if v > 0 || w == 0 { return } // 计数器 > 0 或者等待数为 0,直接返回 // 计数器等于0, 等待数w > 0 for ; w != 0; w-- { runtime_Semrelease(semap, false, 0) } } func (wg *WaitGroup) Wait() { // Wait 会保持阻塞直到 WaitGroup 计数器归零 statep, semap := wg.state() // 获得计数器和信号量 for { // 一个简单的死循环,只有当计数器归零才会结束 state := atomic.LoadUint64(statep) // 原子读 v := int32(state >> 32) // 计数器 w := uint32(state) // 等待数 if v == 0 { return } // 计数器为零,退出循环 // 等待数加 1 if atomic.CompareAndSwapUint64(statep, state, state+1) {..... runtime_Semacquire(semap) // 会阻塞到信号量为 0 return } } }
sync.Map
不太确定
sync.Map 类似 map[interface{}]interface{},是并发安全的。
一般代码应该用普通的map,sync.Map是为了以下两种情景:
1,key只会被写入一次,但会被读取多次,例如不断增长的cache
2,多个goroutine分别操作不同的key
type Map struct { mu Mutex read atomic.Value dirty map[interface{}]*entry misses int } type readOnly struct { m map[interface{}]*entry amended bool }
read有的key,dirty中都有,且对应的value是同一个entry对象,在read中修改,dirty中能直接看到
添加新元素只会添加到dirty中,
获取元素会先到read中,read没有再到dirty中获取,去dirty中读的次数多了,就会把dirty同步到read
对read的Load操作不用加锁,Store需要加锁,可以在不持有mu的情况下更新read中的entry,但更新被标记为删除的entry,需要持有mu
对dirty的操作需要加锁
Load,Store 和 delete 通过延迟操作来均摊成本,可在常数时间内返回
Store(key, value)
1,read中有key,且未被标记为删除,则更新key对应的value,不用加锁
2,read中有key,key被标记为删除,则去掉删除标记,
让dirty[key]指向read[key]所指向的底层对象,并把read[key]赋值为value
3,read中没有key,dirty里有key,直接更新dirty[key]
4,read和dirty中都没有key,把read标记为amended,并把dirty[key]赋值为value
Lord(key)
1,read中有key,直接返回
2,read中没有key,且read有amended标记,到dirty里找,
并将misses加1,misses大于len(dirty map)时,dirty将被提升为read,去掉amended标记
map 的下一次 store 将生成新的 dirty,包含read的全部未删除元素
3,read和dirty中都没有key,返回nil
delete(key)
1,read里有key,则标记为删除
2,read里没有key,dirty里有key,直接从dirty里删掉