Go Mutex
实现思路
Golang中的互斥锁有两种模式:正常模式和饥饿模式;正常模式下,goroutinue按照正常的先进先出(FIFO)顺序出队,依次被唤醒,被唤醒的goroutine并不一定能直接获得锁,它需要与新请求锁的goroutines去争夺锁的所有权,因为新来的goroutinue正在CPU上运行且数量较多,刚被唤醒的goroutinue在抢锁上并没有优势(外来的和尚好念经...),如果一个等待的goroutine超过1ms时仍未获取到锁,会将这把锁转换为饥饿模式;饥饿模式下,锁的所有权会直接给到排在队头的goroutinue(保证公平性,不能让辛苦排队等待的goroutinues一直拿不到锁),将新来的goroutinue放到队尾.
如果一个被唤醒的goroutinue拿到了锁,且是队列中的最后一个或它的等待时间少于1ms,互斥锁切回正常模式(差不多得了,不能让人家新来的都往队尾排,毕竟人家带着资源);普通模式的好处是:一个goroutinue可以多次竞争锁,不用每次都从队尾排起(你行就直接上,不用等);饥饿模式的好处是可以避免尾部延迟,照顾了排在队列后部的goroutinue,不会让它们等待的时间过长.
数据结构
1 | type Mutex struct { |
mutex不能copy,如果要作为参数传递时,需要传指针
部分源码
lock
1 | func (m *Mutex) Lock() { |
上述提到的自旋添加应满足:
- 自旋次数小于4次;
- 在多核机器上;
- GOMAXPROCS > 1并且至少有一个其它的处于运行状态的P;
- 当前P没有其它等待运行的G;
unlock
1 | // lock和unlock不是直接意义上的把资源给锁住,而是一种抽象:lock只是一次允许一个goroutinue去读写资源,其余的goroutinue要么sleep、要么自旋;unlock同理,并不是真正的解锁某个资源,更主要做的是唤醒下一个goroutinue去让它读写资源 |