跳到主要内容
约 6 分钟2 张卡

Go 运行时

Go 运行时是程序执行时提供语言支持的一组实现,负责 goroutine 调度、栈管理、内存分配、垃圾回收等工作,并把网络等待与调度连接起来。它随标准 Go 工具链构建的程序一起工作,业务代码没有显式导入 runtime,也不代表程序不需要这些支持。

理解运行时,先要把各项职责放在一起看:任务怎样得到执行机会,数据放在哪里,等待时谁还能继续运行,不再需要的堆对象又怎样回收。本文以 Go 1.22.12 为实现背景,具体算法与参数在相应专题中展开。

1. 面试核心​

先记住这几条,再读正文
  • Go 运行时提供执行期间的语言支持,包括调度、栈、分配、GC 和网络轮询等机制。解释这些机制的职责与联系,比单独罗列缩写更能说明 runtime 做了什么。卡 · 编译器、运行时与操作系统
  • 编译器、运行时和操作系统各有职责:编译器分析代码并生成机器指令,运行时执行语言需要的支持逻辑,操作系统管理进程、线程和底层资源。卡 · 编译器、运行时与操作系统
  • 运行时并不只等于可导入的 runtime 包接口。不显式导入这个包,程序仍可使用 goroutine 和 GC 等能力。卡 · 没有导入 runtime 的程序
  • 自动调度与垃圾回收不会替应用定义任务退出、业务同步和资源关闭协议。运行时提供机制,正确使用这些机制仍是程序的责任。

2. 编译器、运行时与操作系统​

标准 Go 工具链通常提前把程序编译为目标平台的机器代码。这里的 runtime 是支持程序执行的实现,不要求再通过一层 Java 字节码式虚拟机运行这些机器指令。构建与平台依赖见 Go 构建。

三个层次会协作,但不能相互替代:

层次负责的工作与本文机制的关系
编译器类型检查、优化、生成代码,并在必要位置生成运行时调用或检查逃逸分析影响存储安排,函数入口可以包含栈空间检查,指针写入可能需要写屏障
Go 运行时在执行期间调度 G,管理栈和堆,执行 GC,处理相应等待与唤醒执行编译器和标准库所依赖的支持逻辑
操作系统提供进程、线程、虚拟内存、文件和网络等能力Go 运行时仍需使用操作系统线程、内存映射与 I/O 接口

例如“这个局部值需要放到堆上”可以来自编译期分析;程序真正执行到相应路径时,才可能调用分配器取得内存。之后 GC 再判断堆对象是否仍然可达。这不是运行时重新做了一遍编译器的逃逸分析,具体区别见 Go 逃逸分析。

runtime 包向业务代码公开了部分接口,例如查看 goroutine 数量、调整 Go 代码执行并行度或主动请求 GC。运行时还包含很多编译器、标准库与调度器内部使用的实现;它们不是都作为稳定公共 API 暴露给应用。

2.1 没有导入 runtime 的程序​

下面是一个没有任何导入的完整程序:

package main

func main() {}

把它保存为 main.go,使用标准 Go 工具链构建并查看符号:

go build -o runtime-demo main.go
./runtime-demo
go tool nm runtime-demo

程序不打印内容,但普通构建的符号表中仍能找到 runtime.main 等运行时符号。它说明这个构建产物包含运行时支持,不代表符号表里的每个函数在这次运行中都执行过。链接选项也可以去掉符号信息,不能反过来把“找不到某个符号”当成没有运行时的证据。

启动过程会先完成必要的运行时与包初始化,再进入业务 main;包变量和 init 的顺序见 Go 包初始化。无需为了“启动运行时”而额外写一个空白导入。

3. 主要职责与阅读入口​

这些机制解决不同的问题,也会共享执行资源。先能说明每一项负责什么,再按实际追问进入对应正文。

主题要能解释的核心问题对应正文
goroutine 调度G 怎样获得 M 与 P 的执行机会,等待后怎样恢复Go 调度器
栈管理goroutine 的函数调用状态放在哪里,栈空间不够时怎样处理Go 内存分配
堆内存分配需要堆存储的对象怎样取得空间,分配大小为何不总等于请求大小Go 内存分配
垃圾回收哪些对象仍然可达,怎样标记与回收,以及为什么仍有暂停和执行成本Go 垃圾回收
网络轮询非阻塞 I/O 暂时不能完成时,G 怎样等待并在就绪后继续执行Go 网络轮询

