Goroutine 调度器负责将就绪的 Goroutine 分发到工作线程上。它通过工作窃取模型实现并发,该模型可将 Goroutine 高效地多路复用到 OS 线程上,同时保持高性能和可伸缩性。本文档描述了调度器的架构、核心组件以及支持 Go 轻量级并发模型的调度机制。
有关运行时内存管理的信息,请参阅 内存管理。有关与调度器交互的垃圾回收器的信息,请参阅 垃圾回收。
Go 调度器建立在三个主要抽象之上
G (Goroutine):Goroutine 是 Go 中的基本执行单元。每个 G 都有自己的堆栈,可以根据需要增长和收缩。Goroutine 是轻量级的(初始堆栈大小仅为几 KB),允许程序创建数千个 Goroutine。
M (Machine):M 代表一个 OS 线程。M 的数量在执行期间可能会变化,但通常受 GOMAXPROCS 的限制。M 必须附加到 P 才能执行 Go 代码,尽管它们可以在不附加 P 的情况下阻塞在系统调用中。
P (Processor):P 是一个逻辑处理器,提供执行 Goroutine 所需的资源。P 的数量由 GOMAXPROCS 确定。每个 P 维护一个本地运行队列,其中包含已准备好执行的 Goroutine。
来源:src/runtime/proc.go22-32 src/runtime/runtime2.go395-757
调度器管理几个关键数据结构来跟踪和调度 Goroutine
每个 Goroutine 由一个 g 结构体表示,包含
每个 P 包含
runnext 字段M 结构跟踪
全局调度器结构 (schedt) 维护
来源:src/runtime/runtime2.go395-507 src/runtime/runtime2.go527-622 src/runtime/runtime2.go636-757 src/runtime/runtime2.go759-829
Goroutine 在其生命周期中会经历各种状态
关键的 Goroutine 状态包括:
_Gidle:刚分配,未初始化_Grunnable:在运行队列中,准备执行_Grunning:当前正在 M 上执行_Gsyscall:正在执行系统调用_Gwaiting:阻塞在同步原语上(例如,通道操作、互斥锁)_Gdead:已退出,可重用这些状态通过原子操作进行管理,以确保线程安全。状态转换由 casgstatus() 等函数处理,这些函数会执行仔细的状态更改,并带有适当的内存屏障。
来源:src/runtime/runtime2.go36-107 src/runtime/proc.go1149-1168
当执行 go 语句时
调度器同时使用本地和全局运行队列
当 P 需要查找工作时
schedule() 函数是调度器的核心。当一个正在运行的 Goroutine 放弃处理器(由于阻塞、系统调用或抢占)时,会调用 schedule() 来查找要运行的新 Goroutine。
当 Goroutine 阻塞时(例如,在通道或互斥锁上),它会
gopark() 并提供原因和唤醒函数当它等待的条件满足时,运行时会调用 goready() 来
来源:src/runtime/proc.go385-411 src/runtime/proc.go441-457 src/runtime/proc.go1062-1083
Go 调度器使用工作窃取来平衡处理器之间的负载
当 P 耗尽工作时
这种工作窃取方法有助于保持良好的负载平衡,同时保留局部性,因为 Goroutine 倾向于在创建它们的 P 上运行。
来源:src/runtime/proc.go127-145 src/runtime/export_test.go127-169
调度器会仔细管理 OS 线程以优化资源使用
当没有可用工作时,M 可能会自行挂起
创建新线程的时机是:
一个重要的性能优化是“自旋”线程的概念
来源:src/runtime/proc.go34-114 src/runtime/proc.go1039-1042 src/runtime/proc.go1062-1083
Go 使用多种机制来抢占正在运行的 Goroutine
Goroutine 在以下时机进行协作式抢占
自 Go 1.14 起,运行时还支持异步抢占
抢占对于公平性至关重要,可确保所有 Goroutine 都能获得执行时间,尤其是在垃圾回收期间。
来源:src/runtime/preempt.go5-13 src/runtime/proc.go407-411
调度器初始化发生在运行时启动过程中
在 schedinit() 中,运行时会
创建主 Goroutine 并进行调度
启动系统监控 Goroutine
用户代码开始执行
来源:src/runtime/proc.go821-921 src/runtime/proc.go147-186
调度器与垃圾回收器紧密协调
这种协调对于在垃圾回收周期中保持良好性能至关重要。
来源: src/runtime/mgc.go22-82 src/runtime/proc.go1144-1146
调度器集成了 Go 的计时器系统
这种集成使得 time.Sleep()、time.After() 及其相关函数能够得到高效实现。
来源: src/runtime/time.go46-117 src/runtime/time.go316-351
Go 调度器的设计通过 G-P-M 模型、工作窃取方法和高效的线程管理,实现了高度高效的 goroutine 管理,并将线程调度的复杂性从程序员那里抽象出来。这使得 Go 程序能够有效地利用可用的 CPU 资源进行扩展,同时保持 goroutine 创建和上下文切换的低开销。
调度器仍处于积极开发阶段,在抢占、公平调度和极端情况下的性能方面不断改进。