Skip to main content
  1. Docs/

Golang 锁机制

·3 mins· ·
Golang
Owl Dawn
Author
Owl Dawn
Table of Contents

sync.Mutex
#

可以看 sync/mutex.go 中的实现,有这样一段注释:

// 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.

对于 golang 中互斥锁的实现,有两种模式:正常模式(normal)和饥饿模式(starvation)。

normal 模式
#

waiter 按照先进先出(FIFO)来排队,当锁释放唤醒队首的 waiter,它不会直接拥有互斥锁,而是和新的 goroutine 争夺互斥锁的所有权

一般情况下,新的 goroutine 大概率会争夺到互斥锁,因为它已经调度到 cpu 上运行了,而唤醒的 waiter 大概率会失败。在这种情况下,它被放在等待队列的前面重新等待。

当一个 waiter 超过 1ms 仍然没有获取到互斥锁的时候,就会切换到 starvation 模式

starvation 模式
#

在饥饿模式下,互斥锁的所有权直接从解锁的 goroutine 转交给处于等待队列队首的 waiter。新到来的 goroutine 不会尝试获取锁或者自旋,而是直接添加到等待队列的队尾

当达到以下两个条件是,重新切换回 normal 模式:

  • 队尾只有一个 waiter
  • waiter 等待的时间少于 1ms

对比
#

normal 模式拥有更好的性能,因为一个 goroutine 可以连续获取多次互斥锁,但是可能会造成队尾的 waiter 长时间抢不到锁。

starvation 模式则可以避免队尾延迟问题。

源码剖析
#

sync.RWMutex
#

Go 标准库的 sync/rwmutex.go 实现使用的写优先,即如果一个写操作在等待,那么不管后续有多少个新的读操作,都是写操作优先获取到写锁。但是对于已经获取到了写锁,之后又有新的读锁与写锁在等待的情况,写锁释放后优先唤醒这些已经等待读锁

注意

  • 读写锁不可复制
  • 获取与释放需要成对出现,获取了读锁的协程不能再去获取写锁
  • 互斥锁不可重入,因此写锁的获取不可重入
  • 获取到读锁的协程,不要重入再去获取读锁
In the terminology of the Go memory model,
type RWMutex struct {
    // 用于竞争写锁
	w           Mutex  // held if there are pending writers
    // 写操作信号量,写操作在该信号量中排队等待、被唤醒
	writerSem   uint32 // semaphore for writers to wait for completing readers
    // 读操作信号量,读操作在该信号量中排队等待、被唤醒
	readerSem   uint32 // semaphore for readers to wait for completing writers
    // 记录当前读操作的数量
    // 不管读操作获取到读锁与否,只要调用了 RLock (),都会被计数。
    // 该值使用负数的方式来记录当前是否有写操作(写操作已经获取到了互斥锁 w,但是可能还在等读锁释放,也可能已经获取到了写锁)
	readerCount int32  // number of pending readers
    // 记录写操作请求时已经获取到读锁的读操作数量,即写操作要等待多少个读锁释放。
	readerWait  int32  // number of departing readers
}
// 支持读锁的最大个数
// 该值会在写操作获取写锁时被用来去减去 readerCount,使 readerCount 变成一个负数,用代表有写操作到来了,进而实现阻塞后续新的读操作获取到读锁
const rwmutexMaxReaders = 1 << 30

Related

Go Panic
·3 mins
Golang
c++ 性能
·2 mins
ceph
·6 mins
c++ 无锁队列
·3 mins
Cgroup
·6 mins
Cgroup Cgroup
Cgroup v1 和 v2 的区别
·1 min
Cgroup Cgroup