早期操作系统是单进程的,那么执行进程只能是顺序执行的
- 顺序执行,效率比较低
- 当前进程的阻塞会带来CPU浪费,因为CPU没办法处理后面待处理的进程
后来出现了多进程操作系统,多进程处理下,会涉及到基于CPU调度器对进程分配时间片,宏观上三个进程在并发,实际还是顺序执行,这么看来,解决了进程阻塞带来对CPU浪费
但是这种方式会带来进程切换成本(进程切换涉及到拷贝复制)的浪费。
当进程数量越多,切换成本也就越浪费,CPU的一部分浪费在了切换上。
除此之外,进程和线程占用内存是很大的,进程虚拟内存占用可能是GB级别的,线程是MB级别的。
总的来说,高消耗CPU和高内存占用都是需要解决的问题。
怎么解决呢?线程分为用户空间和内核空间
线程也会涉及用户态和内核态之间的切换,如果把线程一分为二,即用户线程和内核线程,用户线程负责业务上的处理,内核线程负责操作系统层面的处理。
我们把用户线程称为协程,内核线程还称为线程。为了进一步提高效率,我们通过协程调度器来为内核线程绑定多个协程。
协程调度器通过轮询的方式与协程配合
这看起来是1:N的关系,但是这样有个弊端:如果有一个协程阻塞了,考虑到轮询的机制,那就说真阻塞了,所以一个线程是不够的。
所以进一步演化为一种M:N的关系
什么是GMP?- G是Goroutine的缩写,相当于操作系统的进程控制块(process control block)。它包含:函数执行的指令和参数,任务对象,线程上下文切换,字段保护,和字段的寄存器。
- M是一个线程,每个M都有一个线程的栈。如果没有给线程的栈分配内存,操作系统会给线程的栈分配默认的内存。
- P(处理器,Processor)是一个抽象的概念,不是物理上的CPU。当一个P有任务,需要创建或者唤醒一个系统线程去处理它队列中的任务。
线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上。
- 全局队列(Global Queue):存放等待运行的G。
- P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G’时,G’优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。
- P列表:所有的P都在程序启动时创建,并保存在数组中,最多有GOMAXPROCS(可配置)个。
- M:线程想运行任务就得获取P,从P的本地队列获取G,P队列为空时,M也会尝试从全局队列拿一批G放到P的本地队列,或从其他P的本地队列偷一半放到自己P的本地队列。
M运行G,G执行之后,M会从P获取下一个G,不断重复下去。
Goroutine调度器,即Processor,它是和OS调度器是通过M结合起来的,每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU的核上执行。
参考资料https://www.kancloud.cn/aceld/golang



