g0 特殊的goroutine

在上篇 《golang中G、P、M 和 sched 三者的数据结构》文章中,我们介绍了GMP 的数据结构,其中M结构体中第一个字段是 g0,这个字段也是一个 goroutine,但和普通的 goroutine 有一些区别,它主要用来实现对 goroutine 进行调度,下面我们将介绍它是如何实现调度groutine的。

另外还有一个 m0 , 它是一个全局变量,与 g0 的区别如下

M0 与 g0的区别

本文主要翻译自 Go: g0, Special Goroutine 一文,有兴趣的可以查阅原文,作者有一系列高质量的文章推荐大家都阅读一遍。ℹ️ 本文基于 Go 1.13。

我们知道在Golang中所有的goroutine的运行都是由调度器来负责管理的,go调度器尝试为所有的goroutine来分配运行时间,当有goroutine被阻塞或终止时,调度器会通过对goroutine 进行调度以此来保证所有CPU都处于忙绿状态,避免有CPU空闲时间浪费时间。

goroutine 切换规则

在此之前我们需要记住一些goroutine切换规则。runtime源码

// src/runtime/stubs.go

// mcall switches from the g to the g0 stack and invokes fn(g),
// where g is the goroutine that made the call.
// mcall saves g's current PC/SP in g->sched so that it can be restored later.
// It is up to fn to arrange for that later execution, typically by recording
// g in a data structure, causing something to call ready(g) later.
// mcall returns to the original goroutine g later, when g has been rescheduled.
// fn must not return at all; typically it ends by calling schedule, to let the m
// run other goroutines.
//
// mcall can only be called from g stacks (not g0, not gsignal).
//
// This must NOT be go:noescape: if fn is a stack-allocated closure,
// fn puts g on a run queue, and g executes before fn returns, the
// closure will be invalidated while it is still executing.
func mcall(fn func(*g))

mcall() 函数注释的翻译请参考文章 Runtime: 当一个goroutine 运行结束后会发生什么

一、将一个运行中的 Goroutine 切换到另一个的过程涉及到两个切换:

  • 将运行中的 g 切换到 g0
    2
  • 将 g0 切换到下一个将要运行的 g
3

二、在 Go 中,goroutine 的切换成本很低,每次切换都需要对当前G的状态(PC/SP)进行存储(g.sched),以便下次恢复运行时读取当前G的上下文信息:

Continue reading