如何设计响应式网站,美食网站 源码,北京网站建设最便宜的公司哪家好,网站推广方案中每天一篇Go语言干货#xff0c;从核心到百万并发实战#xff0c;快来关注魔法小匠#xff0c;一起探索Go语言的无限可能#xff01; 在 Go 语言中#xff0c;Goroutine 是一种轻量级的并发执行单元#xff0c;它使得并发编程变得简单高效。而 Goroutine 的高效调度机制是… 每天一篇Go语言干货从核心到百万并发实战快来关注魔法小匠一起探索Go语言的无限可能 在 Go 语言中Goroutine 是一种轻量级的并发执行单元它使得并发编程变得简单高效。而 Goroutine 的高效调度机制是 Go 语言在并发处理上的一大亮点。本文将深入剖析 Go 语言的 Goroutine 调度器从 GMP 模型到 Work Stealing 算法带你一探究竟。
一、Goroutine 调度器的背景
Go 语言的并发模型基于 Goroutine它是一种轻量级的线程由 Go 运行时runtime自动管理。Goroutine 的调度机制决定了多个 Goroutine 如何高效地映射到操作系统线程上执行。与传统线程Thread相比具有以下优势
内存占用仅2KB线程默认1MB上下文切换成本仅0.2μs线程约1μs创建速度达到微秒级线程需毫秒级
但是Goroutine本质上是用户态线程需要依赖GMP调度器将其映射到操作系统线程M执行。
二、GMP 模型Goroutine 调度的核心
Goroutine 的调度基于 GMP 模型即 GoroutineG、MachineM和 PProcessor的组合。这个模型实现了从 N:1用户态线程到内核态线程到 N:M用户态线程到内核态线程的灵活映射的调度。
1. GoroutineG
Goroutine 是用户定义的协程它代表了并发执行的任务。创建 Goroutine 的底层方法是newproc 函数它会将 Goroutine 放入 P 的本地队列中。如果本地队列已满则放入全局队列中。
go func() {// 任务代码
}()
2. MachineM
Machine 代表操作系统线程是 Go 运行时与操作系统交互的接口。Go 运行时会根据需要创建和销毁 M以适应不同的并发场景。
3. ProcessorP
Processor 是 Go 运行时中的调度上下文它负责管理 Goroutine 的调度。每个 P 有自己的本地队列用于存储待执行的 Goroutine。
4.GMP模型示意图 通过该示意图可以了解到完整的GMP模型关系。
全局队列本地队列Processor调度器管理满了的情况下将会把新创建的Goroutine加入到全局队列中排队等待执行。
本地队列存放即将执行的Goroutine每个processor中的goroutine将并行执行。
GoroutineG图中的每个圆形图标G就是代表一个Groutine。
ProcessorP管理当前调度器内的本地队列并负责管理 Goroutine 的调度用于存储待执行的 Goroutine。processor和groutine是N:M的关系。
内核线程M每个M代表了一个内核线程操作系统调度器负责把内核线程分配到CPU的核上执行。
三、调度器的工作流程
Go 调度器的核心任务是从队列中获取可执行的 Goroutine并将其分配给可用的 M 执行。
1. 本地队列
每个 P 都有一个本地队列调度器会优先从本地队列中获取 Goroutine 执行。如果本地队列为空则会尝试从全局队列获取。
2. 全局队列
全局队列是所有 P 共享的队列用于存储未被分配的 Goroutine。当本地队列为空时调度器会尝试从全局队列中获取 Goroutine。
3. Work Stealing工作窃取
如果本地队列和全局队列都为空调度器会采用 Work Stealing 算法从其他 P 的本地队列中“偷取” Goroutine。这种策略可以实现线程之间的负载均衡。
func runqsteal(pp, p2 *p, stealRunNextG bool) *g {t : pp.runqtailn : runqgrab(p2, pp.runq, t, stealRunNextG)if n 0 {return nil}n--gp : pp.runq[(tn)%uint32(len(pp.runq))].ptr()if n 0 {return gp}h : atomic.LoadAcq(pp.runqhead)if t-hn uint32(len(pp.runq)) {throw(runqsteal: runq overflow)}atomic.StoreRel(pp.runqtail, tn)return gp
}
四、抢占式调度
Go 调度器采用抢占式调度策略以防止某个 Goroutine 占用过多 CPU 资源。在 Go 1.14 之后调度器在任何安全点都可以进行抢占。
func findRunnable() (gp *g, inheritTime, tryWakeP bool) {mp : getg().mtoppp : mp.p.ptr()// 每61次调度周期就检查一次全局G队列if pp.schedtick%61 0 sched.runqsize 0 {lock(sched.lock)gp : globrunqget(pp, 1)unlock(sched.lock)if gp ! nil {return gp, false, false}}// 本地队列if gp, inheritTime : runqget(pp); gp ! nil {return gp, inheritTime, false}// 全局队列if sched.runqsize ! 0 {lock(sched.lock)gp : globrunqget(pp, 0)unlock(sched.lock)if gp ! nil {return gp, false, false}}// 工作窃取if mp.spinning || 2*sched.nmspinning.Load() gomaxprocs-sched.npidle.Load() {if !mp.spinning {mp.becomeSpinning()}gp, inheritTime, _, _, _ : stealWork(now)if gp ! nil {return gp, inheritTime, false}}return nil, false, false
}
五、协作式调度
除了抢占式调度Go 还支持协作式调度。Goroutine 可以通过调用runtime.Gosched() 函数主动让出 CPU 的执行权。
func main() {go func() {for i : 0; i 10; i {fmt.Println(Goroutine 1)runtime.Gosched()}}()for i : 0; i 10; i {fmt.Println(Goroutine 2)}
}
六、总结
Go 语言的 Goroutine 调度机制通过 GMP 模型和 Work Stealing 算法实现了高效的并发执行。抢占式调度和协作式调度策略确保了 Goroutine 的公平执行而 Work Stealing 算法则进一步提高了多核处理器上的负载均衡。通过这些机制Go 运行时能够高效地利用系统资源实现高性能的并发编程。