Go协程介绍
参考自《go专家编程》
Go协程所实现的是M:N的线程模型,M个协程运行在N个线程中。
1. MPG模型
Go协程中有三个关键实体:
- M(machine): 工作线程,由操作系统调度。应该就是通常所说的内核线程。
- P(processor): 处理器(非CPU),代表着运行Go代码的必要资源,以及调度goroutine的能力。个人觉得可以当作拥有自主调度权的算法模块,用于工作窃取(work stealing)。
- G(gooutine): Go协程,轻量级用户线程。主要包含执行栈和调度管理器。这里的调度管理器指的是,统一并管理调度资源,等待被调度。
其关系如下图:

关于M的数目:
M的个数是根据实际情况自行创建的,一般稍大于P的个数,为了保证runtime包的内置任务的运行。在运行中不够用时,也会再重新创建一个。
关于P的数目:
P的个数默认为CPU的核数,在IO密集的场景下可以适当提高P的个数。设置方式有两种,例:
设置环境变量:
export GOMAXPROCS=80
runtime.GOMAXPROCS()方法:
runtime.GOMAXPROCS(80)
- 全局
runqueue
队列
全局队列由多个处理器共享,访问通过互斥锁来完成。
处理器P
中的协程G
额外再创建的协程会加入到本地的runqueues
中。
两种情况下会放入全局队列中:1. 本地队列已满 2. 阻塞的协程被唤醒
全局队列会被处理器P
周期性的摘取来调度。
2. 调度策略
- 队列轮转
每个处理器
P
维护着一个协程G
的队列,处理器依次将协程G
调度到M
中执行。
此外,每个P
周期性的查看全局队列中是否有G
将其调度到M
中执行。
(全局队列中的G
主要来自系统调用中恢复的G
。)
- 系统调用
前情提要:1.如一个线程进行系统调用会进入阻塞状态,一个协程进行系统调用也会导致线程进入阻塞状态。2. 前面提到
M
的数量会稍大于P
的数量,多出来的M
在系统调用时就会发挥作用。3. 当M
不够用时,会再创建M
。
当
M0
运行的G0
产生系统调用时,M0
将会释放P
,进而某个冗余的M1
将会获取P
,继而执行P
中所剩下的G
。这时候M0
进入阻塞,而M1
接替M0
的工作。
所以,在开发中一定要避免频繁的系统调用!!!一旦调用结束的速度跟不上调用产生的速度,那么系统资源将会有严重的浪费!
那
G0
的系统调用结束后呢?
此时根据M0
是否能获取到P
来对G0
作出不同的处理:1. 如果有空闲的P
,则获取继续执行G0
。2. 如果没有空闲的P
,将把G0
放入全局队列,等待被其他的P
调度。M0
将进入缓存池。
- 工作量窃取
工作量窃取是为了实现
P
的负载均衡。
当当前P
没有协程调度,并且全局队列中也没有需要调度的协程时,会从另一个正在运行的处理器P
中偷取协程,每次偷取一半。
- 抢占式调度
该机制是为了避免某个协程长时间执行,而阻碍到其他协程被调度。
调度器监控每个协程的执行时间,当其执行时间过长且有其他协程在等待是,会把协程暂停,转而调度等待的协程,达到类似时间片轮转的效果。