这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记
go语言GMP调度模型
G:一个G代表一个goroutine,协程的本质是用户态的线程,用户对其有控制权限,内存占用少,切换代价低。
M:内核态线程,一个M代表了一个内核线程,等同于系统线程,所有的G都要放在M上才能运行。
P:处理器,用来管理和执行goroutine,一个P代表了M所需的上下文环境。P的个数取决于设置的GOMAXPROCS,go新版本默认使用最大内核数,比如你有8核处理器,那么P的数量就是8
三者关系:G需要绑定在M上才能运行,M需要绑定P才能运行。 M的数量和P不一定匹配,可以设置很多M,M和P绑定后才可运行,多余的M处于休眠状态。
系统会通过调度器从全局队列找到G分配给空闲的M,P会选择一个M来运行,M和G的数量不等,P会有一个本地队列表示M未处理的G,M本身有一个正在处理的G,M每次处理完一个G就从本地队列里取一个G,并且更新P的schedtick字段,如果本地队列没有G,则从全局队列一次性取走G/P个数的G,如果全局队列里也没有,就从其他的P的本地队列取走一半。
Goroutine阻塞的话,是不是对应的M也会阻塞
这要分为以下四种情况 1、由于原子、互斥量或通道操作调用导致Goroutine阻塞
该情况的G阻塞不会阻塞内核线程M,因此不会导致M的上下文切换而只涉及到G的切换。
2、由于网络请求和文件IO操作导致Goroutine阻塞
该情况的G阻塞不会阻塞内核线程M,因此不会导致M的上下文切换而只涉及到G的切换。
3、由于调用time.Sleep或者ticker计时器导致Goroutine阻塞 这种情况和情况2相似,不会阻塞当前M。
4、由于调用阻塞的系统调用时导致的goroutine阻塞 这种情况会导致当前M阻塞,内核会进行M的切换。而与当前M关联的当前P不会等待M的阻塞,因为这意味当前P下的所有G都无法执行,所以此时P会与当前M解除关联,转而关联到另一个内核线程M2,M2可能时新创建的内核线程,也可能时之前空闲的内核线程被唤醒来执行P的G。