注:该文章基于Go 1.13
所有Go程序创建的协程都由内部调度器的管理。Go调度器尝试给running
状态的协程分配运行时间。当协程被阻塞或中断时,使用running
状态的协程保持CPU处于工作状态。Go调度器实际是一个特殊的协程。
协程调度
GOMAXPROCS
变量让Go可以限制系统线程数量。Go在每个运行的线程上调度和管理协程。这个角色,一个称为g0
的特殊协程,是线程创建的地一个协程。
然后,调度器将ready
状态的协程分配到线程上运行。(参考 P
,M
,G
模型)
为了更好的理解g0
是如何调度的,我们来复习一下管道的使用过程:
1 |
|
管道阻塞时,当前协程被暂停,如进入waiting
状态,被不会被推入任何协程队列中。
然后,g0
替换协程并进行第一轮调度。
调度期间,本地队列local queue
优先级较高,因此将运行#2
协程。
当管道读取到数据时,#7
协程将解除阻塞。
1 |
|
协程接收到管道消息后,会将运行时切换到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程序高效的运行。