Logo

site iconTonyBai | 白明

重复
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

TonyBai | 白明 RSS 预览

GPU 计算的起源

2026-04-17 08:20:14

本文永久链接 – https://tonybai.com/2026/04/17/the-origins-of-gpu-computing

大家好,我是Tony Bai。

在今天的人工智能时代,GPU 已成为数据中心的核心算力引擎,但它的崛起并非一夜之间的奇迹。ACM通讯文章《The Origins of GPU Computing》回溯了 GPU 计算的三十年发展史,揭示了从并行计算、图形系统到流处理等关键技术如何在政府资助的学术研究中逐步成熟,并最终汇聚成推动深度学习革命的基础设施。文章不仅梳理了技术脉络,也展示了学界与产业之间如何通过人才与思想的流动,共同塑造了现代 GPU 计算的格局。

本文是这篇文章的译文,供大家学习参考(格式有调整,更适合公众号阅读)。


政府资助的并行计算、流处理、实时着色语言和可编程图形处理单元(GPU)的学术研究直接推动了 GPU 计算的发展。GPU 被广泛应用于现代数据中心,并促成了当前的人工智能(AI)革命。生产 GPU 的英伟达(Nvidia)现已成为世界上最有价值的公司。这种计算变革及其产生的经济价值,得益于超过 30 年的政府资助研究。政府资助不仅有助于发展许多关键的技术创新,还培养了大量将这些技术带入行业的学生。

本文追溯了 GPU 计算的起源。我们首先描述了 GPU 计算所构建的技术(并行计算、并行图形系统、可编程着色器(shaders)和流处理)的发展,然后详细介绍了这些技术是如何转移到英伟达和其他公司,并最终应用于现代机器学习的。

赋能技术

GPU 计算建立在并行计算、并行图形系统和流处理的早期工作基础上。这些技术是通过超过 30 年的政府资助学术研究发展而来的。

并行计算

当你学习计算时,你了解到的是中央处理器(CPU)按顺序执行一系列指令。

实际上,芯片包含数十亿个并行切换并由导线连接的晶体管。开关和导线是物理计算机的基本构建块,它们同时运行。

此外,晶体管切换消耗的能量很少,而沿导线的通信消耗的能量要多得多。

通信需要功率来将信号从一点发送到另一点;功率随着距离的增加而增加,如果是在芯片之间进行信号传输,功率消耗将非常巨大。

虽然顺序计算机可能比并行计算机更容易理解,但顺序计算机必须通过同时切换的晶体管和同时传输信息的导线来实现。顺序计算机使用许多晶体管并行计算结果,然后仔细地以与顺序执行一致的方式组装这些结果。

创建这种执行是顺序的“幻觉”,在功率和性能上都是低效的。随着可用晶体管数量的增加,这种低效性也随之增加。在现代半导体技术中构建计算机的自然方式是设计并行计算机。GPU 比 CPU 更高效,因为它们是大规模并行计算机。

GPU 计算建立在并行计算的早期工作之上。与所有并行计算机一样,在 GPU 上运行的并行任务或线程必须相互同步和通信。

线程需要通信来使用由另一个线程产生的数据。同步是必要的,以在数据可用时发出信号,确保消耗的是正确的值。

并行计算、同步和通信的许多基础知识是由政府资助的学术研究开发的。由加州理工学院 Chuck Seitz 领导的 DARPA 资助的“宇宙立方”(Cosmic Cube)项目发展了并行计算的许多基础知识。在该项目上开发的硬件是英特尔 iPSC、Delta 和 Paragon 机器的蓝图,以及几台早期的能源部 ASCI 机器。“Cosmic-C”编程语言引入了异步消息传递和集合通信,后来以消息传递接口(MPI)的形式成为编程大型并行机器的标准。

麻省理工学院(MIT)的 DARPA 资助的 J-Machine 和 M-Machine 项目开发了用于通信和同步的低开销机制,以及现代互连网络的许多关键方面。这些机制使得并行性可以在非常细的粒度上被利用,最少只需 10 或 20 条指令即可作为一个可调度的工作单元。J-Machine 的许多特性被 Cray T3D 和 T3E 计算机直接采用。

并行计算有着超越这一特定历史分支的丰富历史。由于篇幅有限,我们无法进行完整的综述。Culler 等人的文章提供了一个很好的回顾。

GPU 计算与所有高性能计算一样,深受这一遗产的影响。它使用 MPI 进行节点间的通信,使用互连网络连接这些节点,并且在此研究过程中开发的许多通信和同步机制被用于协调并行计算。

并行图形系统

虽然不如传统的并行计算和超级计算机广为人知,但并行图形和成像计算机有着悠久的历史。

处理和生成图像需要巨大的计算量。例如,如果一台每秒处理一百万条指令的计算机(1MIPS)对百万像素图像的每个像素应用一次算术运算,计算机需要一秒钟来处理一张图像。

渲染电影和游戏中的 3D 虚拟世界比图像处理每像素需要的计算量大几个数量级。例如,为现代电影生成的图像每个像素需要大约十亿次浮点运算。因此,为了在实践中有用,图形和成像需要高性能的并行超级计算机。这些计算机在大规模数据集合上并行计算。

一个早期的 DARPA 资助研究项目是吉姆·克拉克(Jim Clark)在斯坦福大学领导的几何引擎(Geometry Engine)。

几何引擎促成了硅谷图形公司(Silicon Graphics)的成立,该公司率先开发了 3D 图形工作站。SGI 硬件架构和 OpenGL 软件库定义了现代 GPU 架构。

另一个值得注意的政府资助研究项目是亨利·福克斯(Henry Fuchs)及其合作者在北卡罗来纳大学领导的 Pixel Planes 系列高性能图形系统。事实上,Pixel Planes 5 是一台相当通用的单指令多数据(SIMD)计算机,它在 128 x 128 图像上运行并行计算。其他早期并行图形和图像计算机的例子包括 NASA 的大规模并行处理器(MPP)、Ikonas 图形系统和 Pixar 图像计算机。

早期 GPU 实现了类似于早期 SGI 工作站的固定功能图形流水线。当整个 OpenGL 图形流水线可以在单个芯片上实现时,英伟达引入了“GPU”一词。1999 年推出的英伟达 Geforce 256 由 1700 万个晶体管组成,是第一款商用 GPU。

在此之前,在皮克斯(Pixar)工作期间,Hanrahan 开发了 RenderMan,这是一个生成照片级逼真图像的系统。该系统彻底改变了电影行业,因为它能够生成可以与相机拍摄的实景无缝结合的图像。RenderMan 的一个关键组件是着色语言,它使用户能够扩展系统以模拟复杂的材质和光照。

虽然最初的 GPU 实现了固定功能流水线,但它们是由可编程组件构成的。不幸的是,这些处理单元因系统而异,因代而异。需要的是一种可移植的编程模型。由于 GPU 的主要应用是电脑游戏,因此将 RenderMan 着色语言适配到 GPU,以便游戏开发者可以创造新的光照和着色效果似乎是自然而然的。

