Golang中runtime.schedule()如何获取p和parallelism
Golang中runtime.schedule()如何获取p和parallelism
你好!根据源代码,schedule() 函数代表一轮调度。
从这里开始,我们寻找本地 p 队列(有时为了公平性也会从 sched 的全局队列中寻找),然后找到可运行的 g 并执行它。
这个函数如何知道在哪个 p 内部执行搜索?启动这个函数的顺序是什么?它是由每个附加到 p 的 m 对应的多个操作系统线程启动的吗?
1 回复
更多关于Golang中runtime.schedule()如何获取p和parallelism的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go调度器的runtime.schedule()函数中,获取当前P和并行度(parallelism)的机制如下:
获取当前P(Processor)
在schedule()函数中,通过getg().m.p.ptr()获取当前M绑定的P:
// runtime/proc.go
func schedule() {
// 获取当前goroutine
gp := getg()
// 通过当前goroutine的M获取绑定的P
_g_ := getg()
_p_ := _g_.m.p.ptr() // 获取当前M绑定的P
// 如果没有绑定P,则尝试获取一个
if _p_ == nil {
// 尝试从空闲P列表获取或窃取工作
}
// 后续调度逻辑使用_p_进行本地队列搜索
}
获取并行度(parallelism)
并行度通过gomaxprocs变量获取,该变量在运行时初始化时设置:
// runtime/proc.go
func schedule() {
// 获取GOMAXPROCS值(最大并行度)
procs := gomaxprocs
// 或者通过全局sched获取
sched := &schedinit
maxprocs := sched.maxprocs
}
调度启动顺序
- 初始化阶段:
// runtime/proc.go
func schedinit() {
// 设置GOMAXPROCS
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
// 创建P数组
allp = make([]*p, procs)
// 初始化每个P
for i := 0; i < procs; i++ {
pp := allp[i]
// 初始化P的本地队列等
}
}
- M与P绑定:
// 每个M在执行schedule()前需要绑定P
func acquirep(pp *p) {
// 将P绑定到当前M
getg().m.p.set(pp)
pp.m.set(getg().m)
pp.status = _Prunning
}
- 调度循环:
// 每个M的执行循环
func mstart() {
// 系统线程入口
// ...
schedule()
}
// schedule()被每个M循环调用
func schedule() {
// 获取当前M绑定的P
_p_ := getg().m.p.ptr()
// 调度逻辑:从_p_的本地队列、全局队列或其他P窃取工作
// ...
// 执行找到的goroutine
execute(gp)
}
执行模型示例
// 简化的调度循环示意
func schedulerLoop(m *m) {
// M需要先绑定P
if m.p == 0 {
// 尝试获取空闲P或创建新P
p := pidleget()
if p == nil {
p = procresize()
}
acquirep(p)
}
// 调度循环
for {
schedule() // 每轮调度
// schedule()内部:
// 1. 通过getg().m.p.ptr()获取当前P
// 2. 从该P的本地队列获取G
// 3. 如果本地队列为空,从全局队列或其他P窃取
// 4. 执行找到的G
}
}
关键点
- P的获取:每个M通过
getg().m.p.ptr()获取当前绑定的P - 并行度控制:
gomaxprocs决定同时执行的最大P数量 - 调度触发:每个操作系统线程(M)独立运行调度循环,但必须绑定P才能执行Go代码
- 工作窃取:当本地P队列为空时,会尝试从全局队列或其他P窃取工作
这种设计确保了在GOMAXPROCS限制内的真正并行执行,同时通过工作窃取机制保持负载均衡。

