Go GMP调度模型

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Go 调度器与GMP模型

前奏

关于计算机的简单描述

----------- |-----------------用户应用程序(微信、QQ、浏览器)
| 计算机 |-----------操作系统(linux、windows、mac os)
----------- |-----硬件(CPU、内存、显卡、鼠标、键盘、显示器)

由以上文字可以看出计算机由一堆硬件构成操作系统运行于硬件的上层狭义上来考虑操作系统是为了协调硬件、管理硬件、提供服务。用户应用程序运行于操作系统之上。那么没有操作系统我们想运行自己的软件能否实现呢答案是可以的裸机程序需要自己开发调度策略管理硬件。

进程、线程、协程

进程是程序的运行实例,将程序加载进内存就产生了一个进程。
线程是进程的一部分也是操作系统调度给CPU的最小执行单元一个进程可以包含多个线程。线程可以分为用户态线程和内核态线程其分别运行于操作系统用户空间和内核空间。用户态和内核态的出现是为了隔离资源的使用权限。用户态的线程无法直接操作硬件等资源如想要操作硬件资源需要切换到内核态去完成执行。用户态到内核态的切换有3种情况系统调用中断异常。当执行完内核态的代码后会自动回退到用户态继续执行其代码。
协程(co-routine)可以理解成是更加细粒度的线程其调度策略由应用程序自行指定。Go语言中的协程称作goroutine其主要在用户态下产生并被调度。协程是依托于线程执行的只有将协程绑定到线程上才能执行其代码。

通常情况下32位操作系统中进程的虚拟内存空间可以达到4GB、64位操作系统下其虚拟内存空间会很充裕。不同进程间的资源不可见。操作系统在调度进程切换时会保存相当多的上下文信息极其耗费CPU时间。

线程的栈空间大概为2M该大小无法修改同一个进程中的共享数据对该进程中的所有线程都是可见的。若想让一个线程内的全局变量只对该线程内可见可以使用Thread Local Storage线程局部存储TLS技术。线程的切换需保存线程的上下文信息(前者说单个进程内的线程切换时当多个进程间的线程切换时还要保存进程的相关信息)同时由用户态转换为内核态做切换工作十分耗费CPU时间。

Go协程的栈空间是可变的可由2k(默认初始值)增长到1GB(64位操作系统)或250M(32位操作系统)。栈扩容后会将原栈空间的所有值全部复制到新栈中栈中变量的地址会发生改变。这也许间接的导致了Go语言的普通指针不支持运算操作。协程间可以共享进程的全局变量。协程间切换由协程调度器在用户态完成只需保存几个寄存器的值即可非常省时高效。

3种线程模型

1:1 1个用户态线程与1个内核态线程绑定

N:1 多个用户态线程与1个内核态线程绑定

M:N 多个用户态线程与多个内核态线程绑定

Go 语言协程调度器使用的是M:N的模型即由Go协程调度器将M个协程(非线程)与N个内核线程进行动态绑定执行代码N一般为CPU的逻辑核心数。
Go语言淡化了线程的概念。写代码时只能感知到协程的存在当然这对程序设计是没有任何影响的。

GM调度模型

早期的Go语言版本使用的是GM调度模型。G代表goroutineM代表machine(即内核线程)。同时维护了一个全局的G队列。这个队列在访问时需要加一把大锁。

加锁的全局运行队列 [G,G,G,G,G,G,G,G,G,G,G,G,G]

G1-----------\
G2-----------|---------------M0
G3-----------/
G4---------------------------M1
G5---------------------------M2
G6---------------------------M3
如上所示6个goroutine绑定到了4个内核线程上。若G6在M3线程上运行结束则M3会去全局队列解锁然后拿来一个新的G绑定执行。若G1在运行时发生了阻塞则M0就进入了阻塞状态G2G3需等到阻塞结束才有机会被执行到。可以看出早期的Go调度器是非常粗糙的其调度效率并不高。

GMP调度模型

后期的Go语言改进了GM调度模型加了一个虚拟的逻辑处理器P(procese)G仍代表goroutineM仍代表machine(即内核线程)。

加锁的全局运行队列 [G,G,G,G,G,G,G,G,G,G,G,G,G]

Ggoroutine

M0<->P0: 加锁的本地运行队列 [G,G,G,G,G]

M1<->P1: 加锁的本地运行队列 [G,G]

M2<->P2: 加锁的本地运行队列 [G]

M3<->P3: 加锁的本地运行队列 [G,G,G]

某一时刻1个P只能与1个M进行绑定。一个G可能在多个P的本地运行队列中流转也有可能被移动到全局运行队列。
M在执行G的时候会优先从与其绑定的P的结构中查看其runnext指针是否指向一个可被执行的G若该指针有效则直接执行该G否则说明runnext指向空(0)此时则去P的本地队列中去查找是否有可执行的G若有则加锁拿一个G后解锁然后去执行若没有则去加锁的全局队列中取寻找是否有可用的G若有则从全局队列中窃取按P的个数等分全局运行队列G数量的G窃取来的G的数量不超过P的本地队列的长度的一半而后执行。若全局队列中没有G则去随机选择一个P在其本地运行队列窃取G数量为目标队列中已有的G的数量的一半执行。若其他P中仍没有G则检查网络协程是否有可以被调度的G。为了保证全局队列中的G会被执行到在P被调度61次以后会将本地队列中一般的G加入全局队列并从全局队列中获得G加入本地队列。若M绑定的P没有可执行的G则将M与P解绑M进入阻塞状态。

main与g0,m0,sysmon

main协程是Go程序的主协程main方法是Go程序的入口。在每个M中有一个g0协程g0协程其实就是负责调度策略的协程g0协程会调度其他的G执行。Go程序在启动时会有g0协程负责GC的协程以及一个额外的负责监控Go状态的sysmon线程运行而后才是main协程及用户的其他协程。g0协程是被一个m0线程所启动的g0被启动后m0便负责M应有的责任。

Go调度器

Go 语言协程调度器支持抢占式调度和协作式调度默认是抢占式调度策略。在Go语言的发展中调度器的调度算法及实现仍有很大的提升空间相信Go语言会越做越好。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: go