在斯坦福大学的一个 DARPA 资助项目下,为当时的 GPU 设计并实现了一种实时着色语言(RTSL)。着色语言程序现在被称为着色器(shaders)。博士后学者 Bill Mark 领导了斯坦福 RTSL 的设计,后来加入了英伟达。他与另一位前斯坦福研究生 Kurt Akeley 一起增强了该技术,并创建了 Cg 着色语言。Cg 导致了微软 HLSL 和 OpenGL GLSL 的开发。

人们很快意识到,这些早期的着色语言足够灵活,可以实现科学计算中的许多算法。研究人员采用了诸如矩阵乘法、线性求解器、流体动力学求解器和分子动力学等算法在着色器上运行。这导致了 GPGPU(通用 GPU)计算运动的兴起。

流处理

DARPA 和 DOE 在斯坦福大学资助的关于 Imagine 流处理器和 Merrimac 流式超级计算机的工作发展了流处理,这是一种导致算术强度(计算与带宽之比)增加的并行计算形式。

如前所述,处理器消耗的大部分功率是在通信上。在芯片之间发送信号尤其耗电。芯片外通信也比芯片内通信慢得多。

流处理包含两个减少内存带宽需求的主要思想。

第一个是利用生产者-消费者局部性,使得一个阶段(生产者)将其结果转发给下一个阶段(消费者),而无需写入和读取内存。

第二个主要思想是将计算组织成称为内核(kernels)的函数。每个内核获取一个数据包,对该包执行函数,并输出另一个数据包。函数中的算术运算数量大于对内存的读写次数。这两种技术显著减少了内存访问次数,并提高了流处理架构的效率。

在流处理器中,计算被组织成产生和消耗数据流的内核。产生内核会将输出流写入流寄存器文件(SRF)。消费内核会从 SRF 读取输入,而数据无需写入或从内存中读取。通过适当的调度来匹配流的批处理大小与 SRF 的容量,这种组织使得应用程序能够维持非常高的算术强度(算术与内存带宽之比)。

一个设计和构建 Imagine 流处理器的 DARPA 资助项目于 1997 年在 MIT 启动,并于同年晚些时候转移到斯坦福大学。Imagine 是一台用于信号和图像处理工作负载的图形和媒体处理器。它由许多带有本地寄存器文件的并行算术单元、一个中央流寄存器文件和一个内存系统组成。内核从流寄存器文件读取流,通过本地寄存器文件传递中间结果,并将输出流写回流寄存器文件,供下一个内核读取。

Stream-C 编程语言被开发用于编程 Imagine。它扩展了 C 编程语言,增加了描述内核和流的构造。开发了众多的图形、信号处理和图像处理应用程序来调整和评估该架构。它在纹理映射光栅图形上的性能与当时的固定功能 GPU 相当。

在一次 DARPA 主要研究人员会议上,本文作者意识到这项技术可以应用于高性能计算,并构思了 Merrimac 项目。斯坦福 DOE ASCI 中心的计算机科学(CS)部分被重定向以追求这种高性能计算方法。该中心的年度报告提供了流处理发展史的详实记录。

Merrimac 架构被定义为将流处理适配到科学应用。与 Imagine 相比,主要变化是增加了科学计算所需的数据类型(如 FP64),将架构扩展到通过互连网络连接的多个节点以处理大规模问题,并增加了许多弹性特征,以支持在具有合理故障率的情况下进行大规模计算。

Stream-C 编程语言演变成了 Brook。Brook 背后的关键思想是将流编程的想法与更传统的数据并行计算合并。内核函数成为保持高算术强度的关键处理原语。

Brook 被适配以针对 2000 年代初的 GPU。这些 GPU 运行可编程顶点和片段着色器。着色器实现了内核,但指令数量有限且寄存器很少。常见的数据并行编程原语(如 map、reduce/scan、filter、gather 和 scatter)是通过在低级图形着色器之上构建虚拟数据并行计算机来实现的。这种抽象使得大量现有的并行算法可以在 GPU 上运行,并且早期着色器的局限性逐渐被消除。

早期利用内核执行高算术强度计算的一个很好的例子是稠密矩阵-矩阵乘法,它是现代神经网络算法的基础。在执行矩阵-矩阵乘法时,需要读取两个 n×n 矩阵并写入一个 n×n 矩阵。矩阵乘法需要 n³ 次乘加运算。因此,算术强度为 O(n)。这一事实众所周知,并导致了针对带有缓存的 CPU 进行矩阵乘法分块的有效方法。分块在 GPU 上运行时也非常有效。

斯坦福 ASCI 中心的数值科学家将几种科学代码移植到 Brook,以便在 Merrimac 模拟器上运行。这些代码包括计算流体动力学、磁流体动力学和 n 体模拟。n 体模拟是高效 GPU 应用的一个很好的例子。原子对之间的力由天体物理模拟中的引力定律给出,但非结合原子之间的相互作用由 Lennard-Jones 势(甚至更复杂的经验势)近似。这些函数需要许多算术运算。对于这些模拟,相邻原子存储在“邻居列表”中。分子动力学模拟立即成为 GPU 的主要应用。

GPU 和流处理器的一个关键特征是它们具有多种形式的硬件并行性。

每个 GPU 由许多核心组成。每个核心包含一个 SIMD 处理单元(通常为 32 宽)。

此外,每个核心都是多线程的。

回想一下,GPU 是为图形应用程序开发的,其性能取决于将纹理应用于三角形的效率。

纹理映射涉及计算三角形内每个像素片段的纹理坐标,然后使用这些坐标从图像中获取。这些纹理获取具有空间局部性,但时间局部性很小。空间局部性可以通过小型缓存来处理,但由于缺乏一致性,缓存无法处理时间局部性。

高效的纹理映射要求 GPU 隐藏这些纹理获取的延迟。早期 GPU 通过让片段请求纹理、挂起该片段的执行,并立即切换到处理另一个片段来实现这一点。这是多线程的简化版本,这意味着 GPU 需要有许多并行线程同时运行。任务总数是核心数乘以 SIMD 算术单元数(称为 warp)乘以线程数。Blackwell B200 GPU 拥有 384 个流多处理器(SMs)。每个 SM 有 64 个驻留 warp,每个 warp 有 32 个线程。因此,该 GPU 上有 786,432 个任务同时执行。

技术转移

流处理架构和编程系统通过人员流动从斯坦福转移到了英伟达。英伟达的一位架构师 John Nickolls 听说过流处理,并招募了 Bill Dally 在 2003 年为英伟达的 NV50 架构提供咨询。(NV50 于 2006 年作为 G80 发布)。流处理器的许多特性被合并到了该架构中。NV50 的“共享内存”发挥了 Imagine 和 Merrimac 中 SRF 的作用。

Ian Buck(Merrimac 项目的研究生和 Brook 的主要开发人员)于 2004 年加入英伟达。Ian 与 John Nickolls 合作将 Brook 演进为 CUDA。CUDA 合并了 Brook 和 Cg(一种图形着色语言)的最佳特性,并采纳了 Brook 程序员的反馈。关于该技术如何从斯坦福转移到英伟达的故事在一篇演示文稿中进行了描述。Mike Houston(该项目的另一位研究生)加入了 AMD,并直接使用 Brook 作为其 GPU 的编程语言。G80(NV50)和 CUDA 于 2006 年在超级计算大会上发布。

