Skip to content

Go 中的模式

Go 的运行时和标准库展示了干净、实用的模式实现。

模式Go 中的位置作用
协作调度runtime/proc.go带协作抢占点的 goroutine 调度
位掩码os/types.goFileMode — 通过 iota 类型常量实现的 Unix 权限标志
对象池sync/pool.gosync.Pool — per-P 本地池,无锁快速路径,广泛用于 fmtencoding/json
工作窃取runtime/proc.gostealWork — goroutine 调度器通过 runqsteal/runqgrab 从其他 P 的运行队列窃取
空闲链表runtime/mfixalloc.gofixalloc — 固定大小空闲链表分配器,侵入式 mlink 节点
LRU 缓存groupcache lru/lru.goCache 结构体,双向链表 + 哈希表,Brad Fitzpatrick 作品
一致性哈希groupcache consistenthash.go带虚拟节点的哈希环,用于分布式缓存
限流器x/time/rate扩展标准库中的令牌桶限流器
信号量x/sync/semaphore加权信号量——被 errgroup 内部使用以限制 goroutine 并发
享元 (Flyweight)sync/pool.gosync.Pool 作为享元——Get() 返回缓存实例而非分配新对象,Put() 归还以复用
Arena 分配器arena/arena.go实验性 arena 分配器——New[T]() 分配,Free() 一次性释放绕过 GC

模式如何协作:Goroutine 调度

当你执行 go func() 时,多个模式协同工作,在少量 OS 线程上运行数百万个 goroutine:

go func() { ... }
1
空闲链表

运行时通过固定大小分配器(fixalloc)分配 goroutine 栈。无需每个 goroutine 调用 malloc/free — 直接从空闲链表获取节点。

2
对象池

sync.Pool 提供 per-P 本地池用于临时对象(如 fmt 缓冲区)。每个 P 有私有池 → 无锁竞争。

3
协作式调度

goroutine 运行到抢占点(函数调用、channel 操作或异步抢占信号)。然后调度器选择下一个 goroutine。

4
工作窃取

当 P 的本地运行队列为空时,从其他 P 的队列窃取 goroutine。stealWork() 一次性拿走受害者队列的一半,保持所有核心忙碌。

5
信号量

当 goroutine 需要有界并发(如带限制的 errgroup)时,加权信号量控制同时运行的数量。

GMP 模型(Goroutine、M 线程、P 处理器)是粘合剂:每个 P 拥有本地运行队列、空闲链表分配器和 sync.Pool 分片。工作窃取只在 P 耗尽时才启动。这种设计意味着快速路径上大部分操作是无锁的,竞争只在窃取时发生——而窃取本身就是设计中的罕见情况。

延伸阅读

基于 MIT 许可证发布。