g0
写在前面
本文基于golang 1.13
在Go中创建的所有goroutine都在内部调度器的管理之下。Go调度器试图给所有goroutine提供运行时间,并在当前goroutine被阻塞或终止时,让所有CPU开始运行其它goroutine。它实际上是作为一个特殊的goroutine来运行的。
调度goroutine
Go通过GOMAXPROCS
变量限制了OS线程同时运行的数量。这意味着Go必须在每个运行的线程上调度和管理goroutine。这个角色被委托给一个特殊的goroutine,称为g0
,它是为每个OS线程创建的第一个goroutine。
从上图中可以看出,它将会让那些处于ready
状态的协程在相应的线程上执行。
为了更好地理解g0
上调度的工作原理,我们来回顾一下channel的用法。下面这个代码是当一个goroutine在channel上被阻塞发送时的例子:
ch := make(chan int)
...
ch <- v
当在通道上阻塞时,当前的goroutine将被停放,即处于等待模式,并且不会被加载到其它任何goroutine队列中。
g0
将会替换这个goroutine,开始调度其它的goroutine。
goroutine的本地队列是一个优先级队列,所以G2
goroutine将会被调度执行。
对于G7
,如果有其它接收器能从channel中读取数据,那么G7
这个goroutine将会处于unblock状态。
v := <-ch
当goroutine收到消息后,它又会切换到g0
goroutine,同时解锁那些处于阻塞状态的goroutine并将它们放到本地队列中。
虽然g0
goroutine是管理调度,但这并不是它唯一的工作,它做得更多。
职责
与常规的goroutine相反,g0有一个固定的、更大的栈。这使得Go可以在那些需要更大堆栈但堆栈最不好要增长的场景下可以做更多的事情。
g0
的主要职责有以下几个:
创建goroutine
当我们使用代码go func() {}
或go myFunc()
时,Go会将函数的创建委托给g0
,然后再将其放在本地队列中。
新创建的goroutine以优先级运行,并被置于本地队列的顶部。
推迟函数分配
垃圾回收
如: STW(Stop the World),扫描goroutine的栈,标记清除等
栈增长
当需要时,Go会增加goroutines的大小。这个操作由prolog
函数中的g0
完成。
涉及到很多其他的操作(如:大内存分配,cgo等),这个特殊的goroutine g0
使我们的程序更有效的管理需要更大的栈的操作,以保持我们的程序以更高效的低内存打印。