Go Sync

Mutex

1
2
3
4
type Mutex struct {
    state int32 // 锁状态和等待计数
    sema  uint32 // 信号量,用于阻塞和环形 goroutine
}

state 通过位掩码管理锁的多种状态:

  • 位偏移为 0: 锁是否被持有(1 表示锁定,0 表示未锁定)
  • 位偏移为 1: 是否有被唤醒的 goroutine
  • 位偏移为 2: 是否处于饥饿状态
  • 位偏移为 3-31: 等待锁的 goroutine 数量

sync.Mutex 有两种工作模式:

  1. 正常模式
    • 等待者按先进先出顺粗获取锁,但新的请求会尝试直接获取锁
    • 新请求锁的 goroutine 可能会“插队”,以提高吞吐量
  2. 饥饿模式
    • 当某个等待者的等待时间超过 1ms 时,会切换到饥饿模式,防止部分 goroutine 长时间等待
    • 新请求的 goroutine 进入等待队列尾部,不在尝试竞争所
    • 锁释放时直接交给等待队列头部的 goroutine
    • 等待队列为空,或等待者等待时间少于 1ms 时,切换回正常模式

Lock

  1. 快速路径
    • 通过 atomic.CompareAndSwapInt32 原子操作尝试直接获取锁
  2. 慢路径
    • 进行有限次的自旋,尝试抢占锁
    • 更新等待者计数,挂起 goroutine,并将其加入等待队列,直到被唤醒

Unlock

  1. 快速路径
    • 通过 atomic.CompareAndSwapInt32 原子操作尝试释放锁
  2. 慢路径
    • 根据模式唤醒等待者
      • 正常模式:优先唤醒新请求的 goroutine
      • 饥饿模式:直接将锁交给队列头部的等待者

RWMutex

1
2
3
4
5
6
7
type RWMutex struct {
	w           Mutex // 复用互斥锁提供的能力
	writerSem   uint32 // 写等待信号量
	readerSem   uint32 // 读等待信号量
	readerCount int32 // 当前正在执行的读操作数量
	readerWait  int32 // 当写操作被阻塞时,等待的读操作数量
}

Once

1
2
3
4
type Once struct {
	done uint32
	m    Mutex
}

sync.Once.Do 是对外暴露的唯一方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 0 {
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

Once 通过原子操作和互斥锁来保证函数只执行一次:

  • 原子操作检查 done 是否为 0
  • 如果是 0,获取锁,并将 done 设置为 1
Licensed under CC BY-NC-SA 4.0
最后更新于 Apr 06, 2022 00:00 UTC