• M:内核线程
  • P:处理器,用来执行 goroutine,它维护了本地可运行队列
  • G:goroutine,代码和数据结构
  • S:调度器,维护M和P的信息

设计历史

最开始Go只有M和G,还是单线程,即用线程调度自己的协程,利用全局锁去管理调度。

后面添加了多线程调度,充分利用了多CPU的并发优势。但是还是抢占式,只有一个锁和调度器。其实和我们常规的多线程开发没有什么区别。

终于,引入了P,增加了中间层。

Goroutine MPG模型_系统调用

P主要提供线程需要的上下文环境,负责调度线程上的等待队列,也就是管家的角色。

他会扫描自己底下的所有goroutine,根据信号来决定谁执行。

有意思的是,他自己名下没有需要执行的gorourtine,就会抢其他调度器的。

Goroutine MPG模型_内核线程_02

关于切换:

遇到系统调用的阻塞,保留上下文环境,让M执行其他G即可:

Goroutine MPG模型_Go_03

注意:网络 IO、channel 操作、锁这样的阻塞,线程并不会让出时间片。所以go还是会死锁的。

Goroutine 的状态

// defined constants
const (
// G status
//
// Beyond indicating the general state of a G, the G status
// acts like a lock on the goroutine's stack (and hence its
// ability to execute user code).
//
// If you add to this list, add to the list
// of "okay during garbage collection" status
// in mgcmark.go too.
//
// TODO(austin): The _Gscan bit could be much lighter-weight.
// For example, we could choose not to run _Gscanrunnable
// goroutines found in the run queue, rather than CAS-looping
// until they become _Grunnable. And transitions like
// _Gscanwaiting -> _Gscanrunnable are actually okay because
// they don't affect stack ownership.

// _Gidle means this goroutine was just allocated and has not
// yet been initialized.
_Gidle = iota // 0
//刚刚被分配并且还没有被初始化

// _Grunnable means this goroutine is on a run queue. It is
// not currently executing user code. The stack is not owned.
_Grunnable // 1
//没有执行代码,没有栈的所有权,存储在运行队列中

// _Grunning means this goroutine may execute user code. The
// stack is owned by this goroutine. It is not on a run queue.
// It is assigned an M and a P (g.m and g.m.p are valid).
_Grunning // 2
//可以执行代码,拥有栈的所有权,被赋予了内核线程 M 和处理器 P

// _Gsyscall means this goroutine is executing a system call.
// It is not executing user code. The stack is owned by this
// goroutine. It is not on a run queue. It is assigned an M.
_Gsyscall // 3
//正在执行系统调用,拥有栈的所有权,没有执行用户代码,被赋予了内核线程 M 但是不在运行队列上

// _Gwaiting means this goroutine is blocked in the runtime.
// It is not executing user code. It is not on a run queue,
// but should be recorded somewhere (e.g., a channel wait
// queue) so it can be ready()d when necessary. The stack is
// not owned *except* that a channel operation may read or
// write parts of the stack under the appropriate channel
// lock. Otherwise, it is not safe to access the stack after a
// goroutine enters _Gwaiting (e.g., it may get moved).
_Gwaiting // 4
//由于运行时而被阻塞,没有执行用户代码并且不在运行队列上,但是可能存在于 Channel 的等待队列上

// _Gmoribund_unused is currently unused, but hardcoded in gdb
// scripts.
_Gmoribund_unused // 5
//没有被使用,没有执行代码,可能有分配的栈

// _Gdead means this goroutine is currently unused. It may be
// just exited, on a free list, or just being initialized. It
// is not executing user code. It may or may not have a stack
// allocated. The G and its stack (if any) are owned by the M
// that is exiting the G or that obtained the G from the free
// list.
_Gdead // 6
//栈正在被拷贝,没有执行代码,不在运行队列上

// _Genqueue_unused is currently unused.
_Genqueue_unused // 7

// _Gcopystack means this goroutine's stack is being moved. It
// is not executing user code and is not on a run queue. The
// stack is owned by the goroutine that put it in _Gcopystack.
_Gcopystack // 8
//栈正在被拷贝,没有执行代码,不在运行队列上

// _Gpreempted means this goroutine stopped itself for a
// suspendG preemption. It is like _Gwaiting, but nothing is
// yet responsible for ready()ing it. Some suspendG must CAS
// the status to _Gwaiting to take responsibility for
// ready()ing this G.
_Gpreempted // 9
//由于抢占而被阻塞,没有执行用户代码并且不在运行队列上,等待唤醒

// _Gscan combined with one of the above states other than
// _Grunning indicates that GC is scanning the stack. The
// goroutine is not executing user code and the stack is owned
// by the goroutine that set the _Gscan bit.
//
// _Gscanrunning is different: it is used to briefly block
// state transitions while GC signals the G to scan its own
// stack. This is otherwise like _Grunning.
//
// atomicstatus&~Gscan gives the state the goroutine will
// return to when the scan completes.
_Gscan = 0x1000
//GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在

_Gscanrunnable = _Gscan + _Grunnable // 0x1001
_Gscanrunning = _Gscan + _Grunning // 0x1002
_Gscansyscall = _Gscan + _Gsyscall // 0x1003
_Gscanwaiting = _Gscan + _Gwaiting // 0x1004
_Gscanpreempted = _Gscan + _Gpreempted // 0x1009
)
  • 等待中:Goroutine 正在等待某些条件满足,例如:系统调用结束等,包括 _Gwaiting、_Gsyscall 和 _Gpreempted 几个状态;
  • 可运行:Goroutine 已经准备就绪,可以在线程运行,如果当前程序中有非常多的 Goroutine,每个 Goroutine 就可能会等待更多的时间,即 _Grunnable;
  • 运行中:Goroutine 正在某个线程上运行,即 _Grunning;
type m struct {
g0 *g
curg *g
...
}

其中 g0 是持有调度栈的 Goroutine,curg 是在当前线程上运行的用户 Goroutine,这也是操作系统线程唯一关心的两个 Goroutine。

Goroutine MPG模型_sed_04

当你停下来休息的时候,不要忘记别人还在奔跑!