(上级问题并发 问题逻辑- 并发机制->GMP->goroutine调度)
goroutine(协程)和线程
说线程之前,我们先说进程和线程的关系,
首先线程是进程的执行体,拥有一个执行入口,以及进程虚拟地址空间中分配的栈—包括用户栈和内核栈,操作系统会记录线程控制信息,
而线程会在获得cpu时间片信息后执行 ,cpu中栈指针、指令指针等寄存器都到切换到对应的线程;
而协程就是线程创建的执行体,其控制信息由线程记录,由于用户程序不能操作内核空间,所以只能给协程分配用户栈,而操作系统对这个信息一无所知。
总结:协程是一种”用户态线程”。
goroutine如何调度
说到goroutine的调度就不得不提MPG(GMP)模型
- M: 线程
- P: 执行go协程运行所必需的资源,关联本地可运行G的队列
- G: go协程
G,M,P的个数
- M 的数据默认启动的时候是 10000,内核很难支持这么多线程数,所以整个限制客户忽略,M 一般不做设置,设置好 P,M 一般都是要大于 P。
- P 的数量一般建议是逻辑 CPU 数量的 2 倍。——使用runtime.GOMAXPROCS() 变量数一般是cpu核数的2倍,具体看性能分析。
- G 的个数理论上是无限制的,但是受内存限制。
一开始go创建时只有MG,多个M分担多个G的执行任务时(想象整体G是一个全局队列,多个M执行G时),就会因为频繁 加锁解锁(不能让其他线程执行到同一个G)而发生等待,影响并发性能。所以,使用P,p有一个本地队列,这样只要M关联到P,就可以直接运行队列中待执行的G,不用每次从全局队列中争抢了。
(注意一个问题:程序中主进程结束不管协程有没有运行完,都会结束,所以一般使用wg sync.WaitGroup wg.Add() go func(){ wg.Done} wg.Wait())
总结GMP调度流程:
- 线程M想运行任务就需得获取 P,即与P关联。
- 然从 P 的本地队列(LRQ)获取 G
- 若LRQ中没有可运行的G,M 会尝试从全局队列(GRQ)拿一批G放到P的本地队列,
- 若全局队列也未找到可运行的G时候,M会随机从其他 P 的本地队列偷一半放到自己 P 的本地队列。
- 拿到可运行的G之后,M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。