调度器解决执行机会的分配,不承诺任意两个 goroutine 同时运行;栈和堆也不是两种由 new、make 语法直接指定的区域。GC 负责回收不再可达的堆对象,不能识别仍被引用但已经没有业务用途的缓存。这些边界分别影响并发、内存和故障判断。

基础复习可以先按“调度—栈与分配—GC—网络等待”的顺序建立联系。需要解释某个对象为什么分配到堆上时,再补编译期的 Go 逃逸分析;需要验证资源成本时,使用 Go 性能分析 的 profile 与 trace。回答宽泛的 runtime 问题时,先把职责和执行过程讲清楚,再根据追问展开。

4. 一次网络任务中的协作​

以一个已经建立 TCP 连接、准备读取请求并生成响应的任务为例,几个运行时机制可能这样配合:

  1. 处理连接的 G 获得执行机会,开始执行读取代码。普通函数调用使用这个 G 的栈;如果需要更多栈空间,由相应栈管理逻辑处理。
  2. 暂时没有网络数据时,受 netpoll 管理的连接可以让 G 挂起。之后运行时仍有机会让其他可运行任务继续执行。
  3. 就绪通知到达后,这个 G 变为可运行,获得执行机会后重试读取。就绪不等于完整请求已经到齐,消息边界由协议处理。
  4. 解析请求和构造响应时,部分临时值可能留在栈上或被优化掉,需要堆存储的对象则由分配器提供空间。
  5. 随着分配和程序执行,GC 可能开展标记、清扫等工作;它会使用 CPU,也可能带来暂停或让分配路径承担部分工作。
  6. 任务结束时,应用仍要处理连接所有权、子任务退出和结果提交。失去所有有效引用的堆对象,之后才有机会被 GC 回收。

这只是把职责串起来的例子,不是每个请求固定都会执行的一套阶段。GC 不按请求结束逐个触发,也不是每次函数调用都要增长栈。实际分配和等待由代码、数据和运行条件决定。

运行时同样不能替应用修复数据竞争、结束一个永远收不到数据的任务,或撤销已经提交到远端的操作。相关退出与同步问题见 Go 并发诊断。

5 · 面试题卡片​

先想答案,再翻面先在心里答一遍,再点开卡片对照参考回答。卡片之间可以左右切换,看完会回到这里。
逐张翻看 2 张 →
未标记的卡读完后明天进入复习;已标记的按状态安排
本页题目与参考回答

5.1 Go Runtime 是什么,主要负责哪些工作?​

考察重点:运行时的含义、主要职责,以及与编译器和操作系统的关系。

相关内容:编译器、运行时与操作系统、主要职责与阅读入口、一次网络任务中的协作。

参考回答

Go Runtime 是程序执行期间提供语言支持的实现,主要负责 goroutine 调度、栈管理、堆内存分配、垃圾回收,以及网络等待与调度的衔接。 例如网络读取暂时不能完成时可以挂起 G,后续就绪再让它获得执行机会;堆对象由分配器提供空间,再由 GC 判断是否可回收。

编译器提前分析代码并生成机器指令,必要时使用运行时支持;运行时仍建立在操作系统线程、内存和 I/O 能力之上。它不是整个操作系统,也不只是一组业务代码显式调用的工具函数。自动调度和 GC 仍不能替应用定义正确的同步、退出和资源关闭协议。

5.2 没有 import runtime,程序仍然有运行时吗?​

考察重点:运行时支持与公共包接口之间的区别。

相关内容:没有导入 runtime 的程序。

参考回答

有。标准 Go 工具链构建的程序包含必要的运行时支持,不依赖业务代码显式导入 runtime。 调度、栈管理与 GC 等能力不是由应用导入这个包之后才启动。

只有直接使用 runtime 包公开的函数或常量时,源码才需要相应导入。程序符号表可以帮助观察构建产物包含的运行时符号,但符号可能被剥离,不能仅凭某个符号是否可见判断运行时是否存在。

6. 参考资料​

来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明