当 CUDA 于 2006 年发布时,很少有人了解并行编程,更不用说 GPU 流编程了。为了克服这一劳动力短缺,Wen-Mei Hwu 和 David Kirk 通过为教授讲授 CUDA 编程课程来推广 GPU 计算。参加这些课程的教师随后教授了成千上万的学生使用 CUDA 进行并行编程。从 Cosmic Cube、J-Machine 和 M-Machine 借来的并行计算技术既被应用于 GPU 内部(以协调多个 SM),也被应用于跨 GPU(构建多节点 GPU 系统以解决大型问题)。

赋能 AI

现代机器学习依赖于三个关键要素——海量数据集、具有许多层和权重的庞大模型,以及优化权重的计算能力。核心算法(深度神经网络、卷积网络、使用反向传播的训练和随机梯度下降)自 20 世纪 80 年代或更早以来就一直存在。大型标注数据集,例如 PASCAL 和 Imagenet,出现在 21 世纪初。最近的进展,例如将文本嵌入到向量空间中,使得自然语言深度学习成为可能。Transformers(“注意力就是你所需要的”)用带有历史记录的易于训练的神经网络取代了难以训练的循环神经网络。GPU 计算使得大规模数据集的网络训练在经济上变得可行。一旦展示了这种能力(Alexnet, GPT),AI 的能力就得到了迅速提升。AI 的快速采用为改进 GPU 计算系统提供了更大的动力。

英伟达的机器学习也得益于学术界与产业界的协同效应。2010 年,作者之一(Dally)与吴恩达(Andrew Ng)的一次早餐交谈促成了一个英伟达与斯坦福之间的联合项目,旨在 GPU 上构建深度神经网络。Bryan Catanzaro 领导了该项目的英伟达部分。在此项目中开发的软件成为了 CuDNN,它为英伟达 GPU 上的深度学习提供了一个现成的库——从而推动了深度学习的普及。

结论

GPU 计算背后的技术(已促成了现代机器学习)主要归功于 30 年的政府资助学术研究。

并行计算、并行图形系统和流处理的研究为 GPU 计算奠定了基础。在这些研究项目中培养的许多学生后来进入行业,转移了这些技术并利用其开发了创新产品。

从斯坦福流处理项目到 GPU 计算的转移非常直接,学术上的 Brook 语言演变为 CUDA,流处理器的功能被整合到 G80 GPU 中。

GPU 提供的高效、易于编程且性能极高的计算平台,通过计算着色器促成了当前的机器学习革命——提供了缺失的成分,以补充早已可用但一直缺乏计算能力的算法和数据。

资料链接:https://cacm.acm.org/federal-funding-of-academic-research/the-origins-of-gpu-computing/

关于作者

威廉·J·达利是美国加利福尼亚州圣克拉拉英伟达公司首席科学家兼高级副总裁,同时也是斯坦福大学电气工程与计算机科学的兼职教授。

帕特·汉拉汉是美国加利福尼亚州斯坦福大学电气工程与计算机科学的佳能荣休教授。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

Rust 还没进前十,TIOBE 就开始唱衰了?

2026-04-17 07:36:54

本文永久链接 – https://tonybai.com/2026/04/17/tiobe-ranking-and-the-decline-of-rust-hype

大家好,我是Tony Bai。

过去几年,技术圈最热门的“猜谜游戏”之一,就是预测 Rust 什么时候能杀入 TIOBE 排行榜的前十。

这门被誉为“天选之子”的语言,连续多年霸榜 Stack Overflow“最受喜爱”的宝座,被微软、亚马逊等巨头奉为重写底层基础设施的“银弹”。所有人都觉得,它冲进前十,只是时间问题。

但就在最近,TIOBE 指数发布了 2026 年 4 月的最新排名。

榜单本身平平无奇,Rust 的排名甚至还从去年同期的 18 位微升到了 今年的16 位。

然而,TIOBE 的 CEO Paul Jansen 亲自撰写的一篇社论,却像一盆冷水,劈头盖脸地浇在了所有 Rustacean(Rust 开发者)的头上。

Paul Jansen 用极其明确的措辞,给这门甚至还没来得及摸到前十门槛的语言,提前下了一份“病危通知书”:

“Rust 的崛起显示出放缓的迹象。……它进入前十的梦想,现在看来比以前更加遥远了。”

这篇社论,瞬间引爆了全网的讨论。

无数 Rust 开发者感到匪夷所思,甚至有些愤怒:我们还没真正发力,你怎么就开始唱衰了?

这背后,到底是 TIOBE 对技术趋势的精准预判,还是这把统治了我们十几年的“认知标尺”,已经彻底失灵了?

今天,我们就来扒开这张榜单的底裤,看看在喧嚣的数据背后,Rust 的真实处境,究竟是怎样的。

官方的“诊断书”:Rust 的“阿喀琉斯之踵”

我们先来看看 TIOBE CEO Paul Jansen 的“诊断报告”。

他指出,Rust 在今年年初曾一度冲到历史最高排名第 13 位,但仅仅三个月后,就又跌回了第 16 位。

他给出的解释是:

“一个可能的解释是,尽管 Rust 能够生产出高效和安全的代码,但对于非专家程序员来说,它仍然难以学习。虽然专家们愿意投入时间去掌握这门语言,但更广泛的主流采用似乎面临着更大的挑战。”

这段话,精准地戳中了 Rust 社区最敏感、也最引以为傲的那根神经——陡峭的学习曲线

为了追求极致的内存安全,Rust 发明了极其复杂的“所有权(Ownership)”和“借用检查(Borrow Checker)”系统。这套系统像一个极其严苛的导师,在你编译代码的每一个环节,都对你进行着灵魂拷问。

无数新手在入门 Rust 时,都会经历一段被称为“与编译器搏斗”的痛苦时期。

TIOBE 的观点很明确:这种“精英主义”的设计哲学,正在成为 Rust “出圈”的最大障碍。

榜单的原罪:用“百度指数”去衡量火箭科学

TIOBE 的诊断听起来似乎很有道理。但我们必须先问一个更底层的问题:TIOBE 指数,到底是个什么东西?

TIOBE 的排名,本质上是一个基于“搜索引擎查询量”的指标。它在全球 25 个主流搜索引擎上,统计包含 +” programming” 关键词的页面数量。

看懂了吗?这套诞生于 十多年前的评判标准,在 2026 年的今天,已经变得极其荒谬

它衡量的是一门语言在公网上的“话题度”和“声量”,而不是它的“真实价值”和“商业应用”。

这就像用“微博热搜”的次数,去评判一位科学家的学术贡献一样可笑。

用这把“旧尺子”去衡量现代编程语言,会产生几个致命的认知偏差:

1. 越是难学、坑越多的语言,排名越高。

这恰恰是 TIOBE 逻辑最诡异的地方。Paul Jansen 一边抱怨 Rust 太难学,一边却忽视了,正是因为“难学”,新用户才会频繁地去 Google 搜索“Rust a lifetime that lives long enough”、“the trait Borrow is not implemented for String”这些令人抓狂的报错信息。

每一次“救命”的搜索,都在为 Rust 的 TIOBE 排名,贡献着宝贵的 KPI。

2. 越是成熟、生态完善的语言,排名越吃亏。

