Go语言scheduler调度器

Go语言scheduler调度器

在前面一节中简单介绍了golang的调度模型-GPM模型,介绍了他们各自的作用。这篇文章就来看看他们的源码结构。

Go版本:go1.13.9

M结构体

M结构体是OS线程的一个抽象,主要负责结合P运行G。
它里面有很多字段,差不多有60个字段,我们看看里面主要的字段意思。
/src/runtime/runtime2.go

看看几个比较重要的字段:
g0:用于执行调度器的g0
gsignal:用于信号处理
tls:线程本地存储的tls
p:goroutine绑定的本地资源

 

P结构体

一个M要运行,必须绑定P才能运行goroutine,M阻塞时,P会被传给其他M。

/src/runtime/runtime2.go

其他的一些字段就是gc,trace,debug信息

 

G结构体

G就是goroutine。主要保存 goroutine 的所有信息以及栈信息,gobuf结构体:cpu里的寄存器信息,以便在轮到本 goroutine 执行时,知道从哪里开始执行。

/src/runtime/runtime2.go

gobuf

gobuf结构体用于保存goroutine的调度信息,主要包括CPU的几个寄存器的值。

要了解寄存器是什么,可以点击这里:
寄存器1
寄存器2

/src/runtime/runtime2.go

 

调度器sched结构

所有的gorouteine都是被调度器调度运行,调度器持有全局资源

sched

/src/runtime/runtime2.go

 

gQueue

/src/runtime/proc.go

 

一些重要全局变量

/src/runtime/proc.go

/src/runtime/runtime2.go

在程序初始化时,这些变量都会被初始化为0值,指针会被初始化为nil指针,切片初始化为nil切片,int被初始化为数字0,结构体的所有成员变量按其本类型初始化为其类型的0值。

 

调度器初始化

调度器初始化有一个主要的函数 schedinit(), 这个函数在 /src/runtime/proc.go 文件中。
函数开头还把初始化的顺序给列出来了:

// The bootstrap sequence is:
//
//  call osinit
//  call schedinit
//  make & queue new G
//  call runtime·mstart
//
// The new G calls runtime·main.

开头的这个函数getg(),跳转到了 func getg() *g  ,定义这么一个形式,什么意思?
函数首先调用 getg() 函数获取当前正在运行的 g,getg() 在 src/runtime/stubs.go 中声明,真正的代码由编译器生成。

注释里也说了,getg 返回当前正在运行的 goroutine 的指针,它会从 tls 里取出 tls[0],也就是当前运行的 goroutine 的地址。编译器插入类似下面的代码:

原来是这么个意思。

调度器初始化大致过程:
M初始化            –>   P 初始化          – -> G初始化
mcommoninit           Procresize                newproc
——————————————————-
allm 池                     allp池                       g.sched执行现场
p.runq 调度队列

MPG初始化过程。 M/P/G 初始化:mcommoninit、procresize、newproc,他们负责M资源池(allm)、p资源池(allp)、G的运行现场(g.sched) 以及调度队列(p.runq)

 

调度循环

所有的工作初始化完成后,就要启动运行器了。准备工作做好了,就要启动mstart了。
这个工作在汇编语言中也可以看出来

/src/runtime/asm_amd64.s  (在linux下)

 

参考

  1. 雨痕 《Go语言学习笔记》 https://book.douban.com/subject/26832468/
  2. 深度解密Go语言 https://qcrao.com/2019/09/02/dive-into-go-scheduler/
  3. https://blog.csdn.net/u010853261/article/details/84790392

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注