1.sync.mutex的结构
强调:mutex使用之后不要做copy操作,锁拷贝—使用vet工具检测。
1.1sync.mutex数据结构
type Mutex struct {
state int32 //状态
sema uint32 //信号量,用于控制锁的获得和释放(等待队列)
}
在sync.Mutex的实现中,sema是一个信号量,用于控制锁的获取和释放。它是一个uint32类型的字段,初始值为0。
1.2sync.mutex具体实现
locked:互斥锁的锁定状态
woken:从正常模式中被唤醒
starving : 饥饿模式
waitershift(剩下的位置):等待者数量
为了保证锁的公平性,设计上互斥锁有两种状态:正常状态和饥饿状态。
1.正常模式流程:
①当一个协程需要获取锁时,它会先通过CAS原子操作将sema值加1。如果加1后的结果为1,表示锁之前是未锁定状态,该线程可以直接获取锁;
②如果加1后的结果大于1,表示锁之前已经被其他协程锁定了,会进行多次自旋尝试。如果自旋失败该协程会进入等待队列并被阻塞。
③.当一个协程释放锁时,它会通过原子操作将sema值减1,并唤醒等待队列中的一个协程。
如果一个等待的协程超过1ms没有获得锁,那么就会把锁转为饥饿模式。
2.饥饿模式下,锁的所有权将从 unlock 的 gorutine 直接交给交给等待队列中的第一个。新来的 goroutine 将不会尝试去获得锁,即使锁看起来是 unlock 状态, 也不会去尝试自旋操作,而是放在等待队列的尾部。
饥饿模式的优点:能能够减少锁自旋的竞争,并且尽量的保持公平。
CAS是什么?
CAS(Compare and
Swap)原子操作是一种并发编程中常用的原子操作。它可以在多线程环境下保证变量的原子性操作,从而避免并发问题。CAS操作包含三个参数:内存地址V、旧值A、新值B。当V的值等于A时,CAS会将V的值更新为B。如果V的值不等于A,说明在CAS操作过程中V已经被其他线程修改了,CAS操作不会执行任何操作。CAS操作是一种无锁算法,它比传统的锁算法更加高效,因为它不需要获取锁来保证操作的原子性。
2.读写锁RWMutex
读写锁是在出现大量的并发读、少量的并发写并且有强烈的性能需求时,对于mutex做出的优化。
2.1.读写锁与读者写者模型
2.2RWMutex底层结构示意图:
2.读锁状态下:可以有多个协程同时获取读锁,
读锁实现:
①比较readCount>0 直接+1,如果readCount<0 则说明写锁在排队,readerWait++。
②当一个读者读完的时候,readCount-1,readerWait-1,如果readerWait==0则说明是最后一个读者,可以使写进程执行写操作。
③写锁状态下(readerCount<0):只能有一个协程获取写锁,其他协程不能获取读锁或者写锁。
写锁实现:
①先通过原子操作获取写锁,如果当前没有协程持有读写锁,则可以写。否则需要进入等待队列等待读锁和写锁的释放。
②readerCount变为超级大的负值,阻塞读,并且告诉后面的读进程有写进程等待。
③等待先置协程结束,进行读操作。