面试题答案
一键面试数据结构
在Go语言中,互斥锁 Mutex
的底层数据结构定义在 src/sync/mutex.go
中,其结构如下:
type Mutex struct {
state int32
sema uint32
}
- state:这是一个32位整数,用于表示互斥锁的状态。其低3位有着不同的含义:
- 第0位(最低位)表示是否有锁被持有(0表示未持有,1表示持有)。
- 第1位表示是否有被唤醒的 goroutine 正在等待获取锁。
- 第2位表示是否处于饥饿模式(0表示正常模式,1表示饥饿模式)。
- sema:这是一个信号量,用于阻塞和唤醒等待获取锁的 goroutine。当一个 goroutine 无法获取锁时,它会通过
runtime_Semacquire
函数等待在这个信号量上,而当锁被释放时,会通过runtime_Semrelease
函数唤醒等待在信号量上的 goroutine。
加锁操作具体步骤
- 快速检查与尝试获取锁:
- 首先,
Lock
方法会尝试快速获取锁。它会检查state
的最低位是否为0(即锁是否未被持有)。如果未被持有,它会尝试通过atomic.CompareAndSwapInt32
原子操作将state
的最低位设置为1,以此来获取锁。如果这个操作成功,说明获取锁成功,加锁操作完成。
- 首先,
- 正常模式下的竞争处理:
- 如果快速获取锁失败,说明存在竞争。此时,
Lock
方法会根据当前锁的状态和模式进行处理。在正常模式下,它会先将state
的第1位设置为1,表示有 goroutine 在等待锁。然后,通过runtime_Semacquire
函数将当前 goroutine 阻塞在sema
信号量上。 - 当持有锁的 goroutine 释放锁时(
Unlock
操作),它会检查是否有等待的 goroutine(通过检查state
的第1位)。如果有,它会通过runtime_Semrelease
函数唤醒一个等待的 goroutine。被唤醒的 goroutine 会尝试获取锁,获取成功后会将state
的第1位清零。
- 如果快速获取锁失败,说明存在竞争。此时,
- 饥饿模式处理:
- 如果在正常模式下,一个等待的 goroutine 等待时间过长(超过1毫秒),锁会进入饥饿模式。在饥饿模式下,当持有锁的 goroutine 释放锁时,它会直接将锁交给等待队列中最前面的 goroutine(而不是让新的 goroutine 有机会竞争)。
- 处于饥饿模式下的 goroutine 一旦获取到锁,它会持续持有锁,直到没有其他等待的 goroutine 或者它等待的时间超过1毫秒。如果它等待时间超过1毫秒,它会将锁切换回正常模式。
总的来说,Go语言的互斥锁通过 state
字段记录锁的状态和等待情况,通过 sema
信号量来阻塞和唤醒等待锁的 goroutine,以实现高效的并发控制。