1. 概念梳理

1.1. 进程

进程是计算机中已运行的程序示例,拥有独立的内存空间、资源和系统信息。每个进程都是互相独立的,进程间通信需要通过特定的机制(如管道、消息队列、共享内存、信号、套接字、信号量、内存映射文件、远程过程调用(RPC)等),核心点如下:

(1)独立的内存空间

(2)进程之间相互隔离

(3)创建和销毁开销较大

(4)通常用于操作系统调度的基本单位

1.2. 线程

通常语义中的线程,指的是内核级线程,核心点如下:

(1)是操作系统最小调度单元

(2)创建、销毁、调度交由内核完成,cpu需要完成用户态与内核态的切换

(3)可充分利用多核,实现并行

1.3. 协程

协程,又称为用户级线程,核心点如下:

(1)与线程存在映射关系,为M:1

(2)创建、销毁、调度在用户态完成,对内核透明,所以更轻

(3)从属同一个内核级线程,无法并行;一个协程阻塞会导致从属同一下称的所有协程无法执行

1.4. Goroutine

Goroutine,经Golang优化后的特殊”协程“,核心点如下:

(1)与线程存在映射关系,为M:N;

(2)创建、销毁、调度都在用户态完成,对内核透明,足够轻便

(3)可以利用多线程、实现并行

(4)通过调度器的斡旋,实现和线程间的动态绑定和灵活调度

(5)栈空间大小可动态扩缩,因地制宜

2. GMP模型

2.1. g

  • g,即goroutine,是golang中对协程的抽象

  • g有自己的运行栈、生命周期状态、以及执行的任务函数(用户通过go func指定);

  • g需要绑定在m上执行,在g视角中,可以将m理解为它的cpu

2.2. m

  • m即machine,是golang中对线程的抽象

  • m需要和p进行结合,从而进入gmp调度体系之中

  • m的运行目标始终在g0和g之间切换-当运行g0是执行的是m的调度流程,负责寻找合适的”任务“,也就是g;当运行g时,执行的时m获取到的”任务“,也就是用户通过go func启动的goroutine

2.3. p

  • p即processor,是golang中的调度器

  • p可以理解为m的执行代理,m需要与p绑定后,才会进入到gmp调度模式中;因此p的数量决定了g最大并行数量(可由用户通过GOMAXPROCS进行设定,超过CPU核数时无意义)

  • p是g的存储容器,其自带一个本地g队列(local run queue,简称lrq),承载着一系列等待被调度的g

2.4. gmp

(1)M是线程的抽象;G是goroutine;P是承上启下的调度器

(2)M调度G前,需要和P绑定

(3)全局有多个M和多个P,但同时并行的G的最大数量等于P的数量

(4)G的存放队列有三类:P的本地队列、全局队列和wait队列

(5)M调度G时,优先取P本地队列,其次取全局队列,后取wait队列,这样的好处是,取本地队列时,可以接近于无锁化,减少全局锁竞争

(6)为防止不同P的闲忙差异过大,设立work-stealing机制,本地队列为空的P可以尝试从其他P本地队列偷取一半G补充到自身队列中