第六部分 异构计算与 AI 时代的 Go
前五部分把 Go 当作一个自洽的世界来读:调度器在自己的线程上复用 goroutine, 分配器与垃圾回收器照看自己的堆,编译器与链接器把这一切收束成一个静态的二进制。 这个世界的边界,恰恰是它优雅的来源。可是过去十年,计算本身越过了这道边界。 张量在 GPU 的显存里流动,模型的权重以 GB 计,一次推理要在主机与设备之间往返千百次, 而一个 AI Agent 不过是一个挂着工具调用的控制循环,长时间地等待、流式地产出。 这些负载没有一个住在 Go 的世界里,它们住在 Go 的世界之外。
于是问题就尖锐起来:一门以垃圾回收和 goroutine 调度立身的语言, 如何与一个由加速器、外部运行时和远端模型构成的异构世界打交道? 本部分不打算做一次框架巡礼。CUDA、llama.cpp、Ollama、MCP 都会出现, 但它们出现的身份是「某个机制的证据」,而不是「被讲解的主题」。 框架会过时,去年的推理引擎今年就换了名字;可框架要解决的那个根本问题不会过时。 正如本书开篇所言,代码总可以推倒重来,原理却能「永生」。
把这些看似无关的负载并到一处,是因为它们共享同一道裂缝:FFI 边界。 不论是向 GPU 提交一个 kernel、调用一次本地推理运行时,还是把一帧画面交给图形驱动, Go 的代码都要在某个点上跨过 cgo 这道门,离开运行时托管的领地, 进入一段它既不能抢占、也不能回收、甚至不知道会持续多久的外部执行。 这道边界上盘踞着本部分反复出现的三个机制性问题。
其一,调度器如何面对一段不可见的阻塞。一次 kernel 启动、一次跨设备拷贝,
对运行时而言只是一个迟迟不返回的 C 调用。第 9 章里那套 entersyscall、P 移交、
sysmon 抢占的机器,正是在这里被重新检验:当外部世界还会反过来创建线程、回调 Go,
needm 与 cgocallback 又如何把一个陌生线程临时接纳进运行时。
其二,垃圾回收器与设备内存的分界。显存不在 Go 的堆里,回收器看不见它;
而交给外部调用的那段主机内存,又随时可能被回收器移动或释放。第 12、13 章的所有权与
可达性,在 runtime.Pinner 和 cgo 指针传递规则这里落到了实处。
其三,并发模型的失配与弥合。GPU 的异步、推理的批处理、Agent 的流式产出, 都不是 goroutine 天然的形状。第 10 章的通道、第 7 章的 context 取消, 要在背压、超时、扇入扇出这些场景里证明自己仍然够用。
四章顺着这道边界由近及远地展开。第 18 章直面 GPU 与异构计算, 把 FFI 边界、阻塞调用下的调度、显存与 GC 的分界、异步编程模型逐一剖开, 这是全部分的机制底座。第 19 章转向图形, 图形是最古老的异构负载,渲染管线如何切分 CPU 与 GPU、图形上下文为何被钉在某个系统线程上、 以及软件渲染与浏览器中的渲染各自的取舍。第 20 章走到 AI 推理与服务, 看 Go 为何盘踞在推理与服务这一层而非训练层,本地推理运行时的张量如何越过边界, 分词、批处理、流式输出又各自依赖前面的哪一套机制。第 21 章收束于 AI Agent 运行时, 把一个 Agent 还原成一个并发问题:控制循环、工具调用与 MCP、流式与背压与取消, 看 Go 的并发模型在这个最年轻的负载上是否仍然称手。
读这一部分不需要预先懂得 CUDA 或深度学习,需要的是前五部分搭好的那套直觉: 什么是系统调用,什么是抢占,回收器凭什么敢移动你的对象,通道为什么能传递所有权。 把这些带到边界上来,异构与 AI 就不再是另一个世界的魔法, 而是同一套原理在更远处的回响。