0. 前提知识
对比分析进程、线程与协程 (htmonster.xyz)
a.协程的M:N关系
- 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调度
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