面试题答案
一键面试Goroutine负载均衡基本原理
- M:N 调度模型:Go语言采用M:N调度模型,即多个Goroutine映射到多个操作系统线程(M)上。其中,Goroutine是用户态轻量级线程,操作系统线程是内核态线程。Go运行时系统负责将Goroutine合理地调度到操作系统线程上执行。
- 全局队列与本地队列:
- 全局队列:存放等待执行的Goroutine。当本地队列已满或者本地队列为空时,会从全局队列中获取Goroutine。
- 本地队列:每个M(操作系统线程)都有一个与之关联的本地队列,M优先从本地队列中获取Goroutine执行,这样可以减少线程间竞争,提高执行效率。
- 工作窃取机制:当某个M的本地队列为空时,它会尝试从其他M的本地队列中窃取Goroutine来执行。这种机制使得计算资源能够更充分地被利用,避免了有的M忙而有的M空闲的情况,实现了负载均衡。
简单场景下通过调度器实现Goroutine合理分配
- 初始化阶段:在程序启动时,Go运行时系统会初始化调度器,创建一定数量的M(默认与CPU核心数相同)和一个全局Goroutine队列。
- 创建Goroutine:当使用
go
关键字创建一个新的Goroutine时,它会被放入到当前M的本地队列中。如果本地队列已满,则会被放入全局队列。 - 调度执行:M从本地队列中取出Goroutine并执行。当M执行完本地队列中的Goroutine后,会尝试从全局队列中获取Goroutine。若全局队列也为空,M会尝试从其他M的本地队列中窃取Goroutine。
- 示例代码:
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
go func(j int) {
fmt.Printf("Goroutine %d is running\n", j)
}(i)
}
time.Sleep(time.Second)
}
在这个简单示例中,创建了10个Goroutine。调度器会将这些Goroutine分配到各个M上执行,由于默认的M数量与CPU核心数相同,调度器会通过上述的本地队列、全局队列和工作窃取机制,尽量将这些Goroutine合理地分配到各个M上,以充分利用CPU资源。