前言
在GMP中各个元素在调度器的调度下其实有各种不同的状态转换,比如goroutine就定义了比如_Gidle、_Grunnable、_Grunning、_Gsyscall和_Gwaiting这些状态,在不同的场景中实现这些状态进行不同的转换。
G状态转换
goroutine的状态在runtime/runtime2.go中,_Gidle中被定义为iota,声明为一个无类型整数序号 0,其他定义的枚举逐步进行递增。
代码语言:javascript复制// _Gidle means this goroutine was just allocated and has not
// yet been initialized.
_Gidle = iota // 0
_Grunnable // 1
...
Goroutine 枚举的这些状态有以下几种:
- _Gidle = 0 goroutine刚刚被分配并且还没有被初始化
- _Grunnable = 1 没有执行代码,没有栈的所有权,存储在运行队列中
- _Grunning = 2 可以执行代码,拥有栈的所有权,被赋予了内核线程 M 和处理器 P
- _Gsyscall = 3 正在执行系统调用,没有执行用户代码,拥有栈的所有权,被赋予了内核线程 M 但是不在运行队列上
- _Gwaiting = 4 由于运行时而被阻塞,没有执行用户代码并且不在运行队列上,但是可能存在于 Channel 的等待队列上。若需要时执行ready()唤醒
- _Gdead = 6 没有被使用,可能刚刚退出,或在一个freelist;也或者刚刚被初始化;没有执行代码,可能有分配的栈也可能没有;G和分配的栈(如果已分配过栈)归刚刚退出G的M所有或从free list 中获取
其中有几个状态是不用去理会的:_Genqueue_unused(目前未使用)_Gcopystack=8 (不在运行队列上) _Gpreempted=9 (没有执行用户代码) _Gscan=10 GC (没有执行代码,可以与其他状态同时存在 )
M状态转换
M本身其实是无状态的,不过我们可以根据M是否空闲,执行代码,休眠等这些情况将M的列举一些当前行为。
- 自旋中:(spinning) M正在从运行队列中获取G,这个时候M拥有一个P
- 执行代码:M正在执行go代码,此时M拥有一个P
- 执行原生代码中:M正在执行原生代码或者阻塞的syscall,此时的M不会拥有P
- 休眠中:M发现无待运行的G时会进入休眠,并添加到空闲M链表中
P状态转换
p的底层结构和状态枚举跟g在同一个文件中,主要定义了一下几种状态:
代码语言:javascript复制const (
_Pidle = iota
_Prunning
_Psyscall
_Pgcstop
_Pdead
)
type p struct {
id int32
status uint32 // one of pidle/prunning/...与状态相关
...
runq [256]guintptr //p本地队列 默认容量为256个G
runnext guintptr //下一个将要运行的G
}
- Pidle:当前p尚未与任何m关联,处于空闲状态
- Prunning:当前p已经和m关联,并且正在运行g代码
- Psyscall:当前p正在执行系统调用
- Pgcstop:当前p需要停止调度,一般在GC前或者刚被创建时
- Pdead:当前p已死亡,不会再被调度
在P创建之初或结束gc后,会被置为Pgcstop状态,在完成初始化之后,会进入Pidel状态,进入该状态后的P可被调度器调度,当P与某个M相关联时,会进入到Prunning状态,当其执行系统调用时,会进入到Psyscall状态,当P应为全局P列表的缩小而被删除时会进入Pdead状态,不会再进行状态流转和调度。当正在执行的P由于某些原因停止调度时,会统一流转成Pidle空闲状态,等待调度,避免线程饥饿。