随着一门语言的成熟,它的文档会越来越完善,社区的最佳实践会沉淀下来。开发者遇到的问题,更多地会在官方文档、IDE 提示、或者小圈子的 Slack/Discord 里被解决,而不会产生大量的公开搜索。

没有问题,就没有搜索。没有搜索,就没有 TIOBE 排名。

3. TIOBE 无法衡量“生态位”的价值。

Rust 的江山在哪里?在 Linux 内核里(注:最近发布的Linux Kernel 7.0里,Rust已经正式转正了!),在 Windows 的系统组件里,在 Cloudflare 的边缘网络里,在 Figma 的渲染引擎里,在那些对性能和安全要求达到极致的底层基础设施里。

这些领域的开发者,是金字塔尖的系统程序员。他们讨论问题,是在 GitHub Issue、Zulip 频道,而不是在 CSDN 上问“我的 &mut 为什么传不进去”。

Rust 的价值,深藏在那些不会产生大量公开搜索记录的、高壁垒的硬核场景里。而 TIOBE 的爬虫,可能永远也爬不到那里。

真实的版图:Rust 正在经历一场“青春期的烦恼”

扒开 TIOBE 的“障眼法”,我们该如何客观看待 Rust 在 2026 年的真实处境?

Rust 并没有“增长放缓”,它只是在经历一场必然的“出圈阵痛”。

任何一门新技术的发展,都会经历两个阶段:

  1. 从 0 到 1 的“深耕期”:吸引最硬核、最狂热的一批早期用户,在特定的垂直领域里,将自己的核心优势打磨到极致。Rust 在“系统编程”领域,已经完美地完成了这个阶段。
  2. 从 1 到 N 的“出圈期”:试图将自己的影响力,扩展到更广阔的领域,吸引更多的主流开发者。

Rust 现在正处于从阶段一向阶段二过渡的关键时期。它那套为系统编程量身打造的、极致安全的内存管理哲学,在 Web 开发、数据科学、GUI 应用等场景下,确实给很多开发者带来了巨大的心智负担。

Rust 社区内部,关于是否应该为了“易用性”而牺牲部分“极致性”的争论,也从未停止。比如,关于异步运行时的分裂(Tokio vs async-std)、关于标准库的精简与扩充,都反映了这种“青春期的烦恼”。

Rust 没有停滞,它只是在“成长的十字路口”,在思考自己到底想成为谁。

我们真正应该关注什么?

作为身处一线的工程师,我们应该如何看待 TIOBE 的这份“诊断书”?

第一,永远不要把“流行度”作为技术选型的唯一标准。

JavaScript 很流行,但你不会用它去写操作系统内核。COBOL 极其冷门,但全球的银行系统依然跑在它上面,顶级 COBOL 程序员的薪资高得吓人。

技术的价值,永远取决于它在特定场景下,解决了多大规模、多高难度的商业问题。

第二,警惕“易用性”的陷阱。

Go、Python 很简单。但这种简单,可能是以牺牲“运行时安全保证”(比如Python 的动态类型、Go的Nil指针等)为代价的。

Rust 的“难”,恰恰是把所有可能在深夜引发线上雪崩的风险,全部前置到了编译阶段。它用“编译时的痛苦”,换取了“运行时的安宁”。

这种设计哲学,对于金融交易、底层基础设施、航空航天等“不容有失”的领域来说,是无价之宝。

第三,对自己的成长负责,而不是对榜单负责。

与其每个月焦虑地刷新 TIOBE 的排名,不如去问自己几个更本质的问题:

  • 我所处的行业,未来 3-5 年最核心的技术瓶颈是什么?
  • 为了解决这些瓶颈,我需要掌握哪些不可替代的底层能力?
  • 哪门语言的生态和哲学,与这个方向最契合?

你的技术护城河,从来不是由 TIOBE 的排名决定的,而是由你所处行业以及要解决问题的深度决定的。

小结:你的价值,与榜单无关

TIOBE 的这份榜单,与其说是一份严肃的技术报告,不如说是一场成功的“引流狂欢”。

它用一个看似客观的数据,精准地挑动了每个程序员心中最敏感的那根“身份焦虑”神经。

但作为身处一线的工程师,我们必须保持清醒。

衡量一门技术价值的唯一标准,从来不是它在搜索引擎上的热度,而是它在真实的商业世界里,解决了多大、多复杂、多有价值的问题。

当你在用 Rust 构建着下一代安全操作系统,或者用它重写着公司最核心的交易引擎时,你根本无需关心 TIOBE 上的排名是 16 还是 60。

因为你正在创造的价值,早已不是这些过时的“声量指标”所能衡量的。

你的技术栈没有背叛你,但你的认知,可能会。


今日互动探讨:

你觉得 TIOBE 对 Rust“增长放缓”的判断准确吗?你认为 Rust 陡峭的学习曲线,是它最大的优势,还是最大的障碍?

欢迎在评论区分享你的看法!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

为什么说 go 语句是新时代的 goto?四大法则拯救失控 goroutine

2026-04-16 07:34:11

本文永久链接 – https://tonybai.com/2026/04/16/structured-concurrency-in-go-research-oriented-perspective

大家好,我是Tony Bai。

Go 语言的 go 关键字是并发编程史上的一次民主化革命,它让并发变得前所未有的廉价和简单。只需在一个函数调用前加上 go,我们就拥有了一个并发执行的任务。

这种语法是如此的诱人,以至于新手 Gopher 往往会沉迷于创建成千上万个 Goroutine。

随着 Go 语言步入第 16 个年头,学术界和工程界也开始重新审视这种“极简主义”带来的副作用。

2025 年 3 月,一篇发表在《Scientific Research Journal》上的重磅论文《Structured Concurrency in Go: A Research-Oriented Perspective》,将 Go 的并发模型与 1968 年 Dijkstra 对 Goto 语句的批判联系了起来。

论文作者 Georgii Kliukovkin 指出,这种“发射后不管(Fire-and-Forget)”的模式,虽然在 Hello World 级别的程序中运行良好,但在大规模分布式系统中,它是资源泄漏、死锁和竞态条件的温床。

我们日常也常听到这样的抱怨:“Go 的并发很简单,但写出正确的并发代码很难。” 这并非语言本身的缺陷,而是因为我们缺乏一种与语言灵活性相匹配的约束纪律。这种纪律,就是结构化并发

本文将深入解读这篇论文,探讨为何“不受限制的 Goroutine”正在成为新时代的“Goto 语句”,以及我们如何通过结构化并发(Structured Concurrency)的四大法则,将失控的协程重新关回笼子,构建坚如磐石的系统。

历史的镜像——从 Goto 有害论到 Goroutine 有害论?

要理解“结构化并发”,我们必须先回顾历史。

1968年的呼喊:结构化编程的诞生

在 20 世纪 60 年代,编程界流行的是“非结构化编程”。开发者可以随心所欲地使用 goto 语句在代码的任意位置跳转。这种自由带来了极大的灵活性,但也导致了所谓的“意大利面条代码(Spaghetti Code)”——控制流杂乱无章,难以追踪程序的执行路径,维护简直是噩梦。

