浅析Go中的MPG模式(一)
Goroutine(协程)
首先了解一下协程(goroutine)这个东西
1、Go线程(主线程,一般称为线程,有的大佬们也直接叫进程),也可理解为进程。是一个物理级线程,重量级的,非常耗费CPU资源
2、一个线程上可以有多个协程(goroutine),协程是轻量级的线程(go对于线程进行的特殊处理)。逻辑态,消耗资源相对少。按照消耗资源可以这样排序:进程 >= 主线程 > 子线程 >= 协程
3、Go协程特点
(1)有独立的栈空间
(2)共享程序堆空间
(3)调度由用户(程序)控制
(4)协程是轻量级的线程
4、主线程退出了,协程即使未执行完毕也会停止,退出程序。
MPG模式:
1、解释一下MPG含义:
M(Machine):操作系统的主线程
P(Processor):协程执行需要的资源(上下文context),可以看作一个局部的调度器,使go代码在一个线程上跑,他是实现从N:1到N:M映射的关键
G(Gorountine):协程,有自己的栈。包含指令指针(instruction pointer)和其它信息(正在等待的channel等等),用于调度。一个P下面可以有多个G
2、**P的数量可以通过GOMAXPROCS()来设置,**他其实代表了真正的并发度,即有多少个goroutine可以同时运行。P同时也维护着G(协程)的队列(称之为runqueue进程队列)。Go代码中的M每有一个语句被执行,P就在末尾加入一个G(从runqueue队列中取出来的),在下一个调度点(P),就从runqueue队列中取出G。(图片来源于网络,太多类似的忘了在哪截的)
3、P可以在OS线程(主线程,或者是M)被阻塞时,转到另一个OS线程(M)!Go中的调度器保证有足够的线程来运行所有的P。当启用一个M0中的G0被sysCall(系统调用)的时候,M0下面的P转给另一个线程M1(可以是创建的,也可以是原本就存在的)。M1接受了P(包括P所带的runqueue的队列里面所有状态的G,但不包括已经被syscall的G0),继续运行。而M0会等待执行syscall的G0的返回值。当G0的syscall结束后,他的主线程M0会尝试取得一个P来运行G0,一般情况下,他会从其他的M里面偷一个P过来,如果没有偷到的话就会把G0放到一个Global runqueue(全局进程队列)中,然后把自己(M0)放进线程池或者转为休眠状态。
4、Global runqueue是各个P在运行完自己的本地的goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个Global runqueue上的goroutine,否则全局runqueue上的goroutine可能得不到执行而饿死。。。
9、当P中的runqueue队列里面的G全部执行完毕之后,他会偷取未执行完的P中的G(偷取一半)!
上面MPG中的第3和4可以参考:Go并发原理—RyuGou博客