0. 前提知识
a.协程的M:N关系
为什么是M:N,而不是1:1或者N:1?
- N:1关系(一个内核线程thread 管理着多个用户协程co-routine)
- 优点:切换开销小
- 缺点:一个挂了就全挂了,一锅端
- 1:1 关系 (一个内核线程thread 管理着一个用户协程co-routine)
- 缺点:创建、删除、切换开销大
b.Golang中的协程Goroutine
Goroutine其实就是Go语言中的协程。但是不同的是,Golang在语言层次,从runtime
,系统调用
等方面对协程进行了封装。
1. 相关定义
- G (Goroutine) 协程: 发布出去的任务,由go关键字创建 【携带任务】
- **P **(Processor) 处理器:调度G到M上,包含包含了gorountine的资源以及可运行的G的队列【分配任务】
- M (Machine thread) 内核线程:G要放在M上才能运行【寻找执行任务】
2. GMP模型
两个队列
- 全局队列(Global Queue):存放等待运行的G
- **P的本地队列:**也是存放等待运行的G,但是数量有限(256)。
新创建的G’, 优先加入P的本地队列。若队列满了,会将本地队列中的一半移入到全局队列。
两个调度器
- Goroutine调度器:将G分配给M
- OS调度器: 将M分配到CPU上
两个数量
- P列表:程序启动时候创建,由a. 环境变量
$GOMAXPROCS
或者是由b.runtime
的方法GOMAXPROCS()
决定 - M列表: 程序运行时没有空闲M时候创建。由a. go程序启动时最大数量默认10000 b.
runtime/debug
中的SetMaxThreads
函数设置 c. M阻塞了,会创建新的M决定。
3. Goroutine调度
Go调度本质是把大量的goroutine分配到少量线程上去执行,并利用多核并行,实现更强大的并发
2.1设计策略
a. 复用线程
避免频繁的创建、销毁线程,而是对线程的复用
work stealing
机制: 线程中没有G时候,从别的绑定P中偷取Ghand off
机制: 本线程阻塞时候,释放P给别的线程
b. 并行利用
最多有GOMAXPROCS
个线程分布在多个CPU上同时运行
c. 抢占
在Go中,一个goroutine最多占用CPU 10ms
d. 全局队列
当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G。
2.2 go func()
调度过程
go func()
创建一个G- G加入到队列中
- 先尝试加入
局部队列
- 局部队列满了会尝试加入
全局队列
- 先尝试加入
- P从队列中获取G,M与G是1:1的关系
- M会从本地队列中取出一个可执行的G
- 入股本地队列为空,会从其它的MP组合中偷取一个可执行的G
- M循环调度G
- M执行
G.func()
- G如果发生了syscall或则其余阻塞操作,M会阻塞,runtime把线程M从P中摘除(detach)
- 创建一个新M或者复用一个休眠M
- M来服务于这个P
- 销毁G
- 返回
2.3 go func
调度流程
M0
: 启动后的主线,M0负责执行初始化操作和启动第一个G, 在之后M0就和其他的M一样了。G0
:每次启动一个M都会第一个创建的gourtine,G0仅用于负责调度的G,G0不指向任何可执行的函数, 每个M都会有一个自己的G0
4. Go调度器执行场景
4.1 G1创建G2
P拥有G1,M1获得P,M开始运行G1,G1中创建了G2,G2优先加入了P1的本地队列。
4.2 G1执行完毕
G0执行完毕退出,切换到G0,G0调度G2到M1中
4.3 G2开辟过多的G
G2创建过多的G导致P1的本地队列满了
4.4 P1本地队列负载均衡
当新创建的G无法再加入本地队列时候,将新G和本地队列中一半的G移入全局队列。
4.5 G2本地队列未满加入G8
创建G8的时候,本地队列P1还没满,则加入到P1的本地队列中。
4.6 唤醒休眠M
当创建G的时候,会首先尝试唤醒其它空闲的P-M组合去执行G
4.7 M2尝试批量获取G
自旋线程M2会尝试从全局队列中获得一批G到P2本地队列中
4.8 M2偷取P1本地队列中的G
当全局队列和P2本地队列中没有G的时候,会尝试去P1本地队列中偷取G
4.9 关闭多余的M
当许多的M在自旋(运行),为了不浪费CPU资源,会限制最大数量的自旋线程。
4.10 G阻塞时候P切换M绑定
当G阻塞时候,M2与P2立即解绑。如果还有G或者还有空闲M,会唤醒一个M与其绑定,否则则会加入空闲P队列。
4.11 G非阻塞时候P-M切换绑定
当G非阻塞系统调用时候,同场景10。但是不同的是,当系统调用返回时候,会尝试恢复绑定原来的P。当失败时候则G加入全局,M变为空闲状态。