Go语言 GMP 中的 Work Stealing 机制
Go语言GMP指的是 G(Goroutine)、M(Machine)、P(Processor),Go语言 GMP 中的 Work Stealing 机制
获取 P 本地队列,当从绑定 P 本地 runq 上找不到可执行的 g,尝试从全局链 表中拿,再拿不到从 netpoll 和事件池里拿,最后会从别的 P 里偷任务。P此 时去唤醒一个 M。P 继续执行其它的程序。M 寻找是否有空闲的 P,如果有则将 该 G 对象移动到它本身。接下来 M 执行一个调度循环(调用 G 对象->执行-> 清理线程→继续找新的 Goroutine 执行)
在Go语言的并发模型中,GMP(Goroutine, M(Machine,代表执行Go代码的操作系统线程), P(Processor,代表执行Go代码的虚拟处理器))架构是其核心。其中,Work Stealing(工作窃取)机制是GMP模型用来提高Go程序并发性能的关键技术之一。这种机制允许空闲的M(机器)从其他忙碌的P(处理器)的队列中“窃取”待执行的Goroutine来执行,以此减少线程等待时间,提高资源利用率。
Work Stealing 机制简介
在GMP模型中,每个P都维护一个Goroutine队列,用于存放等待在该P上执行的Goroutine。当某个M执行完其当前P上的所有Goroutine后,它会变成空闲状态。此时,该M会尝试从其他P的队列中“窃取”Goroutine来执行,这个过程就是Work Stealing。
示例(非直接代码实现,因为Work Stealing由Go运行时自动管理)
虽然我们不能直接通过代码来控制或展示Work Stealing的具体实现,但可以通过一个简单的Go程序来观察其效果:
package main
import (
"fmt"
"runtime"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Goroutine %d starting\n", id)
// 模拟工作负载
runtime.Gosched() // 允许其他goroutine运行
fmt.Printf("Goroutine %d finishing\n", id)
}
func main() {
runtime.GOMAXPROCS(4) // 设置P的数量为4
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All goroutines have finished.")
}
在这个例子中,我们创建了20个Goroutine,但只设置了4个P(通过runtime.GOMAXPROCS(4)
)。这意味着在任意时刻,只有4个Goroutine可以直接在P上执行,而其他的Goroutine会被放置在P的队列中等待执行,或者如果P上的Goroutine执行完毕且其他P有等待的Goroutine,则可能发生Work Stealing。
注意
- Work Stealing是Go运行时自动管理的,不需要(也不允许)开发者直接干预。
runtime.Gosched()
函数用于让出当前Goroutine的执行权,让其他Goroutine有机会运行,这有助于在测试或演示中观察并发行为,但不应在实际程序中作为并发控制的手段。- 调整
runtime.GOMAXPROCS
的值可以影响P的数量,进而影响并发性能,但并非总是设置得越高越好,因为过多的P可能会导致线程切换的开销增加。