wwqdrh

简介

gmp模型是golang中并发的基本模型。

复用线程

goroutines本身就是运行在一组线程之上,不需要频繁的创建、销毁线程,而是对线程的复用。在调度器中复用线程还有2个体现:

  • work stealing,当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程。
  • hand off,当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行。

goroutine

其中g表示goroutine, 是程序的一个并发基本粒度,每个goroutine都维护着自己的栈区,栈结构是连续栈,是一块连续的内存,g结构体标记着栈区边界的stack信息,stack里记录着栈区边界的高位内存地址和低位内存地址。(有栈)协程可以理解为一个用户态下的线程,在用户态下进行线程(协程)的上下文切换。

用户可以在堆上模拟出协程的栈空间,当需要进行协程上下文切换的时候,主线程只需要交换栈空间和恢复协程的一些相关的寄存器的状态就可以实现一个用户态的线程上下文切换,没有了从用户态转换到内核态的切换成本,协程的执行也就更加高效。

processor

P表示逻辑processor,代表线程M的执行的上下文。

P的最大作用是其拥有的各种G对象队列、链表、cache和状态。

P的数量也代表了golang的执行并发度,即有多少goroutine可以同时运行

对M来说,P提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等

machine

真正的执行计算单元

M在绑定有效的P后,进入调度循环,而且M并不保留G状态(由p保存),这是G可以跨M调度的基础。

启动流程

参考: https://zboya.github.io/post/go_scheduler/#%E6%B7%B1%E5%85%A5golang-runtime%E7%9A%84%E8%B0%83%E5%BA%A6

m0 表示进程启动的第一个线程,也叫主线程。它和其他的m没有什么区别,要说区别的话,它是进程启动通过汇编直接复制给m0的,m0是个全局变量,而其他的m都是runtime内自己创建的。

每个m都有一个g0,因为每个线程有一个系统堆栈,g0 虽然也是g的结构,但和普通的g还是有差别的,最重要的差别就是栈的差别。g0 上的栈是系统分配的栈,在linux上栈大小默认固定8MB,不能扩展,也不能缩小。 而普通g一开始只有2KB大小,可扩展。在 g0 上也没有任何任务函数,也没有任何状态,并且它不能被调度程序抢占。因为调度就是在g0上跑的。

go 关键字创建了G,并插入到P的本地队列或者全局队列,线程M从各个队列中或者从别的P中得到G, 切换到G的执行栈上并执行G上的任务函数,调用goexit做清理工作并回到调度程序,调度程序重新找个可执行的G,并执行,如此反复。 其中 sysmon 会监控整个调度系统,如果某个G长时间占用cpu,会被标记为可抢占。