sync.Mutex是一个不可重入的排他锁。 这点和Java不同,golang里面的排它锁是不可重入的。
当一个 goroutine 获得了这个锁的拥有权后, 其它请求锁的 goroutine 就会阻塞在 Lock 方法的调用上,直到锁被释放。
数据结构与状态机
sync.Mutex 由两个字段 state 和 sema 组成。其中 state 表示当前互斥锁的状态,而 sema 是用于控制锁状态的信号量。
type Mutex struct {
state int32
sema uint32
}
需要强调的是Mutex一旦使用之后,一定不要做copy操作。
Mutex的状态机比较复杂,使用一个int32来表示:
const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken //2
mutexStarving //4
mutexWaiterShift = iota //3
)
32 3 2 1 0
| | | | |
| | | | |
v-----------------------------------------------v-------------v-------------v-------------+
| | | | v
| waitersCount |mutexStarving| mutexWoken | mutexLocked |
| | | | |
+-----------------------------------------------+-------------+-------------+-------------+
最低三位分别表示 mutexLocked、mutexWoken 和 mutexStarving,剩下的位置用来表示当前有多少个 Goroutine 等待互斥锁的释放:
在默认情况下,互斥锁的所有状态位都是 0,int32 中的不同位分别表示了不同的状态:
- mutexLocked — 表示互斥锁的锁定状态;
- mutexWoken — 表示从正常模式被从唤醒;
- mutexStarving — 当前的互斥锁进入饥饿状态;
- waitersCount — 当前互斥锁上等待的 goroutine 个数;
为了保证锁的公平性,设计上互斥锁有两种状态:正常状态和饥饿状态。
正常模式如果一个等待的goroutine超过1ms没有获取锁,那么它将会把锁转变为饥饿模式