1968 年,图灵奖得主 Edsger W. Dijkstra 发表了那篇著名的《Go To Statement Considered Harmful》(Goto 语句有害论)。他主张废除无限制的跳转,转而使用结构化编程(Structured Programming):即所有的逻辑都应由顺序结构、选择结构(if/else)和循环结构(for/while)以及函数调用(Function Call)组成。

结构化编程的核心价值在于“黑盒化”。当你调用一个函数时,你确信控制权最终会回到你手中(除非死循环或崩溃);你确信该函数内部的变量不会污染外部环境。这种“入口-出口”的对称性,是软件可维护性的基石。

2025年的回响:go 语句 即 Goto

论文提出了一个让人振聋发聩的观点:Go 语言中的 go 语句,在某种意义上,就是并发领域的 goto。

当你执行 go func() 时,你实际上是启动了一个新的执行流,它跳出了当前的词法作用域(Lexical Scope)。

  • 它什么时候开始?不确定。
  • 它什么时候结束?不知道。
  • 它如果 Panic 了会怎样?可能会炸掉整个程序。
  • 父函数返回了,它还在运行吗?很有可能。

这种“射后不理(Fire-and-Forget)”的模式,破坏了代码的封装性。就像当年的 goto 打破了控制流的结构一样,不受约束的 go 语句打破了并发流的结构。

结构化并发的目标,就是要把这些“野生”的 Goroutine 重新关进“代码块”的笼子里,让并发程序的生命周期像同步程序一样清晰、可预测。

打破幻象——Go 并发的三个误区

在引入解决方案之前,论文首先抨击了 Go 社区中常见的三个关于并发的迷思。这些误区往往是导致系统不稳定的根源。

误区 1:“Goroutine 极度廉价,所以可以随便开”

是的,Goroutine 的初始栈只有 2KB,但这只是“内存”成本。从“生命周期”的角度看,一个泄露的 Goroutine 是极其昂贵的。

如果不加控制地启动 Goroutine 而不确保其退出,这些“孤儿”协程可能会:

  • 持有数据库连接或文件句柄不释放。
  • 阻塞在某个永远不会发送数据的 Channel 上。
  • 阻止垃圾回收器(GC)回收其引用的对象。

在长期运行的服务中,这种微小的泄漏会像滚雪球一样,最终导致服务 OOM(内存溢出)。

误区 2:“Channel 解决了所有同步问题”

Rob Pike 的名言“不要通过共享内存来通信,要通过通信来共享内存”被许多人奉为圭臬。然而,Channel 并不是银弹。

Channel 实际上引入了复杂的状态机问题:

  • 向已关闭的 Channel 发送数据会 Panic。
  • 从 nil Channel 读取会永久阻塞。
  • 无缓冲 Channel 容易导致死锁。
  • 过多的 Channel 会导致逻辑碎片化,增加认知负担。

论文强调,Channel 是一种传输机制,而不是一种架构保障。没有设计良好的生命周期管理,Channel 只会让 Bug 变得更难调试。

误区 3:“Go 的并发代码很容易测试”

Go 提供了 go test -race,但这远远不够。并发 Bug 往往是非确定性的(Heisenbugs),在本地开发环境(低负载、少核)下可能永远不会出现,一上生产环境(高负载、多核)就崩溃。

如果代码缺乏结构化,测试将变得极其困难。你无法确定在断言(Assert)的那一刻,后台的 Goroutine 是否已经完成了数据的写入。结构化并发通过明确的“等待”机制,能让并发测试变得像同步测试一样稳定。

核心法则——构建坚固的并发大厦

既然 Go 语言层面(目前)没有强制的结构化并发语法(不同于 Java Project Loom 的 StructuredTaskScope 或 Python Trio 的 Nursery),我们需要依靠工程纪律和设计模式来实现它。论文详细阐述了四大核心法则。

法则一:Scope 闭环原则 —— 在谁的 Scope 启动,就在谁的 Scope 等待

定义任何启动 Goroutine 的函数,必须负责等待它们结束。

这是结构化并发的第一天条。绝不允许 Goroutine 的生命周期“逃逸”出启动它的函数。这保证了当函数返回时,它所衍生的所有并发工作都已完结,资源已释放。

❌ 反模式:泄露的抽象

// 这是一个危险的模式:函数返回了,但后台任务还在跑
// 调用者无法知道任务何时完成,也无法处理 panic
func FireAndForget() {
    go func() {
        // 执行一些可能会阻塞很久的任务
        // 这里发生的一切,父函数都无法控制
    }()
}

✅ 正模式:Wait 优于 Sleep

论文强烈建议使用 sync.WaitGroup 或 errgroup 来显式地界定生命周期边界。

func ProcessStructured(items []Data) {
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        // 使用闭包捕获变量时需注意
        go func(val Data) {
            defer wg.Done()
            process(val)
        }(item)
    }

    // 关键点:在函数返回前,必须收敛所有并发流
    // 这形成了一个清晰的“并发块”
    wg.Wait()
}

通过这种方式,ProcessStructured 函数的行为变成了“同步”的黑盒。调用者不需要知道它内部是否使用了并发,只需要知道“当函数返回时,所有工作都已完成”。

法则二:同步外观原则 —— API 应当表现为“同步”

定义即使函数内部使用了高并发,对外暴露的 API 签名应当是同步阻塞的。

这是一个看似反直觉的建议。既然我们写的是并发程序,为什么 API 要设计成同步的?

论文指出,异步 API(如返回一个 <-chan Result 或 Future)具有“传染性”。一旦你的函数返回了一个 Future,调用者就必须处理这个 Future 的等待逻辑,这会层层向上传递,导致整个调用链都充满了并发管理的细节。

经典案例:http.ListenAndServe

Go 标准库的 http.ListenAndServe(“:8080″, nil) 是结构化并发 API 设计的典范。

  • 内部:它是一个极其复杂的并发系统,为每个进来的 TCP 连接启动一个新的 Goroutine。
  • 外部:它是一个简单的阻塞函数。
// 调用者代码
err := http.ListenAndServe(":8080", nil)

// 当这行代码返回时,我们确切地知道:
// 1. 服务已经停止了。
// 2. 或者发生了错误(如端口冲突)。

如果 ListenAndServe 被设计成异步返回(即在后台启动服务后立即返回),那么调用者将面临巨大的困扰:我该如何知道服务启动成功了?如果启动失败,错误去哪里了?主进程该何时退出?

除非是专门的任务调度器,否则业务逻辑函数的 API 应该看起来是同步阻塞的。让调用者去决定是否使用 go 关键字来调用它。

法则三:所有权原则 —— 在哪写入,就在哪关闭

定义只有负责向 Channel 写入数据的 Goroutine,才有资格关闭该 Channel。

Channel 的关闭操作是 Go 并发中最容易导致 Panic 的环节(向已关闭的 Channel 发送数据)。论文强调,结构化并发可以极大地简化 Channel 的管理。

原则非常简单:谁生产,谁负责清理。 接收者(Consumer)永远不应该关闭 Channel,因为通过关闭 Channel 来通知生产者“我读完了”是一种错误的设计(应该使用 Context 来取消)。

结合法则一,如果生产者 Goroutine 的生命周期是受控的,那么 Channel 的生命周期自然也是受控的。

