GMP g指的是协程、m指的是线程、p指的是处理器
调度:指的是调度cpu,在多线程、多进程的场景,保证每个线程、进程看上去都在执行。
进程、线程的创建、销毁都比较占用cpu的调度时间,而且线程的调度还要考虑到多线程之间资源的竞争、锁的问题等。
用户态线程指的是程序里面用户创建的线程,内核态线程指的是cpu调用的线程。
这个时候就引入了协程的概念,可以在用户态的线程来管理协程,这样就减少了内核态线程调用用户态线程的切换。
golang中协程的调用都是在用户态的线程中进行实现的。如果一个协程阻塞,这个线程中的其他协程会被runtime转移到其他可以用的线程中,或者创建新的线程。(这个也是造成golang进程中,线程数量过大的原因)。
golang初始的gm版本(已废弃)没有p,调用还是依靠的cpu:
1. g有一个全局的g队列,每一个m获取g时,都需要加锁进行互获取。
2. 在m中如果有新创建的协程g1时,最好是g1在m中执行,这种关联是不合理的。或者在m1上执行也是不合适的。
3. golang在多线程之间的切换导致线程的阻塞与取消阻塞也增加了系统的开销。
最新版本的gmp:
引入了最新的处理器p,每个p中包含了可以运行的g队列。m想运行g,必须先获取p。
重点:
- 全局队列,用来存储需要运行的g列表
- p本地队列,本地队列存储的也是可以运行的g队列,但是总量不会超过256个,如果在m中创建新的g,会把新创建的g放到p的本地队列中。如果队列满了,会把本地队列的一半放入到全局队列中。
- p列表,所有的p都会在程序启动时创建,最多有GOMAXPROCS个p。
- m运行g,会从p的本地队列中进行获取,如果本地队列为空,会从全局队列G中获取一部分g队列,或者从其他的p本地队列中偷取一部分过来。
整体逻辑:
在进程启动后,会根据GOMAXPROCS创建需要的处理器队列
m在获取需要运行的g时,会首先获取一个未被关联的p,如果未获取到则m休眠。在获取到p后,会先从p的本地g队列中获取可运行的g,如果本地g队列中没有可运行的g,会优先从其他p中偷取一部分g,如果未获取到,则会从全局队列中获取一部分可运行的g。整个执行过程是一个循环的过程。
m如果在调用过程中,出现阻塞,则runtime会把m从当前的p中摘除,重新关联一个空闲的线程或者重新创建一个线程。
m是复用线程的,如果当前线程关联的p没有可运行的g,不会销毁线程,会从其他p或者全局队列获取可运行g。
G0指的是每个M启动时,创建的第一个协程,仅用来负责G的调度。
M0时程序启动时创建的第一个线程,用来启动第一个G。这个M0对应的实例会在全局变量runtime中,其他新创建的M会放在heap上。