聊聊 g0

qcrao · · 6259 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

很多时候,当我们跟着源码去理解某种事物时,基本上可以认为是以时间顺序展开,这是编年体的逻辑。还有另一种逻辑,纪传体,它以人物为中心编排史事,使得读者更聚焦于某个人物。以一种新的视角,把所有的事情串连起来,令人大呼过瘾。今天我们试着以这样一种逻辑再看 g0。

回顾一下 Go 夜读第 78 期,关于调度器源码分析的内容。我们讲过,与主线程绑定的 M 对应的 g0 的主要作用是提供一个比一般 goroutine 要大的多栈(64K)供 runtime 代码执行。

初始化的过程中,在函数 runtime·rt0_go 里会给主线程的 g0 分配栈空间:

g0 栈空间

之后,主线程会与 m0 绑定,m0 又与 g0 绑定:

主线程绑定 m0,g0

之后,又与 p0 绑定:

g0-p0-m0

这样,主线程的这一套 GPM 就可以转起来了。接着,就创建了 main goroutine,放入 p0 的本地待运行队列。最后,通过 schedule() 函数进入调度循环。

前面说的是程序初始化的过程中,g0 是如何诞生的。当执行到 main.main() 函数,也说是用户在 main 包下写的 main 函数里,我们随手一句:

1
2
3
go func() {
// 要做的事
}()

就启动了一个 goroutine 时,在 Go 编译器的作用下,最终会转化成 newproc 函数。在 newproc 函数的内部,会在 g0 栈上调用 newproc1 函数,完成后续的工作。创建完成后,会将新创建的 goroutine 放入 _p_ 的本地待运行队列。

因为新增加了一个 g,这时会尝试去唤醒一个 P 来一起执行任务。判断条件是:

1
2
3
if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {
wakep()
}

即在有空闲 P 以及没有正在“找工作的 M”的情况下,才会尝试去唤醒一个 P。我们又知道,其实 P 的数量在程序运行过程中一般不会变化,所以这里所谓的唤醒其实就是把空闲的 P 利用起来。

通过 wakep() -> startm() -> newm() -> allocm() -> malg() 这条链路创建 g0,这里 g0 的栈大小实际上为 8KB

1
mp.g0 = malg(8192 * sys.StackGuardMultiplier) // sys.StackGuardMultiplier 在 linux 里为 1

g0 作为一个特殊的 goroutine,为 scheduler 执行调度循环提供了场地(栈)。对于一个线程来说,g0 总是它第一个创建的 goroutine。之后,它会不断地寻找其他普通的 goroutine 来执行,直到进程退出。

当需要执行一些任务,且不想扩栈时,就可以用到 g0 了,因为 g0 的栈比较大。g0 其他的一些“职责”有:创建 goroutine、deferproc 函数里新建 _defer、垃圾回收相关的工作(例如 stw、扫描 goroutine 的执行栈、一些标识清扫的工作、栈增长)等等。

因为 g0 这样一个特殊的 goroutine 所做的工作,使得 Go 程序运行地更快。

注:最近在 medium 上看到了一个非常赞的关于 Go 的博客,题图画得很有阅读的欲望。这篇文章也是参考于其中的一篇


有疑问加站长微信联系(非本文作者)

本文来自:qcrao

感谢作者:qcrao

查看原文:聊聊 g0

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

6259 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传
X
登录和大家一起探讨吧