关于goroutine
2018-05-17 本文已影响0人
涵仔睡觉
goroutine是什么?
进程是“程序执行的一个实例” ,担当分配系统资源的实体。进程创建必须分配一个完整的独立地址空间。
线程是进程的一个执行流,独立执行它自己的程序代码。用户级线程主要缺点在于对引起阻塞的系统调用的调用会立即阻塞该线程所属的整个进程。内核实现线程则会导致线程上下文切换的开销跟进程一样大,所以折衷的方法是轻量级线程(Lightweight)-- 协程。
协程是轻量级的线程,一个进程可轻松创建数十万计的协程。子程序其实是协程的特例,子程序其实就是函数。协程可以调用其它协程,彼此对称、平等的。协程的起始处是第一个入口点,在协程里,返回点之后是接下来的入口点。协程的生命期完全由他们的使用的需要决定。一旦创建完线程,你就无法决定他什么时候获得时间片,什么时候让出时间片了,你把它交给了内核。而协程编写者可以有一是可控的切换时机,二是很小的切换代价。
go中的Goroutine, 普遍认为是协程的go语言实现。
goroutine调度
goroutine调度主要基于三个基本对象上:G、M、P
- G代表一个goroutine对象,每次调用go的时候,都会创建一个G对象;
- M代表一个内核级线程,每次创建一个M的时候,都会有一个底层线程创建;所有的G任务,最终还是在M上执行;
-
P代表一个调度器,每一个运行的M都必须绑定一个P,就像线程必须在么一个CPU核上执行一样。
P的个数就是GOMAXPROCS(n)(最大256),启动时固定的,一般不修改; M的个数和P的个数不一定一样多(最大10000);每一个P保存着本地G任务队列,也有一个全局G任务队列,如下图
image
调度过程:
- 调用GOMAXPROCS(n)创建n个P(n <= 256)。
- 创建一个M,用于系统监控和管理,维护P中所有G的计数,G执行后递增;若一个G的计数不增加则说明P一直在执行这个G,如果超过一定时间(10ms),则在该G上设置标记,在执行该G时会先检查标记,然后中断这个G,放到队列末尾,执行下一个G。
- 用关键字go会创建一个G,放入到当前M的P中(每个G都有自己的栈,其sched字段维护了栈地址以及程序计数器等基本调度信息)。
- 当G太多,并且有空闲的P,而且M个数小于P时,就创建一个M(新线程)(一个M对应于一个P)。
- 每个P中均有一个待处理的G队列(本地G队列),另外还有一个全局G队列。
- 对于每对M、P,根据调度算法,M从P的本地G队列中取出一个G进行操作;若P中没有G,则从全局G队列中取出一个G操作;若全局G队列中没有G,则去其他P中取一半G到自己的P中,再取出一个G操作;如果其他P中也没有G,那么证明当前没有任务在执行,那么M将放弃P进入休眠。
- 若P中有太多G,M会唤醒其他休眠中的M参与工作;若没有休眠的M,则会创建新的M。
- 对G调用park函数,将使G放弃CPU,进入waiting状态,如对waiting状态的G调用ready,G将会再被执行。
- gosched调度函数也会让G放弃CPU,G状态变为runable,放入全局G列表中。