一、简述go语言的GMP调度模型
G:一个G代表一个goroutine,协程的本质是用户态的线程,用户对其有控制权限,内存占用少,切换代价低。
M:内核态线程,一个M代表了一个内核线程,等同于系统线程,所有的G都要放在M上才能运行。
P:逻辑处理器,可以承载若干个G,并且使这些G适时的与M对接,一个P代表了M所需的上下文环境。P的个数取决于设置的GOMAXPROCS, go新版本默认使用最大内核数,比如你有8核处理器,那么P的数量就是8
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的本地队列取走一半。
二、golang 的协程:Goroutine 阻塞的话,是不是对应的M也会阻塞
分以下几种情况:
由于原子、互斥量或通道操作调用导致Goroutine 阻塞
该情况的G阻塞不会阻塞内核线程M,因此不会导致M的上下文切换 而至涉及到G的切换
由于网络请求文件IO 、 time.sleep、ticker计时器操作导致Goroutine 阻塞
该情况的G阻塞不会阻塞内核线程M,因此不会导致M的上下文切换 而至涉及到G的切换
由于调用阻塞的系统 导致的Goroutine 阻塞
这种情况会导致 M 阻塞,内核会进行M的切换,而与M关联的P不会等待M的阻塞,这意味着当前P下的所有G都无法执行,所以此时P会与当前M解除关联,转而关联到另一个内核线程M2,M2可能是新创建的内核线程,也可能时之前空闲的内核线程被唤醒来执行P的G。
当M进行 系统调用阻塞 结束的时候,这个G会尝试获取一个空闲的 P 执行,并放入到这个 P 的本地队列。如果获取不到 P,那么这个线程 M 变成休眠状态, 加入到空闲线程中,然后这个 G 会被放入全局队列中。
三、如何阻塞一个Goroutine
方法1:从一个不发送数据channel中接收数据
方法2:向不接收数据的channel中发送数据
方法3:从空的channel中接收数据
方法4:向空channel中发送数据
方法5:使用select