什么是Go语言的GPM模型呢?
- GPM模型是go语言实现的高并发模型。其中G是Go语言实现的协程数据结构,P是处理器,增加P是为了解决Go1.0版本获取可运行的协程时需要全局锁导致并发冲突严重的问题,M可以认为是线程。
- GPM结构的定义除了调度、GC、系统调用以及cgo外,模型目的是尽最大可能的运行协程,通过P调度协程减少与P绑定的线程的切换,让线程一直占用cpu,为了尽量不切换线程,GPM在语言层面实现了当协程自旋、锁等待、sleep以及网络I/O等中断或内部等待阻塞时只切换协程而不切换线程,如果是文件I/O导致的中断和阻塞,P通过与现有的M解绑,寻找空闲或者新建内核线程的方式去执行协程,所以P会维护一个本地的协程队列以及一个runnext的协程。
- 当P的本地协程队列为空的时候,P就会去全局的协程队列取可执行的协程,如果全局队列为空的话,就会查看其它P中是否有协程可以执行,如果有就会取一部分执行。所以GPM也称为基于偷窃的调度模型。当P的本地协程队列比较多时也会拿出一部分放到全局队列里。所以GPM模型就是尽量快的执行可执行的协程的模型。
- 其实Go的GPM模型实现了语言层面实现的基于协程的调度,跟操作系统调度线程类似。那区别在哪里呢?首先是线程比协程要开销要大,一个线程的堆栈大概要1M,go协程的堆栈设计成可扩容的2KB空间,开销较小,也就是常说的协程比较轻。其次go在语言层面通过调度尽量切换协程而不是进程,节省了线程上下文的开销。
- go的调度也在不断的发展中,1.1是基于偷窃工作模式的调度,但到如果协程一直执行导致其他协程饥饿,所以从1.12~1.13引入的基于编译时期协调的可抢占模式;1.14真正引入的基于信号的真抢占模式的调度模型,其中基于信号的抢占的调度模型主要通过守护协程去监控协程的运行时常来切换调度的正常模式和饥饿模式,如果时正常模式就是上述的调度模型,如果是饥饿模式就将正在运行的协程移除然后执行优先级高的协程。
- GPM是go实现的三个数据结构,今天翻源码发现G里会有一个m的数据结构,m和p是通过指针做多的n对m的对应关系,G,P,M结构也定义了多个状态,其中G中包含stack、gobuf、pc,sp等用于上下文切换的数据结构,M会有比较多的系统调用,P为了可运行状态的G循环队列等,还需要继续深入理解。