以下是基于 go1.14.2 进行分析
在了解源码之前,因为涉及到goroutine的调度,所以先了解一下go语言的GMP模型。M相当于线程,一个M对应一个P控制器,P控制器负责goroutine调度在M上。这里涉及2个goroutine队列,一个是P本地的队列runq,这个队列存储待运行的goroutines,存储方式是用循环队列进行存储,在代码中是用数组实现的。另外一个队列是全局的schedt,用链表实现的,这里个数没有进行限制。当本地队列为空时,会向全局队列进行调度。
以下时简单的GMP模型:
一、案例分析
下面是一个简单的协程实例,使用go关键字开启一个协程执行funTest()
1 | func funTest() { |
我们通过汇编来看一下具体的执行流程:
1 | go tool compile -S main.go |
这里我们只看main函数的汇编流程:
SP:指向当前栈帧的栈顶
BP:指向当前栈帧的栈底,函数栈的起始位置。这个寄存器占8个字节,跳过8字节后才是函数栈上局部变量的内存
从汇编看来,使用CALL关键字,go调用会跳转到newproc处理函数
二、源码分析
经过GC编译,会把内存的大小以及go需要执行的函数指针传进来。
下图的fn即为需要执行的参数
1 | //go:nosplit |
newproc1实现了调用goroutine具体流程
1 | /* |
在上面的函数中会获取一个空闲的goroutine,以下是具体实现:
1 | // goroutine所在处理器的调度器或者全局调度器sched.gFree 列表中获取 runtime.g 结构体;找一个空闲goroutine |
将获取的goroutine放在运行队列具体实现功能:
1 | // 可能是全局的运行队列,也可能是处理器的运行队列 |