初代版本
源码地址 weekly.2009-11-06 src/pkg/sync/mutex.go
- Mutex 中 key 代表持锁状态
- 取值范围 0 <= key <= 2^32 - 1
- key = 0 代表未被持有锁
- key = 1 代表被持有锁,无等待者
- 1 < key <= 2^32 - 1 代表被持有锁,有 key - 1 个等待者
- sema FIFO(等待队列) 信号量
初代版本锁比较简单,通过 cas 来 对key 进行加减来控制状态,原理就是一个 FIFO 先进先出的队列
这个版本有一个明显的问题,虽然 FIFO 没什么不好,但是有个唤醒动作,如果这时候有新进来的 goroutine,在这个版本中是要排队的,再去唤醒队列中的 goroutine,如果能让新来的直接运行,那么能够节省一个唤醒操作
// the zero value for a Mutex is an unlocked mutex.
type Mutex struct {
key uint32;
sema uint32;
}
func xadd(val *uint32, delta int32) (new uint32) {
for {
v := *val;
nv := v+uint32(delta);
if cas(val, v, nv) {
return nv;
}
}
panic("unreached");
}
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
if xadd(&m.key, 1) == 1 {
// changed from 0 to 1; we hold lock
return;
}
runtime.Semacquire(&m.sema);
}
// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
//
// A locked Mutex is not associated with a particular goroutine.
// It is allowed for one goroutine to lock a Mutex and then
// arrange for another goroutine to unlock it.
func (m *Mutex) Unlock() {
if xadd(&m.key, -1) == 0 {
// changed from 1 to 0; no contention
return;
}
runtime.Semrelease(&m.sema);
}
给新人机会
- Mutex 中 state 代表状态
- 第一位代表是否被持有锁
- 第二位是否有goroutine被唤醒
- 后面的几位代表等待的 goroutine
1101
等待的goroutine数量 = (2^3 + 2^2) / 4 = 3
所以 1101 代表 当前锁被持有,但是无醒着的goroutine 有三个等待着的 goroutine
这个版本和初代版本的相比,给新进来的goroutine一个机会去竞争,而不是让新人去排队,且如果在唤醒FIFO队列中的goroutine之前,就获取到锁,那么就能省一个唤醒开销
Lock:
type Mutex struct {
state int32
sema uint32
}
const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexWaiterShift = iota
)
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
// 幸运的拿到了锁
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return
}
awoke := false
for {
old := m.state
new := old | mutexLocked
// 锁被持有 || 有 goroutine被唤醒 || 有 goroutine 在等待
if old&mutexLocked != 0 {
// 增加等待的数量,+4 因为 state 第一位代表锁是否被持有,第二位代表是否有goroutine被唤醒
new = old + 1<<mutexWaiterShift
}
if awoke {
// 清除唤醒标记
new &^= mutexWoken
}
// 变更state到新的状态
if atomic.CompareAndSwapInt32(&m.state, old, new) {
//新人拿锁,不用睡了
if old&mutexLocked == 0 {
break
}
//拿不到锁,等待唤醒
runtime.Semacquire(&m.sema)
awoke = true
}
}
}
Unlock:
func (m *Mutex) Unlock() {
// 先变更状态(因为lock是 CompareAndSwapInt32),再判断之前的状态是否是已经解锁
new := atomic.AddInt32(&m.state, -mutexLocked)
if (new+mutexLocked)&mutexLocked == 0 {
panic("sync: unlock of unlocked mutex")
}
old := new
for {
//如果没有等待的goroutine,
//已经有goroutine获取到锁
//已经有goroutine被唤醒
//上面三种不需要再去唤醒其它goroutine
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken) != 0 {
return
}
//唤醒状态
new = (old - 1<<mutexWaiterShift) | mutexWoken
//标记唤醒状态,走唤醒流程
if atomic.CompareAndSwapInt32(&m.state, old, new) {
runtime.Semrelease(&m.sema)
return
}
//新人比较猛,获取成功了,走到这里
old = m.state
}
}
多给一些机会
- 这个版本顾名思义,就是多给一些机会
- 获取不到锁,就多自旋几次,而不是获取不到就进入队列等待
- 如果临界区代码耗时很短,那么就能节省唤醒开销
type Mutex struct {
state int32
sema uint32
}
const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexWaiterShift = iota
)
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
// 幸运的拿到了锁
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
return
}
awoke := false
iter := 0
for {
old := m.state
new := old | mutexLocked
// 锁被持有 || 有 goroutine被唤醒 || 有 goroutine 在等待
if old&mutexLocked != 0 {
//还能自旋
if runtime_canSpin(iter) {
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
//进入自旋流程
runtime_doSpin()
iter++
continue
}
// 增加等待的数量,+4 因为 state 第一位代表锁是否被持有,第二位代表是否有goroutine被唤醒
new = old + 1<<mutexWaiterShift
}
if awoke {
//当重复解锁是有可能触发
if new&mutexWoken == 0 {
panic("sync: inconsistent mutex state")
}
// 清除唤醒标记
new &^= mutexWoken
}
// 变更state到新的状态
if atomic.CompareAndSwapInt32(&m.state, old, new) {
//新人拿锁,不用睡了
if old&mutexLocked == 0 {
break
}
//拿不到锁,等待唤醒
runtime.Semacquire(&m.sema)
awoke = true
//已经进入等待队列了,下次唤醒重置自旋次数
iter = 0
}
}
}
解决饥饿
源码地址
新人是有机会了,但是有可能每次都让新人获取了,那么老人怎么办,所以要解决饥饿问题
总的来说,就是让超过一定时间的goroutine打上饥饿标识,来获取优先权去执行,来解决饥饿问
// A Mutex must not be copied after first use.
type Mutex struct {
state int32
sema uint32
}
// A Locker represents an object that can be locked and unlocked.
type Locker interface {
Lock()
Unlock()
}
const (
mutexLocked = 1 << iota //锁占用标识
mutexWoken //唤醒标识
mutexStarving //第三位做为饥饿模式标识
mutexWaiterShift = iota //第三位被占用,后移一位作为goroutine等待数(2+1)
// Mutex fairness.
//
// Mutex can be in 2 modes of operations: normal and starvation.
// In normal mode waiters are queued in FIFO order, but a woken up waiter
// does not own the mutex and competes with new arriving goroutines over
// the ownership. New arriving goroutines have an advantage -- they are
// already running on CPU and there can be lots of them, so a woken up
// waiter has good chances of losing. In such case it is queued at front
// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,
// it switches mutex to the starvation mode.
//
// In starvation mode ownership of the mutex is directly handed off from
// the unlocking goroutine to the waiter at the front of the queue.
// New arriving goroutines don't try to acquire the mutex even if it appears
// to be unlocked, and don't try to spin. Instead they queue themselves at
// the tail of the wait queue.
//
// If a waiter receives ownership of the mutex and sees that either
// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,
// it switches mutex back to normal operation mode.
//
// Normal mode has considerably better performance as a goroutine can acquire
// a mutex several times in a row even if there are blocked waiters.
// Starvation mode is important to prevent pathological cases of tail latency.
starvationThresholdNs = 1e6
)
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
// 幸运获取锁
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
//静态检查可以忽略
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
var waitStartTime int64 //超过多长时间进入饥饿模式
starving := false //饥饿标识
awoke := false //唤醒标识
iter := 0 //自旋次数
old := m.state
for {
//非饥饿