全球最大的网站建设外包网,设备上哪个网站做外贸推广,关于网站建设的奖项名称,域名除了做网站还能做什么1.Mutex
互斥锁是并发程序中对共享资源进行访问控制的主要手段#xff0c;对此Go语言提供了Mutex#xff0c;对外暴露Lock()和Unlock两个方法#xff0c;分别用于加锁和解锁。
1.1 Mutex的数据结构
源码如下#xff1a;
type Mutex struct{state int32//代表互斥锁的状…1.Mutex
互斥锁是并发程序中对共享资源进行访问控制的主要手段对此Go语言提供了Mutex对外暴露Lock()和Unlock两个方法分别用于加锁和解锁。
1.1 Mutex的数据结构
源码如下
type Mutex struct{state int32//代表互斥锁的状态sema uint32//表示信号量协程阻塞等待该信号量。解锁的协程释放信号量从而唤醒等待信号量的协程
}state表面上看起来是32位的整型变量实际上内部分成了4部分。
Locked表示该Mutex是否已被锁定0表示没有锁定1表示已被锁定Woken表示是否有协程已被唤醒0表示没有协程唤醒1表示已有协程唤醒正在加锁过程中Starving表示该Mutex是否处于饥饿状态0表示没有饥饿1表示饥饿状态说明有协程阻塞了超过1msWaiter表示阻塞等待锁的协程个数协程解锁时根据此值来判断是否需要释放信号量
抢锁实际上是抢给Locked赋值的权利抢不到就阻塞等待信号量sema一旦持有锁的协程解锁等待的协程就会依次被唤醒。
1.2 加/解锁过程
简单加锁 locked置为1即可 加锁被阻塞 waiter1 简单解锁 locked置为0即可 解锁并唤醒协程 locked置为0查看waiter0释放一个信号量唤醒一个阻塞的协程被唤醒的协程吧locked置为1
1.3 自旋过程
加锁时如果当前Locked位为1尝试加锁的协程不是马上转入阻塞而是会持续地探测Locked位是否变为0这个过程为自旋过程。
自旋时间很短如果在自旋过程中发现锁已被释放那么协程可以立即获取锁。此时即便有协程被唤醒也无法获取锁只能再次阻塞像极了被插队的情况
1.3.1 什么是自旋
自旋对应于CPU的PAUSE指令CPU对该指令什么也不做相当于CPU空转对于程序来说相当于”sleep“了一小段时间时间很短30个时针周期。不同于sleep自旋不会将协程转换为睡眠状态。
1.3.2 自旋条件
加锁时程序会自动判断是否可以自旋无限制的自旋会给CPU带来巨大压力所以判断是否可以自旋就很重要了。 自旋必须满足以下所有的条件
自旋次数要足够小通常为4CPU核数要大于1否则自旋没有意义协程调度机制中的Processor数量要大于1协程调度机制中的可运行队列必须为空否则会延迟协程调用
自旋的条件很苛刻总而言之就是不忙的时候才会启动自旋。
1.3.3 自旋的优势
自旋的优势就是更充分地利用CPU尽量避免协程切换。经过短时间的自旋可以获得锁就不必进入阻塞状态。
1.3.4 自旋的问题
如果自旋过程中获得锁那么之前被阻塞的协程将无法获得锁。如果加锁的协程特别多每次都通过自旋获得锁那么之前被阻塞的进程将很难获得锁从而进入了Starving状态。
1.4 Mutex模式
1.4.1 Normal模式
在该模式下如果协程加锁不成功不会立即转入阻塞队列而是先判断是否满足自旋状态如果满足则会启动自旋过程尝试加锁。
1.4.2 Starving模式饥饿模式
被唤醒的协程尝试加锁时发现已经被抢占了只好再次阻塞不过阻塞前会判断上一次阻塞距离这次阻塞的时长如果超过1ms则会将Mutex标记为Starving模式然后阻塞。
在Starving模式下不会启动自旋。
1.5 Woken状态
加锁的程序在自旋的过程中会把woken标记为1用于通知解锁协程不需要释放信号量。
1.6 为什么重复解锁要触发panic
如果多次Unlock()那么可能每次都释放一个信号量这样会唤醒多个协程多个协程被唤醒后会继续在Lock()的逻辑中抢锁会增加Lock()实现的复杂度也会引起不必要的协程切换。
1.7 编程Tips
使用defer避免死锁 加锁后立即使用defer对其解锁可以有效避免死锁 加锁和解锁应该成对出现 加锁和解锁最好出现在同一层次的代码块中
2. RWMutex
读写互斥锁在读取数据频率远远大于写数据频率的场景会有更好的性能。
需要解决的问题
写锁阻塞写锁写锁阻塞读锁读锁阻塞写锁读锁不阻塞读锁
2.1 读写锁的数据结构
2.1.1 数据结构
源码
type RWMutex struct{w Mutex //用于控制多个写锁获得写锁首先要获取该锁writerSem uint32 //写阻塞等待的信号量最后一个读者释放锁时会释放信号量readerSem uint32 //读阻塞的协程等待的信号量持有写锁的协程释放锁后会释放信号量readerCount int32 //记录读者个数readerWait int32 //记录写阻塞时读者个数
}2.1.2 接口的定义
RWMutex提供了以下几个简单的接口
RLock()读锁定 增加读操作计数readerCount阻塞等待写操作结束如果有 RUnlock()解除读锁定 减少读操作计数readerCount–最后一个解除读锁定的协程唤醒等待写操作的协程如果有 Lock()写锁定 获取互斥锁阻塞等待所有读操作结束 Unlock()解除写锁定 唤醒因读锁定而被阻塞的协程如果有解除互斥锁 TryLock()以非阻塞方式尝试写锁定TryRLock()以非阻塞方式尝试读锁定
2.2 场景分析
写操作需要等待读操作结束写操作等待期间可能还有新的读操作持续到来。但在RWMutex中为什么写锁定不会被“饿死”
因为写操作到来时会把RWMutex.readerCount值拷贝到RWMutex.readerWait中用于标记排在写操作前面的读者个数。写操作结束后会递减readerWait这个值为0时唤醒写操作。