Go 语言 sync.Mutex 源码分析
go 语言以并发作为其特性之一,并发必然会带来对于资源的竞争,这时候我们就需要使用 go 提供的 sync.Mutex 这把互斥锁来保证临界资源的访问互斥。
既然经常会用这把锁,那么了解一下其内部实现,就能了解这把锁适用什么场景,特性如何了。
打开源码,我们先看一下mutex.go文件的描述。
// 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.
//------------------------------------------------------------------------------------------------------------------------------------
// 互斥公平锁 .
// 锁有两种模式,【正常模式】和【饥饿模式】
// 在正常模式下锁有等待锁的goroutine都会进入一个先进先出的队列(轮流被唤醒),但是被
//唤醒的goroutine不会直接获得锁,而是要跟新到来的gorotine竞争。
//新来的goroutine有个一个优势 -- 他们已近CPU上运行,并且数量众多,
//所以刚被唤醒的goroutine大概率获取不到锁.在这样的情况下,被唤醒的goroutine会被
//队列头部。如果一个goroutine等待超过1ms(写死的)没有获取到锁,互斥锁将进入饥饿模式。
//
//在饥饿模式中,解锁的goroutine会将锁直接交付给等待队里最前面的goroutine.
//新来的goroutine 不会尝试获取锁(即使锁在空闲状态),也不会进行自旋,
//他们只是加入到等待队列尾部.
//
//如果一个goroutine 获取到锁,他会判断
//1 . 他是否是位于等待队列末尾
//2 . 他等待是否超过1ms
// 以上只有有一个成立,将把互斥锁切换至正常模式
//
// 正常模式 :具有较好的性能,即使存在许多阻塞者,goroutine也也会尝试几次获取锁。
// 饥饿模式 :对于防止尾部延迟是非常重要的。
`` sync.Mutex
``
stage
这个字段会同时被多个goroutine公用(使用atomic来保证原子性),第1个bit 表示已加锁。第2个bit 表示某个goroutine被唤醒,尝试获取锁,第3个bit表示这把锁是否是饥饿状态。
``[1][1][1]
`` : 第一个[1] 表示锁状态,第二个[1]表示是否有唤醒,第三个[1]表示是否是饥饿模式
·``001
`普通模式 ,无唤醒, 锁 ,
`010
` 普通模式, 有唤醒 ,无锁状态,,
`101
`` 饥饿模式 ,无唤醒 ,锁
sema
用来唤醒 goroutine 所用的信号量。
LOCK
`` 在看代码之前,我们需要有一个概念:每个 goroutine 也有自己的状态,存在局部变量里面(也就是函数栈里面),goroutine 有可能是新到的、被唤醒的、正常的、饥饿的。
``
UNLOCK
>接下来我们来看看 Unlock 的实现,对于 Unlock 来说,有两个比较关键的特性:
如果说锁不是处于 locked 状态,那么对锁执行 Unlock 会导致 panic;
锁和 goroutine 没有对应关系,所以我们完全可以在 goroutine 1 中获取到锁,然后在 goroutine 2 中调用 Unlock 来释放锁(这是什么骚操作!)
结语
锁和解锁的代码只有这么简单的几行,但是其中的原来和设计的巧妙点缺非常多,从这个里我们可以看出,系统设计的好坏跟代码多少无关,系统内涵的设计跟代码设计也无关,真的大师一定是大道至简。
版权声明: 本文为 InfoQ 作者【Dnnn】的原创文章。
原文链接:【http://xie.infoq.cn/article/71dbcba9c80644495afb8c322】。文章转载请联系作者。
评论