Golang调度器GMP学习笔记(一)
调度器的由来#
单进程时代的问题#
-
单一执行流程,计算机只能一个任务一个任务处理
-
进程阻塞所带来的CPU时间浪费
多进程、多线程的问题#
-
设计变得复杂
-
进程/线程的数量越多,切换成本就越大
-
多线程伴随着同步竞争(锁、资源冲突等)
-
-
多进程、多线程的壁垒
-
高内存占用
-
高CPU调度消耗
-
协程的问题#
-
N:1
-
无法利用多个CPU
-
出现阻塞的瓶颈
-
-
1:1
-
和多线程/多进程模型无异
-
切换协程成本代价反而变得昂贵
-
-
M:N
-
能够利用多核
-
过于依赖协程调度器的优化和算法
-
早期调度器#
M:goroutine协程
N:thread线程
M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的。
问题:
-
创建、销毁、调度G都需要每个M获取锁,这就形成了激烈的锁竞争
-
M转移G会造成延迟和额外的系统负载
-
系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销
GMP模型#
-
全局队列(Global Queue):存放等待运行的G
-
P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G'时,G'优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列
-
P列表:所有的P都在程序启动时创建,并保存在数组中,最多有
GOMAXPROCS
(可配置)个 -
M:线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去
P和M何时会被创建?
-
P何时创建:在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P
-
M何时创建:没有足够的M来关联P并运行其中的可运行的G。比如所有的M此时都阻塞住了,而P中还有很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M
参考:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性