func Producer() <-chan int {
    ch := make(chan int)

    // 启动生产者协程
    go func() {
        // defer close 确保无论正常退出还是 panic,channel 都会关闭
        // 避免接收者永久阻塞
        defer close(ch) 

        for i := 0; i < 10; i++ {
            ch <- i
        }
    }()

    return ch
}

法则四:物理封装原则 —— 数据与锁不分家

定义将共享的可变数据(Mutable State)与保护它的同步原语(Mutex)封装在同一个结构体中。

在共享内存的并发模型中,最大的噩梦是“锁与数据分离”。例如,你定义了一个全局变量 var Cache map[string]int,然后又定义了一个全局锁 var Mu sync.Mutex。随着代码量的增加,开发者很容易忘记在访问 Cache 时加锁,或者错误地使用了其他的锁。

论文建议采用一种“物理强绑定”的策略:

type SafeCounter struct {
    // 1. 将锁作为结构体的第一个字段
    mu sync.Mutex

    // 2. 受保护的数据应当是私有的(小写)
    // 强制外部必须通过方法来访问
    values map[string]int
}

// 3. 只有通过这个方法才能访问数据
func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    // 4. 利用 defer 确保锁的释放与函数作用域绑定
    defer c.mu.Unlock()

    c.values[key]++
}

这种模式被称为 Monitor Pattern(监视器模式)。它通过封装强制实施了并发安全,将“会不会加锁”的问题变成了“能不能调用方法”的问题,后者由编译器保证,前者只能靠人品。

进阶——超越标准库的尝试

虽然标准库提供了 sync.WaitGroup 和 context,但要完美实现结构化并发,样板代码依然繁多。论文提到了社区中一些优秀的尝试,其中最值得关注的是 Sourcegraph 开源的 conc 库

conc 库试图解决标准库 WaitGroup 的两个痛点:

  1. Panic 逃逸:在标准 go func 中,如果子协程 panic,整个程序会直接崩溃(Crash),父协程无法 recover。这对于高可用服务是致命的。
  2. Error 传播:WaitGroup 不支持错误返回,需要开发者自己维护一个 err 变量或使用 errgroup。

conc 提供了增强版的 WaitGroup:

import "github.com/sourcegraph/conc"

func main() {
    var wg conc.WaitGroup

    wg.Go(func() {
        // 如果这里 panic 了
        panic("something went wrong")
    })

    // Wait() 会自动捕获子协程的 panic
    // 并将其重新抛出或作为错误返回(取决于具体 API)
    // 从而避免进程直接崩溃
    wg.Wait()
}

这种工具库的出现,标志着 Go 社区正在从“手动管理并发”向“自动化管理并发”演进,这正是结构化并发理念的工程化落地。

小结:从“能用”到“可控”

Go 语言通过 go 关键字将并发编程的门槛降到了历史最低,赢得了云计算时代的入场券。但在构建大规模、高可靠的系统时,我们不能止步于“能用”。

这篇学术论文为我们提供了一个冷静的视角:并发不是目的,只是手段。 失控的并发是灾难,只有受控的并发才是生产力。

结构化并发不是一种束缚,而是一种保护。它要求我们在写下每一个 go func 的时候,都要问自己三个问题:

  1. 它什么时候结束?
  2. 谁负责等待它结束?
  3. 如果它出错了,谁来处理?

只有当这三个问题都有明确答案时,我们才能说,我们真正掌握了 Go 的并发艺术。

参考资料


你更倾向于哪一派?

有人认为 Go 的自由是生产力之源,有人认为约束才是工程的救赎。在你的项目中,你是否也曾因为“射后不理”的 goroutine 踩过坑?你认为 Go 官方是否应该在语言层面引入类似 Java 或 Python 的结构化并发原生支持?

欢迎在评论区分享你的看法或“血泪史”!

想深入掌握 Go 并发调度的底层原理?点击查看我的微专栏《Go 并发调度艺术》。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

C++ 社区内部大讨论:新特性到底是“生产力革命”,还是“叠加的复杂性”?

2026-04-15 08:27:57

本文永久链接 – https://tonybai.com/2026/04/15/cpp-community-debate-productivity-revolution-vs-complexity

大家好,我是Tony Bai。

如果你把编程语言比作工具,Go 是一把极简的手术刀,精准且克制;Rust 是一套带智能传感器的外骨骼装甲,严苛且安全。

而 C++ 呢?它更像是一把在过去四十年里不断被加挂零件的、超重型复合瑞士军刀。

最开始,它只有刀片和叉子;后来,它加了锯子、剪刀和钳子;再后来,它甚至被塞进了一套显微镜和一支激光笔。在开发者眼里,它是能解决世间一切难题的万能神兵,但也是一个重到让你拿不稳、甚至随时可能切到自己手指的“庞然大物”。

但就在前几天,r/cpp 这个拥有近 10 万 C++开发者的顶级社区里,一篇名为《现代 C++ 是让我们更高效了… 还是更复杂了?》的帖子,引发了一场深度大讨论。

发帖人发出了灵魂拷问:

“C++20/23 给我们带来了 Ranges、协程(Coroutines)、Concepts、Modules……这些新特性真的很酷,我也在用。但我总在想,我们是不是在用这些东西吓跑新人的同时,眼睁睁地看着老代码库永远冻结在 C++98?现代 C++ 对生产力来说,到底是一场革命,还是在原本已经足够复杂的巨兽身上,又叠加了一层复杂性?”

这篇帖子,精准地戳中了每一个 C++ 开发者心中最深的困惑。短短一天,就吸引了上百条充满血泪与思考的评论。

今天,我们就来复盘这场顶级的社区大讨论,看看这柄“瑞士军刀”在疯狂“堆料”的背后,到底藏着怎样的挣扎、分裂与反思。

分裂的社区:C++98 遗老、C++17 中坚与 C++23 先锋的“平行宇宙”

在这场大讨论中,我仿佛看到了 C++ 社区三个泾渭分明的平行宇宙。

宇宙一:永远的 C++98/11 ——“能跑就行,别动!”

评论区里,点赞最高的一派观点,充满了对“存量代码”的敬畏与无奈。

一位开发者吐槽道:

“我在太多项目里因为各种原因被迫使用旧标准,以至于我已经懒得去关心最新的特性了。我感觉很多专业场景就是这样:我们用着‘穴居人 C++’,因为那玩意儿安全(指熟悉)、方便。”

另一位开发者更是直接引用了 Matt Godbolt 的名言:“向后兼容性才是 C++ 的超能力。”

“别想着重构了,那只会破坏一切。跑了 20 年没 Bug 的生产代码是无价之宝,别碰它!”

更有甚者,因为芯片厂商的编译器只支持 C++89,或者因为“法律原因”,一个项目被迫在一个 3 年前的工具链上锁死 7 年。

在这个宇宙里,C++20 的新特性,对他们来说都像火星科技一样遥远。

宇宙二:拥抱 C++20/23 ——“旦用难回,太香了!”

与“遗老派”形成鲜明对比的,是那些已经吃上新标准红利的“先锋派”。

有开发者激动地表示:

“自从我开始用协程(Coroutines)写网络 IO 代码,我再也回不去以前那种回调地狱了!”

另一位则对 C++23 的 std::println 赞不绝口:

