g0一个特殊的协程.md

注:该文章基于Go 1.13

所有Go程序创建的协程都由内部调度器的管理。Go调度器尝试给running状态的协程分配运行时间。当协程被阻塞或中断时,使用running状态的协程保持CPU处于工作状态。Go调度器实际是一个特殊的协程。

协程调度

GOMAXPROCS变量让Go可以限制系统线程数量。Go在每个运行的线程上调度和管理协程。这个角色,一个称为g0的特殊协程,是线程创建的地一个协程。

然后,调度器将ready状态的协程分配到线程上运行。(参考 P,M,G模型)

为了更好的理解g0是如何调度的,我们来复习一下管道的使用过程:

1
2
3
ch := make(chan int)
[...]
ch <- v

管道阻塞时,当前协程被暂停,如进入waiting状态,被不会被推入任何协程队列中。

然后,g0替换协程并进行第一轮调度。

调度期间,本地队列local queue优先级较高,因此将运行#2协程。

当管道读取到数据时,#7协程将解除阻塞。

1
v := <-ch

协程接收到管道消息后,会将运行时切换到g0,并解除#7的阻塞再将其放到本地队列列首。

虽然调度器负责所有协程的调度,但它还有很多其他工作。

调度器的职责

与常规协程不同,g0有一个固定且更大的栈空间。有些操作可能需要较大的栈空间,有些系统要求其栈空间不能增长。那么,g0协程主要有以下职责:

  • 负责协程的创建。当调用go func(){ ... }()go myFunction()时,Go先将函数创建任何转给g0,再将其放入本地队列中。

新近创建的协程有更高的优先级,会被放置在本地队列的列首。

  • 分配 defer 函数。
  • 垃圾收集,例如STW(stopping the world),扫描协程栈空间,标记/清楚操作等。
  • 栈空间增长。当协程需要更大的栈空间时,This operation is done by g0 in the prolog functions

另外还有诸如超大内存分配,cgo等,这个有着较大栈空间的特殊协程保证了我们的Go程序高效的运行。