Go 并发编程

1、Mutex 几种状态
⚫ mutexLocked — 表示互斥锁的锁定状态;
⚫ mutexWoken — 表示从正常模式被从唤醒;
⚫ mutexStarving — 当前的互斥锁进入饥饿状态;
⚫ waitersCount — 当前互斥锁上等待的 Goroutine 个数;

 

2、Mutex 正常模式和饥饿模式
正常模式(非公平锁)
正常模式下,所有等待锁的 goroutine 按照 FIFO(先进先出)顺序等待。唤醒
的 goroutine 不会直接拥有锁,而是会和新请求 goroutine 竞争锁。新请求的
goroutine 更容易抢占:因为它正在 CPU 上执行,所以刚刚唤醒的 goroutine20
有很大可能在锁竞争中失败。在这种情况下,这个被唤醒的 goroutine 会加入
到等待队列的前面。

 

饥饿模式(公平锁)
为了解决了等待 goroutine 队列的长尾问题
饥饿模式下,直接由 unlock 把锁交给等待队列中排在第一位的 goroutine (队
头),同时,饥饿模式下,新进来的 goroutine 不会参与抢锁也不会进入自旋状
态,会直接进入等待队列的尾部。这样很好的解决了老的 goroutine 一直抢不
 到锁的场景。

 

 

饥饿模式的触发条件:当一个 goroutine 等待锁时间超过 1 毫秒时,或者当前
队列只剩下一个 goroutine 的时候,Mutex 切换到饥饿模式。
总结
对于两种模式,正常模式下的性能是最好的,goroutine 可以连续多次获取
锁,饥饿模式解决了取锁公平的问题,但是性能会下降,这其实是性能和公平
的一个平衡模式。  

 


RWMutex 实现
通过记录 readerCount 读锁的数量来进行控制,当有一个写锁的时候,会将读
锁数量设置为负数 1<<30。目的是让新进入的读锁等待之前的写锁释放通知读
锁。同样的当有写锁进行抢占时,也会等待之前的读锁都释放完毕,才会开始21
进行后续的操作。 而等写锁释放完之后,会将值重新加上 1<<30, 并通知刚才
新进入的读锁(rw.readerSem),两者互相限制。
5、RWMutex 注意事项
⚫ RWMutex 是单写多读锁,该锁可以加多个读锁或者一个写锁
⚫ 读锁占用的情况下会阻止写,不会阻止读,多个 Goroutine 可以同时获取
读锁
⚫ 写锁会阻止其他 Goroutine(无论读和写)进来,整个锁由该 Goroutine
独占
⚫ 适用于读多写少的场景
⚫ RWMutex 类型变量的零值是一个未锁定状态的互斥锁
⚫ RWMutex 在首次被使用之后就不能再被拷贝
⚫ RWMutex 的读锁或写锁在未锁定状态,解锁操作都会引发 panic
⚫ RWMutex 的一个写锁去锁定临界区的共享资源,如果临界区的共享资源已
被(读锁或写锁)锁定,这个写锁操作的 goroutine 将被阻塞直到解锁
⚫ RWMutex 的读锁不要用于递归调用,比较容易产生死锁
⚫ RWMutex 的锁定状态与特定的 goroutine 没有关联。一个 goroutine 可
以 RLock(Lock),另一个 goroutine 可以 RUnlock(Unlock)
⚫ 写锁被解锁后,所有因操作锁定读锁而被阻塞的 goroutine 会被唤醒,并
都可以成功锁定读锁
⚫ 读锁被解锁后,在没有被其他读锁锁定的前提下,所有因操作锁定写锁而
被阻塞的 Goroutine,其中等待时间最长的一个 Goroutine 会被唤醒
          
Cond 是什么
Cond 实现了一种条件变量,可以使用在多个 Reader 等待共享资源 ready 的场
景(如果只有一读一写,一个锁或者 channel 就搞定了)
22
每个 Cond 都会关联一个 Lock(*sync.Mutex or *sync.RWMutex),当修改条
件或者调用 Wait 方法时,必须加锁,保护 condition。 






、Broadcast 和 Signal 区别
func (c *Cond) Broadcast()
Broadcast 会唤醒所有等待 c 的 goroutine。
调用 Broadcast 的时候,可以加锁,也可以不加锁。
func (c *Cond) Signal()
Signal 只唤醒 1 个等待 c 的 goroutine。
调用 Signal 的时候,可以加锁,也可以不加锁。

 

Cond 中 Wait 使用
func (c *Cond) Wait()
Wait()会自动释放 c.L 锁,并挂起调用者的 goroutine。之后恢复执行,
Wait()会在返回时对 c.L 加锁。
除非被 Signal 或者 Broadcast 唤醒,否则 Wait()不会返回。
由于 Wait()第一次恢复时,C.L 并没有加锁,所以当 Wait 返回时,调用者通常
并不能假设条件为真。如下代码:。
取而代之的是, 调用者应该在循环中调用 Wait。(简单来说,只要想使用
condition,就必须加锁。)
c.L.Lock()
for !condition() {
 c.Wait()
}
... make use of condition ...
c.L.Unlock()