goroutine的创建
通过go tool compile -S main.go 我们来看看发生了什么?
汇编过于太长,只截取其中一部分。
我们看到有一行CALL runtime.newProc()的函数被调用了,这是通过起关键字go func创建goroutine的入口
通过
gp:=getg()
来获取g0,然后通过systemstack
切到g0栈,再执行newproc1
,newproc1就是我们的goroutine诞生的地方。我们来看看newproc1干了什么:
- 如果我们的func为nil,则报错
- 如果我们的func的参数太多,则报错
- 获取本地的p
- 尝试从本地的p的gfree上获取一个不用的g,或者从全局的p中获取
- 没有获取到空闲的g的时候,则去创建一个g,默认大小为2k
- 新创建的g的状态gdead,防止gc错扫面
- 将新的g加入全局的allg列表中
- 初始化这个g的一些参数
- 将我们的func和这个g绑定
- 初始化完成后,将这个g的状态设置为runable,处于可以被执行状态
- 通过runqput将g放入p的队列,p的队列满的话,就放入全局队列
- 尝试通过wakep唤醒一个正处于休眠的p来执行
至此一个新的goroutine创建完毕。
gopark(goroutine的休眠)
goroutine的切换涉及到一个很重要的函数gopark。
gopark的作用:
- 将running状态的goroutine设置为waiting
- 解除goroutine和当前工作线程M的关系
- 获取一个新goroutine来运行
gopark函数的关键就是mcall函数调用的park_m。
park_m:
- gopark通过mcall将当前线程的堆栈切换到g0的堆栈
- 保存当前goroutine的上下文(pc、sp寄存器->g.sched)
- 在g0栈上,调用park_m
- 将当前的g从running状态设置成waiting状态
- 通过
dropg
来解除m和g的关系
func dropg() {
_g_ := getg()
setMNoWB(&_g_.m.curg.m, nil)
setGNoWB(&_g_.m.curg, nil)
}
复制代码
- 最后通过schedule来发起新一轮的调度
schedule()->execute()->gogo()
,gogo尝试从gobuf中恢复出协程执行状态并跳转到上一次指令处继续执行。
goready (goroutine的唤醒)
与gopark相反的,有一个goready的函数,它的作用就是唤醒waiting状态的goroutine
还是通过systemstack切到g0栈,在g0栈上发起调度
- 获取goroutine的状态
- 将waiting状态的goroutine切换到runable状态
- 尝试唤起一个p来执行当前goroutine
本文来自:掘金
感谢作者:假装懂编程
查看原文:go高级进阶:goroutine的创建、休眠与恢复
相关阅读 >>
更多相关阅读请进入《Go》频道 >>

Go语言101
一个与时俱进的Go编程知识库。