“我离不开 C++23,完全是因为 println。我不知道我还在用 23 的什么其他特性,但光这一个就太棒了。”

对于这部分开发者来说,现代 C++ 的每一个新特性,都是一次生产力的解放。他们就像一群拿到了新玩具的孩子,兴奋地探索着 Ranges 的组合魔法和 Concepts 带来的清爽报错。

宇宙三:爱恨交织的“中间派”——“一半是天堂,一半是地狱”

这或许是最大多数 C++ 开发者的真实写照。

正如帖子作者所言,新特性确实很酷,但它们也带来了巨大的认知负荷和决策成本。

一个开发者的评论获得了 82 个高赞:

“我们大多数人只用了 C++ 语言特性的一小部分。这就像一个‘鸡生蛋、蛋生鸡’的问题:这里有个新特性,但我不知道该怎么用、为什么要用;或者,我代码里有个痛点,可能能用新特性解决,但我不知道该用哪个。”

这种“选择的困境”,正是 C++ “自由”的代价。

底层矛盾:C++ 的“集市”哲学 vs 团队的“教堂”困境

为什么 C++ 会演变成今天这样?

评论区里的一位开发者给出了一个极其精妙的比喻:“集市(Bazaar)”

“我绝对热爱 C++ 的一点是:它有一个特性集市,你可以挑选你认为适合你项目的工具。如果你看其他语言,比如 Java 要求万物皆对象,Haskell 要求万物皆函数。C++ 给了你面向对象,你讨厌它?没问题,不用就行。你喜欢函数式?C++ 也支持。”

这种“万物皆可选”的自由,是 C++ 最大的魅力,当然也是它最大的诅咒。

因为在一个团队里,当每个人都从“集市”上拿回了自己最喜欢的锤子时,整个项目就会变成一个风格迥异的“建筑工地”。

原帖作者自己也承认:

“自由是真实的,但这也意味着两个 C++ 代码库可能看起来像两种完全不同的语言。”

当一个文件里还在用裸指针和手动内存管理,而另一个文件里已经用上了 std::unique_ptr 和 std::span;当一部分团队在用 boost::asio 写回调,而另一部分团队在用 C++20 的协程……

Code Review 就变成了一场噩梦。

反思:“技术债”还是“护城河”?

这场大讨论的背后,其实隐藏着两个更深层次的软件工程哲学问题。

问题一:新特性是“锦上添花”,还是“非用不可”?

很多 C++ 老兵认为,现代 C++ 增加的很多特性,比如 Ranges 和 Coroutines,其实早在几十年前的 LISP 语言里就已经被证明是伟大的思想。C++ 只是在用一种极其缓慢、极其复杂的方式,在“偿还”几十年前欠下的“技术债”。

但另一些人认为,C++ 的伟大恰恰在于,它能用“零成本抽象(Zero-cost Abstraction)”的硬核方式,将这些高级思想,落地到对性能要求极致的生产环境中。

问题二:复杂性是“敌人”,还是“朋友”?

一位开发者的评论极具辩证思维:

“这(新特性)既是好事,也是坏事。学习的门槛确实在不断提高。但这些工具是实实在在有用的,它们让你能用更干净、更安全、更高效的方式表达代码。”

当 Go在极力做“减法”,试图降低开发者的心智负担时,C++ 却似乎在坚定地走着另一条路:它信任开发者是专家,它把所有的选择权和复杂性都交给你,让你自己去构建属于你的“最佳子集”。

这就像驾驶一架拥有几百个仪表盘的航天飞机。对于新手来说是灾难,但对于顶尖的飞行员来说,每一个按钮都意味着更精准的控制力。

出路何在?:拥抱“渐进式现代化”

在这场看似无解的“内部大讨论”中,我们依然能找到一条充满智慧的中间路线。

有人分享了一个极具参考价值的真实案例:

他成功地在一个庞大的 C++98 代码库中,引入了一个用 C++17 编写的新功能模块。他没有去重构任何老代码,只是简单地升级了编译器和构建脚本。结果:新特性带来了性能的提升和开发效率的飞跃,而老代码依然稳定运行。

这或许就是现代 C++ 正确的打开方式:不要试图用新标准去“革命”旧代码,而是在写新代码时,大胆地、有选择地拥抱新特性。

让 C++98 的归 C++98,让 C++23 的归 C++23。在一个代码库中,允许不同时代的“方言”共存,用新增的模块去逐步“稀释”历史的包袱。

小结:一场关于“自由”的伟大实验

C++ 的这场大讨论,没有赢家。

它只是再次向我们证明了这门语言的“独一无二”:它是一门民主的语言。它给了你选择一切的自由,也要求你为自己的选择承担一切后果。

用一位开发者的话来说:

“Rust 强加给你它的观点;而 C++ 要求你有你自己的观点。这就像专制与民主的区别。大多数时候,民主只是一个被猴子笼子管理的、组织混乱的马戏团。但我更喜欢民主。

或许,对于我们这些已经习惯了 Go 和 Rust 那种“带你走”模式的开发者来说,偶尔回头看看 C++ 这个充满“混沌与活力”的古老集市,会让我们对“软件工程”这门手艺,有更深刻的理解。

资料链接:https://www.reddit.com/r/cpp/comments/1sihs1w/is_modern_c_actually_making_us_more_productive_or


今日互动探讨:

在你的技术生涯中,你是否也曾被困在某个古老的“技术版本”里动弹不得?对于 C++ 这种“万物皆可选”的自由哲学,你是向往,还是恐惧?

欢迎在评论区分享你的看法!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

别再无脑 go func() 了!Go 资深布道师 Dave Cheney 的 Goroutine 管理哲学

2026-04-13 06:29:12

本文永久链接 – https://tonybai.com/2026/04/13/dave-cheney-goroutine-management-philosophy

大家好,我是Tony Bai。

在 Go 语言的江湖里,go func() 就像一把绝世好剑。它轻灵、锋利,只需几个字符,就能让你瞬间拥有“分身术”,并发地处理海量任务。Go 团队曾自豪地告诉我们:Goroutine 很廉价,你可以随手启动成千上万个。

于是,我们习惯了在代码里肆意挥洒:

  • HTTP 请求来了?go handle()。
  • 要写日志?go log()。
  • 要发通知?go notify()。
  • … …

我们以为自己掌握了并发的捷径。

但就在去年的 GopherCon Singapore 技术大会上,Go 社区的资深布道师 Dave Cheney,却用一场充满哲学思考的演说,给所有 Gopher 敲响了警钟。

他的核心论点很明确:Goroutine 绝非免费的午餐,它是一种需要付出代价的“有限资源”。如果你只管启动(Start)而不懂如何停止(Stop),你并没有在写高效的并发程序,你只是在为系统埋下慢性自杀的伏笔。

今天,我们就来深度拆解 Dave Cheney 的这场重要演讲,梳理出他在 AI 大模型和微服务时代,为我们总结的 “Goroutine 声明周期管理四大哲学”以及他最终给出的Goroutine管理方案。

哲学一:内存是有价的,而 Goroutine 是“内存之根”

Dave Cheney 在演讲开头提出了一个极其硬核的观点:内存不是无限的,它是和数据库连接、文件句柄一样的有限资源。

