深入 Go 底层原理(八):sync 包的实现剖析
1. 引言
除了 channel
,Go 还提供了传统的、基于共享内存的同步原语,它们都位于 sync
包中。在高并发场景下,正确且高效地使用这些工具至关重要。理解它们的底层实现,能帮助我们避免死锁、竞争条件,并写出性能更优的并发代码。
本文将深入 sync
包,重点剖析 Mutex
、WaitGroup
和 Pool
这三个最常用工具的内部工作原理。
2. sync.Mutex
(互斥锁)
Mutex
用于保护临界区,确保同一时间只有一个 goroutine 可以访问共享资源。
核心数据结构:
type Mutex struct {state int32 // 锁的状态 (锁定、唤醒、饥饿等)sema uint32 // 信号量,用于唤醒等待的 goroutine
}
state
: 这是一个复合状态字段,通过位操作(bit manipulation)来存储多个信息:Locked Bit: 标记锁是否已被持有。
Woken Bit: 标记是否有 goroutine 已被唤醒。
Starving Bit: 标记锁是否进入“饥饿”模式。
Waiter Count: 记录等待锁的 goroutine 数量。
工作模式: Mutex
有两种工作模式:
正常模式 (Normal Mode):
当一个 goroutine 请求锁时,如果锁未被持有,它会立即获得锁。
如果锁已被持有,它会通过几次**自旋(Spinning)**尝试获取锁。自旋是指在不挂起 goroutine 的情况下,进行忙等待循环。这对于锁很快会被释放的场景能提升性能。
如果自旋后仍未获取到锁,goroutine 会被加入等待队列并挂起。
当锁被释放时,会唤醒等待队列中的一个 goroutine。被唤醒的 goroutine 和新来的 goroutine 会公平竞争。
饥饿模式 (Starving Mode):
如果一个 goroutine 等待锁的时间超过了 1ms,
Mutex
会切换到饥饿模式。在饥饿模式下,锁的所有权会直接交接给等待队列中的队头 goroutine,新来的 goroutine 即使自旋也无法获取锁。
目的:防止等待队列中的 goroutine 因为不断有新来的“插队者”而长时间“饿死”,保证了公平性。
3. sync.WaitGroup
WaitGroup
用于等待一组 goroutine 全部执行完毕。
核心数据结构:
type WaitGroup struct {noCopy noCopy // 保证 WaitGroup 不被复制state1 [3]uint32
}
state1
: 同样是一个复合字段,存储了两个核心信息:Counter: 一个 32 位的计数器,记录需要等待的 goroutine 数量。
Waiter Count: 等待
Wait()
的 goroutine 数量。
工作原理:
Add(delta int)
: 直接通过原子操作(atomic operations)给Counter
增加delta
。Done()
: 等价于Add(-1)
,通过原子操作将Counter
减一。当Counter
变为 0 时,它会唤醒所有在Wait()
处等待的 goroutine。Wait()
:检查
Counter
是否为 0。如果是,立即返回。如果不为 0,它会通过信号量机制将当前 goroutine 挂起,直到
Counter
变为 0 时被Done()
唤醒。
4. sync.Pool
(临时对象池)
Pool
用于缓存和复用临时对象,以减轻 GC 压力。它特别适用于那些需要频繁创建和销毁、生命周期短暂的对象的场景。
核心设计:
Pool
是并发安全的。Pool
中的对象随时可能被 GC 回收。你不应该依赖Pool
来存储有状态的、持久的对象。每个 P (Processor) 都有自己的本地池:为了避免锁竞争,
sync.Pool
为每个 P 都维护了一个私有的对象列表(poolLocal
)。
工作原理:
Get()
:优先尝试从当前 P 的本地私有池 (private) 中获取对象。此操作无锁。
如果私有池为空,尝试从当前 P 的本地共享池 (shared) 中获取。此操作也无锁。
如果本地共享池也为空,尝试从其他 P 的共享池中窃取一个对象。
如果窃取也失败,最后会调用
Pool
中预设的New
函数(如果存在)来创建一个新对象。
Put(x interface{})
:将对象
x
放入当前 P 的本地私有池中。此操作无锁。
GC 与 Pool:
在每次 GC 开始时,
sync.Pool
会清理其缓存。它会移除所有 P 的本地共享池中的对象,并将它们移动到一个“受害者缓存”中。私有池中的对象则会被直接丢弃。这保证了Pool
不会无限增长,也解释了为什么池中的对象是不稳定的。