ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ### **什么是 GMP?** > 先解释代表的意思: G:Goroutine,实际上我们每次调用`go func`就是生成了一个 G。 P:Processor,处理器,一般 P 的数量就是处理器的核数,可以通过`GOMAXPROCS`进行修改。 M:Machine,系统线程。 > 再说GMP模型构成 在Go中,**线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上** ![](https://img.kancloud.cn/38/f8/38f83e7e7cffa0dfd2e22795cde4244b_1024x768.png) 1. **全局队列**(Global Queue):存放等待运行的G。 2. **P的本地队列**:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G'时,G'优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。 3. **P列表**:所有的P都在程序启动时创建,并保存在数组中,最多有`GOMAXPROCS`(可配置)个。 4. **M**:线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列**拿**一批G放到P的本地队列,或从其他P的本地队列**偷**一半放到自己P的本地队列。M运行G,G执行之后,M会从P获取下一个G,不断重复下去。 > 最后说调度流程 ![](https://img.kancloud.cn/f8/33/f833f08292f506ea308a4f8972aa0486_1920x1080.png) 1、 GPM 的调度流程从 go func()开始创建一个 goroutine,新建的G优先放入P的本地队列保存待执行的 goroutine(流程 2),当 M 绑定的 P 的的局部队列已经满了之后就会把 goroutine 放到全局队列(流 程 2-1) 2、每个 P 和一个 M 绑定,M 是真正的执行 P 中 goroutine 的实体(流程 3), M 从绑定的 P 中的局部队列获取 G 来执行 3、当 M 绑定的 P 的局部队列为空时,M 会从全局队列获取到本地队列来执行 G (流程 3.1),当从全局队列中没有获取到可执行的 G 时候,M 会从其他 P 的局部队列中偷取 G 来执行(流程 3.2),这种从其他 P 偷的方式称为 work stealing 4、一个M调度G执行的过程是一个循环机制 5、 当 G 因系统调用(syscall)阻塞时会阻塞 M,此时 P 会和 M 解绑即 hand off,并寻找新的空闲的 M,若没有空闲的 M 就会新建一个 M(流程 5.1) 6、当 G 因 channel 或者 network I/O 阻塞时,不会阻塞 M,M 会寻找其他的 G;当阻塞的 G 恢复后会重新进入 runnable 进入 P 队列等待执 行(流程 5.3) 7、 当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列。如果获取不到P,则将G放入全局队列,等待被其他的P调度。然后M将进入缓存池睡眠。 ### **抢占调度方式** >协作式的抢占式调度 程序只能依靠 Goroutine 主动让出 CPU 资源才能触发调度,长时间占用线程,会造成其他Goroutine饥饿 >基于信号的抢占式调度(反应可能迟钝) 通过 sysmon 监控实现的抢占式调度,最快20us,最慢10-20ms ### **G-M-P的数量关系** * M:有限制,默认数量限制是 10000,可调整。(debug.SetMaxThreads 设置) * G:没限制,但受内存影响。 ~~~ 假设一个 Goroutine 创建需要 4k: 4k * 80,000 = 320,000k ≈ 0.3G内存 4k * 1,000,000 = 4,000,000k ≈ 4G内存 以此就可以相对计算出来一台单机在通俗情况下,所能够创建 Goroutine 的大概数量级别。 注:Goroutine 创建所需申请的 2-4k 是需要连续的内存块。 ~~~ * P:受本机的核数影响,可大可小,不影响 G 的数量创建。(**`GOMAXPROCS`**) ### **GMP 调度过程中存在哪些阻塞** * I/O,select * block on syscall * channel * 等待锁 * runtime.Gosched() ### **Sysmon 有什么作用** Sysmon 也叫监控线程,变动的周期性检查 * 释放闲置超过 5 分钟的 span 物理内存; * 如果超过 2 分钟没有垃圾回收,强制执行; * 将长时间未处理的 netpoll 添加到全局队列; 30 * 向长时间运行的 G 任务发出抢占调度(超过 10ms 的 g,会进行 retake); * 收回因 syscall 长时间阻塞的 P;