在 Java 或 C++ 中,我们要时刻担心内存泄漏。但在 Go 里,我们觉得有 GC(垃圾回收器)在,一切无忧。

然而,Dave 指出了一个被 99% 的人忽略的真相:在 Go 的世界里,每一个正在运行的 Goroutine,都是一个“GC 根节点(GC Root)”。

什么意思?

只要一个 Goroutine 还在运行,它所引用的所有内存、它栈上的所有变量、它指向的所有堆对象,GC 都绝对不敢回收。

“你可以关闭一个文件,可以解锁一个互斥锁。但你如何‘回收’一个失控的 Goroutine?”

如果你启动了一个 Goroutine 后失去了对它的追踪,它就变成了一个永远无法回收的“内存僵尸”。它不仅自己霸占着 2KB 以上的栈空间,更可能死死拽着几个 GB 的业务对象不撒手。

哲学二:永远不要启动一个你不知道如何停止的 Goroutine

这是 Dave Cheney 演讲中最核心的一句军规:Never start a goroutine without knowing how it will stop.

为了证明“野 Goroutine”的破坏力,Dave 在现场演示了一个极其经典的血泪 Demo。

他写了一个 HTTP 服务器,为了让请求秒回,他把日志记录放到了后台:go logRequest(r)。

接着,他通过重定向标准输出模拟了下游日志系统网络拥堵、写入被阻塞的场景。

恐怖的一幕发生了:

服务器内存开始疯狂飙升,每秒钟都有成百上千个新的 Goroutine 被创建,但因为输出被阻塞,它们全都卡在写入的那一行,一个都死不掉。
不到一分钟,整个程序因为 OOM(内存溢出)当场暴毙。

Dave 的结论非常冷酷:

启动一个 Goroutine 只需要 1 微秒,但如果不考虑它的“死法”,这个 Goroutine 最终会成为杀掉你整个集群的凶手。

哲学三:不要强迫它停,要“优雅地求它停”

在 Java 中,曾经有一个 thread.stop() 方法,后来被禁用了,因为它会引发不可控的资源损坏。Go 语言聪明地避开了这个坑:Go 没有任何一种方式,能让一个 Goroutine 强行停止另一个。

你只能通过 “协同(Cooperation)”

Dave 强调,defer 是 Goroutine 的“临终遗言”。所有的资源释放(文件关闭、锁解除)都必须放在 defer 里。

而管理这一切的唯一“生死符”,就是 Context

在 Dave 的哲学里,一个合格的后台服务函数,必须长成这样:

func (s *Service) Run(ctx context.Context) error {
    // 1. 临终遗言:无论如何,最后一定要清理战场
    defer s.cleanup() 

    for {
        select {
        case <-ctx.Done():
            // 2. 收到“生死符”,优雅退出
            return ctx.Err()
        case task := <-s.taskChan:
            s.process(task)
        }
    }
}

你必须给 Goroutine 一个“想得开”的机会,让它在收到 ctx.Done() 时,带着所有的 defer 体面地离开。

哲学四:把并发权留给调用者,而不是库

这是 Dave Cheney 给库开发者(Library Authors)提出的最高阶要求。

他引用了另一位大神 Peter Bourgon 的话:“Leave concurrency to the caller.”

一个设计糟糕的库: 在你调用 NewProvider() 的时候,悄悄在后台启动了一个 Goroutine 去跑心跳,却没给你返回任何停止它的句柄。这种库是不可靠的。

一个具有“管理哲学”的库: 即使它需要后台运行,它也应该把那个 Run 函数暴露给用户,让用户自己决定:

  • 是开一个 Goroutine 去跑它?
  • 还是把它扔进一个 errgroup 里集中管控?
  • 还是干脆同步运行它?

只有这样,作为顶层架构师的你,才能真正实现所有子系统的 “同生共死”

历史的挣扎:从 Tomb 到 Errgroup,我们与“失控”的斗争

事实上,Go 社区与“Goroutine 管理”这个恶魔的斗争,从 2012 年就开始了。Dave带着我们一起回顾了一下社区的方案,虽然每个方案都不完美!

第一代武器:Tomb (坟墓)

来自 Canonical(Ubuntu 母公司)的 Juju 项目,发明了 tomb 包。它通过一个 t.Go() 方法来启动 Goroutine,并用一个 t.Wait() 来等待它们全部结束。但它的缺点是,如何通知这些 Goroutine“你们该停了”,依然需要开发者手动传来传去。

第二代武器:Errgroup

由 Go 社区大神 Brad Fitzpatrick 编写的 errgroup,极大地简化了“并发执行一组任务,并收集第一个错误”的场景。但它同样没有解决“如何优雅地通知所有任务提前中止”的问题。

第三代武器:OK Log 的 group 包

由 Peter Bourgon 设计的 group 包,首次引入了一个极其优雅的范式。它要求你在添加一个任务时,必须同时提供两个函数:一个 execute 函数(如何启动),和一个 interrupt 函数(如何打断)。

这是一种“契约式”的设计,强制开发者在启动一个 Goroutine 的时候,就必须想好如何杀死它。

Dave Cheney 的Goroutine管理方案

在吸收了上述哲学以及社区尝试后,Dave 给出了一个现代 Go 微服务的“标准起手式”,当然也是他自己的Goroutine管理方案:pkg/group。

在吸收了社区十几年来的所有经验和教训之后,Dave Cheney 在演讲的最后,亮出了他自己多年来在无数个项目中沉淀下来的“终极武器”——一个同样名为 group 的、集大成的 Goroutine 管理库:pkg/group,也可以认为是一个现代 Go 微服务的“标准起手式”:

在 Dave Cheney 的 group 里,你添加的每一个任务,都必须是一个接受 context.Context 作为参数的函数。

g.Add(func(ctx context.Context) error {
    // ...
})

Context 成了所有 Goroutine 唯一的“生死符”。无论是超时、是上游请求被取消、还是整个服务收到了 SIGTERM 信号准备关闭,都会通过 ctx.Done() 这个唯一的通道,通知到每一个角落。

在 Dave Cheney 的 group 中,任何一个子 Goroutine 发生的 panic,都不会导致整个进程崩溃。它会被 recover 住,转化为一个 error,然后触发整个 group 的优雅关闭流程。

pkg/group的使用典型示例如下:


在这段代码里,所有的后台服务被捆绑成了一个“命运共同体”。任何一个服务失败,或者 k8s 发来关闭 Pod 的信号,都会导致所有服务一起进入优雅关闭流程,确保数据不丢失、连接被妥善断开。

小结

从“启动”到“坟墓”,Dave Cheney 为我们揭示了并发编程的下半场:Goroutine管理

go func() 赋予了我们随手创造并发的权力,但真正体现架构师功力的,是你管理这些并发生命周期的责任感。

下一次,当你在键盘上敲下那几个字符时,请停顿一秒。

想一想:这把剑挥出去,你还能收回来吗?

资料链接:https://www.youtube.com/watch?v=eJLVT157BSs


今日互动探讨:

在你的项目中,是否曾遇到过 Goroutine 泄漏导致的内存灾难?你是如何定位出那个“失踪”的 Goroutine 的?

欢迎在评论区分享你的避坑经验!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.