这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战
基本原语
go提供的在并发计算中最基本的同步原语(相比channel更为原始),主要有
- sync.Mutex
- sync.RWMutex
- sync.WaitGroup
- sync.Once
- sync.Cond
Mutex
互斥锁
type Mutex struct {
state int32
sema uint32 // 等待队列
}
state不同位有不同的含义,表示当前互斥锁的状态,从高到低分别是
- mutexLocked:互斥锁的锁定状态
- mutexWoken:从正常模式被从唤醒
- mutexStarving:互斥锁进入饥饿模式
- waitersCount:当前互斥锁上面在等待的Goroutine数量
正常模式与饥饿模式
- 正常模式:等待者按照先进先出的顺序获得锁
-
- 性能更好
- 先进先出指的是Goroutine抢锁的顺序,而不是等待的顺序,旧的goroutine会被唤醒与新的goroutine竞争
-
- 没睡醒的肯定争不过刚来的
- 饥饿模式:goroutine超过1ms没有获取到锁,进入饥饿模式,防止被饿死
-
- 变成队列的形式排队进行锁的获取
加锁和解锁
加锁:
- 使用CAS的方式(compare and swap)
- 使用自旋锁来检查当前锁是否可用
-
- 自旋锁:goroutine反复检查锁变量是否可用,直到获取该锁,持有该锁直到完成操作
- 自旋锁会持续占用CPU,避免Goroutine的切换,在多核下才有意义,否则自旋等锁没有人能够释放锁
- 条件
-
- 正常模式
- 多核
- 自旋次数小于4
- 存在一个正在运行的处理器P,且等待队列为空
- 每次旋都会检查当前锁的状态
- 尝试通过CAS更新锁
-
- 如果是正常模式,直接退出即可
- 如果是在队列,出队
-
- 只有当前Goroutine,从饥饿模式退出
解锁:
- 直接减去加锁偏移量解锁就好了
- 解锁不成功的时候进入sync.Mutex.unlockSlow
-
- 正常模式解完直接返回
- 饥饿模式从队列唤醒等待者
RWMutex
mutex只要需要访问该资源都需要上锁,RWMutex不限制并发读,只是不允许并发写与读写,有着更高的性能
type RWMutex struct {
w Mutex // 复用锁能力
writerSem uint32 // 写等待
readerSem uint32 // 读等待
readerCount int32 // 当前并发读数量
readerWait int32 // 写阻塞是
}
主要有四个函数,分别针对读、写的上锁与解锁操作
写锁
sync.RWMutex.Lock()上锁
- 使用w上锁阻塞后续其他写操作
- 获取当前读队列,并且将读队列置为负数
-
- 不为零,并且无法阻塞其他读操作时
- 当前写goroutine进入休眠,等待唤醒
sync.RWMutex.Unlock()解锁
- 将readcounter恢复为pending的reader数量,释放读锁,释放读routine
- 释放写锁
先读后写,保证读goroutine不会被饿死
读锁
sync.RWMutex.RLock()上锁
- 如果readerCounter为负数,说明当前有写Goroutine,当前读Goroutine进入休眠
sync.RWMutex.RUnlock()解锁
- 减少读队列
-
- 如果读队列小于0,说明有写操作在进行
- 减少等待队列
- 尝试解锁写goroutine
WaiteGroup
等待一组Goroutine返回,常用于多Groutine的同步,盲猜使用了信号量
type WaitGroup struct {
noCopy noCopy
state1 [3]uint32 // 存储当前结构体的状态
}
- noCopy用于防止被拷贝(值复制),防止同步数量对不上
-
- 在编译的时候会进行检查,如果用户进行了值拷贝,会报错
三个方法
Add():
- 更新内部的计数器
- 传入的可以是一个负值
- 计数器为负值的时候会报错
- 当计数器为0的时候会唤醒所有等待的Goroutine
Waite():
- 如果计数器为0,直接过
- 否则休眠Goroutine计数器+1,并且使得当前Goroutine休眠
Done():
- 实际上就是调用了Add(),传入-1
Once
能够保证内部的函数只执行了1次
type Once struct {
done uint32
m Mutex
}
只唯一向外暴露了一个方法
Do()
接受参数为一个无参函数
- 判断一下是否执行过
- 没有执行过
-
- 上锁
- 执行无参函数
- 标记为执行过
Cond
用于多个Goroutine等待一个条件再启动,条件广播
type Cond struct {
noCopy noCopy
L Locker // 保护内部notifyList字段
notify notifyList // goroutine链表
checker copyChecker
}
type notifyList struct {
wait uint32 // 正在等待的Goroutine索引
notify uint32 // 已经被唤醒的Goroutine索引
lock mutex
head *sudog
tail *sudog
}
Wait()
使当前Goroutine进入休眠
- 加入notify list
- 加入wait list的末端,并且使当前Goroutine陷入休眠
Signal()
使队列最前面的Goroutine被唤醒
Broadcast()
唤醒队列中所有的Goroutine