go的GMP
进程&线程&协程
进程
- os中的 程序运行的实例。资源分配的基本单位。
- 进程占用内存空间
线程
CPU调度的基本单位
每个进程可以拥有多个线程
线程占用CPU的时间
线程使用系统分配给进程的内存,线程之间共享内存
线程的问题
- 线程本身占用资源大
- 线程切换开销大
- 线程的操作开销大
协程将一段程序的运行状态打包,可以在线程之间调度
协程的优点
- 资源利用
- 快速调度
- 超高并发
总结
- 进程用来分配内存空间
- 线程用来里分配CPU时间
- 协程用来精细化利用线程(协程复用线程)
协程的本质
- runtime中,协程的本质是 一个 g结构体
- stack:堆栈地址 (lo、hi)
- gobuf 目前程序运行现场
- atomicstatus:协程的状态
线程的抽象
- runtime中将 操作系统的线程抽象为 m 结构体
- g0:go协程,操作调度器
- curg: current g,目前 线程运行的g
- mOS:操作系统线程的信息
协程是如何执行的
schedule->execute->gogo()汇编实现->业务方法->goexit->g0的 schedule->… 循环往复
- 操作系统并不知道Goroutine的存在
- 操作系统线程执行一个调度循环,顺序执行Goroutine
问题:
顺序执行,无法并发,此时会引发协程的饥饿问题
多线程并发时,会发生抢夺协程队列的全局锁的情况(使用本地队列)
单线程模型
多线程模型
G-M-P调度模型
GMP调度模型用来解决全局锁的争抢问题&顺序执行引发的饥饿问题
全局锁的争抢问题的解决
p结构体 (processor 送料器)
- M与G之间的中介
- p持有一些G,使得每次获取G的时候不用从全局中找
- 大大减少了并发冲突问题
本地全局都没有G,任务窃取,以增强线程的利用率
新建协程
- 随机寻找一个p
- 将新协程放入 P的runnext(插队)
- 若本地队列都满了,放入全局队列
顺序执行引发的饥饿问题的解决
如果协程顺序执行,会有饥饿问题
协程执行中间,将协程挂起,执行其他协程
完成系统调用时挂起,也可也主动(channel 锁。。)挂起
防止全局队列饥饿,本地队列随机抽取全局队列 (每执行goroutine 61 次,从全局队列中取)
问题:
- 永远不主动挂起
- 永远不系统调用
此时还是会造成协程的饥饿
总结
- 基于系统调用和主动挂起,协程可能无法调度
- 基于协作的抢占式调度:业务主动调用 morestack()
- 基于信号的抢占式调度:强制线程调用 doSigPreempt()
协程太多怎么办
- 文件打开数限制
- 内存限制
- 调度开销过大
优化业务逻辑
利用 channel
- 利用 channel 的缓存机制
- 启动协程前,向channel 发送一个空结构体
- 协程结束,去除一个空结构体
1 |
|
使用协程池
总结
进程、线程、协程
协程的本质:
- runtime的角度
- 线程的角度
单线程模型
多线程模型
G-M-P模型
并发问题
饥饿问题
协程太多怎么优化
go的GMP
http://example.com/2024/02/24/go的GMP/