Lock
类型
互斥锁 (Mutex)
1var mu sync.Mutex
2mu.Lock()
3// 临界区代码
4mu.Unlock()
- 同一时刻只有一个goroutine能持有锁
- 零值可用(未锁定的mutex)
- 不可重入(同一goroutine重复Lock会死锁)
读写锁 (RWMutex)
1var rwmu sync.RWMutex
2rwmu.RLock() // 读锁
3rwmu.RUnlock() // 读操作
4
5rwmu.Lock() // 写锁
6rwmu.Unlock() // 写操作
- 读-读不互斥
- 读-写互斥
- 写-写互斥
- 适合读多写少的场景
其他同步原语
sync.Once
:确保操作只执行一次sync.WaitGroup
:等待一组goroutine完成sync.Cond
:条件变量sync.Map
:并发安全的mapatomic
包:原子操作
特性
公平
源码
1const (
2 mutexLocked = 1 << iota // mutex is locked
3 mutexWoken
4 mutexStarving
5 mutexWaiterShift = iota
6
7 // Mutex fairness.
8 //
9 // Mutex can be in 2 modes of operations: normal and starvation.
10 // In normal mode waiters are queued in FIFO order, but a woken up waiter
11 // does not own the mutex and competes with new arriving goroutines over
12 // the ownership. New arriving goroutines have an advantage -- they are
13 // already running on CPU and there can be lots of them, so a woken up
14 // waiter has good chances of losing. In such case it is queued at front
15 // of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,
16 // it switches mutex to the starvation mode.
17 //
18 // In starvation mode ownership of the mutex is directly handed off from
19 // the unlocking goroutine to the waiter at the front of the queue.
20 // New arriving goroutines don't try to acquire the mutex even if it appears
21 // to be unlocked, and don't try to spin. Instead they queue themselves at
22 // the tail of the wait queue.
23 //
24 // If a waiter receives ownership of the mutex and sees that either
25 // (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,
26 // it switches mutex back to normal operation mode.
27 //
28 // Normal mode has considerably better performance as a goroutine can acquire
29 // a mutex several times in a row even if there are blocked waiters.
30 // Starvation mode is important to prevent pathological cases of tail latency.
31 starvationThresholdNs = 1e6
32)
这些常量用于表示互斥锁的不同状态,通过位掩码方式存储在锁的内部状态中:
mutexLocked
(1 « 0 = 1) - 表示锁已被占用mutexWoken
(1 « 1 = 2) - 表示有 goroutine 被唤醒mutexStarving
(1 « 2 = 4) - 表示锁处于饥饿模式mutexWaiterShift
(值为3) - 表示等待 goroutine 数量的位移量
工作模式
正常模式 (Normal Mode)
- 等待的 goroutine 按照 FIFO 顺序排队
- 被唤醒的 waiter 需要与新到达的 goroutine 竞争锁
- 新到达的 goroutine 有优势:因为它们已经在 CPU 上运行,数量可能很多
- 如果被唤醒的 waiter 竞争失败,会被重新放到队列前面
- 如果 waiter 等待超过 1ms 还获取不到锁,则切换到饥饿模式
饥饿模式 (Starvation Mode)
- 锁的所有权直接从解锁的 goroutine 移交给队列前面的 waiter
- 新到达的 goroutine 不会尝试获取锁,即使锁看起来是未锁定的
- 新到达的 goroutine 也不会自旋,而是直接排到队列尾部
- 当以下条件满足时,切换回正常模式:
- 当前 waiter 是队列中最后一个
- 当前 waiter 等待时间少于 1ms(starvationThresholdNs 定义了 阈值时间)
设计考量
- 性能优先:正常模式下,即使有阻塞的 waiter,goroutine 也可以连续多次获取锁,性能更好
- 防止饥饿:饥饿模式防止了尾部延迟的极端情况,确保没有 goroutine 会无限期等待
- 自适应:根据实际情况在两种模式间自动切换,平衡性能和公平性
这种设计使得 Go 的互斥锁在大多数高性能场景下表现良好,同时在极端情况下也能保证公平性。
TryLock
尝试获取锁,获取不到也不堵塞
1// TryLock tries to lock m and reports whether it succeeded.
2//
3// Note that while correct uses of TryLock do exist, they are rare,
4// and use of TryLock is often a sign of a deeper problem
5// in a particular use of mutexes.
6func (m *Mutex) TryLock() bool {
7 old := m.state
8 if old&(mutexLocked|mutexStarving) != 0 {
9 return false
10 }
11
12 // There may be a goroutine waiting for the mutex, but we are
13 // running now and can try to grab the mutex before that
14 // goroutine wakes up.
15 if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
16 return false
17 }
18
19 if race.Enabled {
20 race.Acquire(unsafe.Pointer(m))
21 }
22 return true
23}
Go1.18 引入的新特性,但是 rsc 并不推荐使用它,他认为 TryLock 会鼓励设计者对锁进行不精确的思考,这可能最终会成为 race(竞态) 的根源。