Go中的特殊协程g0

【译文】原文地址
本文基于go 1.13版本
所有在Go中创建的goroutines都由一个内部调度程序的管理。Go调度程序试图给所有的goroutines分配运行时间,并且在当前goroutine被阻塞或终止情况下也能使CPU忙于运行其他goroutines。

调度goroutine

Go通过GOMAXPROCS变量来限制操作系统线程同时运行的数量。这意味着,Go必须在每个正在运行的线程上调度和管理所有的goroutines。这个角色通过一个特殊的goroutine来完成,称为g0,这是为每个操作系统线程创建的第一个goroutine:


然后,它将调度准备就绪的goroutine到对应的线程上去。关于更多M,P,G模型的内容可以阅读Goroutine、操作系统线程和CPU的管理。
为了更好的理解调度是如何在g0上工作的,让我们回顾下channel的用法。下面是一个goroutine阻塞在channel的发送上:

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

当goroutine被阻塞在channel上时,当前的goroutine就会被挂起,即处于等待模式将不会推入任何goroutines队列中。


然后g0取代等待的goroutine,进行第一轮的调度:

本地队列将优先得到调度,所有G2将被执行:

更多关于调度优先级的内容,可以阅读Go中的抢占调度。
只要有协程从channel中接收数据,图中G7协程将立即脱离阻塞:

v := <-ch

收到消息的goroutine将切换到g0,然后将挂起的goroutine放入到本地调度队列中:



尽管g0这个特殊goroutine是管理调度的,但是它不止这些工作还有其他更多的功能。

Responsibility

与普通goroutine相反,g0有固定且比较大的栈。这允许Go在需要更大栈时,还能执行操作。g0的职责可以如下:

  • 创建Goroutine。当调用go func{...}()或go myFunction(),Go将函数创建委托给g0,然后在将goroutine放入本地队列:



    新创建的goroutines以更高优先级运行,并发在本地队列的顶部。关于更多goroutines的优先级,可以阅读文章[Go:并发&调度亲和性]。

  • Defer函数的分配
  • 垃圾回收操作,例如STW,扫描goroutine的栈和标记清除操作。
  • 栈扩容,当需要时,Go会增加goroutine的大小。该操作有g0来完成的。
    这种特殊的g0,涉及很多操作(大堆栈分配,cgo等)使我们的程序在需要更大堆栈情况下更有效地管,并保持程序内存低消耗。

你可能感兴趣的:(Go中的特殊协程g0)