<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; GC</title>
	<atom:link href="http://tonybai.com/tag/gc/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Thu, 30 Apr 2026 23:46:25 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>从 1960 到 2026：一文看透 Java、Go、Python 垃圾回收器的原理与演进</title>
		<link>https://tonybai.com/2026/04/07/garbage-collectors-deep-dive/</link>
		<comments>https://tonybai.com/2026/04/07/garbage-collectors-deep-dive/#comments</comments>
		<pubDate>Tue, 07 Apr 2026 00:17:15 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ColoredPointers]]></category>
		<category><![CDATA[ConcurrentMarking]]></category>
		<category><![CDATA[CopyingCollector]]></category>
		<category><![CDATA[CPython]]></category>
		<category><![CDATA[Cycles]]></category>
		<category><![CDATA[EscapeAnalysis]]></category>
		<category><![CDATA[G1GC]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[GenerationalHypothesis]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[HybridWriteBarrier]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[latency]]></category>
		<category><![CDATA[MarkAndSweep]]></category>
		<category><![CDATA[MemoryManagement]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[ReferenceCounting]]></category>
		<category><![CDATA[StopTheWorld]]></category>
		<category><![CDATA[STW]]></category>
		<category><![CDATA[Throughput]]></category>
		<category><![CDATA[TriColorMarking]]></category>
		<category><![CDATA[WriteBarrier]]></category>
		<category><![CDATA[ZGC]]></category>
		<category><![CDATA[三色标记]]></category>
		<category><![CDATA[全线停顿]]></category>
		<category><![CDATA[内存管理]]></category>
		<category><![CDATA[写屏障]]></category>
		<category><![CDATA[分代假说]]></category>
		<category><![CDATA[吞吐量]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[复制算法]]></category>
		<category><![CDATA[并发标记]]></category>
		<category><![CDATA[延迟]]></category>
		<category><![CDATA[引用计数]]></category>
		<category><![CDATA[循环引用]]></category>
		<category><![CDATA[有色指针]]></category>
		<category><![CDATA[标记清除]]></category>
		<category><![CDATA[混合写屏障]]></category>
		<category><![CDATA[逃逸分析]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=6154</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/04/07/garbage-collectors-deep-dive 大家好，我是Tony Bai。 为什么 Java 的 G1GC 需要设置停顿目标？Go 的混合写屏障是如何消除栈重扫的？Python 又是如何解决引用计数无法处理的循环引用？ 垃圾回收（GC）不仅是语言运行时的核心，更是理解高性能系统绕不开的坎。 本文翻译自Shubham Raizada的文章《Garbage Collection: From First Principles to Modern Collectors in Java, Go and Python》。 此文通过对历史经典论文的溯源和对现代主流语言底层实现的拆解，构建了一套完整的 GC 知识体系。 文章涵盖了从基础的标记-清除、复制与整理算法，到复杂的三色标记抽象、写屏障机制以及有色指针技术。 无论你是想调优 JVM 性能，还是试图理解 Go 并发垃圾收集的吞吐成本，这篇文章都将为你提供从理论支撑到代码实现的全景视角。 以下是译文全文： 在过去的几年里，我的技术栈经历了从 Java 到 Go，再到 Rust，现在又回到了 Java 的过程。 在这些语言之间切换时，一直绕不开的一个话题就是垃圾回收（Garbage Collection, GC）。Java 和 Go 有 GC，而 Rust 没有。 在基准测试、延迟讨论以及“为什么这个服务变慢了”的对话中，GC 总会出现在某个角落。我经常听到关于 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/04/07/garbage-collectors-deep-dive">本文永久链接</a> &#8211; https://tonybai.com/2026/04/07/garbage-collectors-deep-dive</p>
<p>大家好，我是Tony Bai。</p>
<p>为什么 Java 的 G1GC 需要设置停顿目标？Go 的混合写屏障是如何消除栈重扫的？Python 又是如何解决引用计数无法处理的循环引用？</p>
<p>垃圾回收（GC）不仅是语言运行时的核心，更是理解高性能系统绕不开的坎。</p>
<p>本文翻译自Shubham Raizada的文章《<a href="https://shbhmrzd.github.io/systems/garbage-collection/memory-management/2026/04/01/garbage-collectors-deep-dive.html">Garbage Collection: From First Principles to Modern Collectors in Java, Go and Python</a>》。</p>
<p>此文通过对历史经典论文的溯源和对现代主流语言底层实现的拆解，构建了一套完整的 GC 知识体系。</p>
<p>文章涵盖了从基础的标记-清除、复制与整理算法，到复杂的三色标记抽象、写屏障机制以及有色指针技术。</p>
<p>无论你是想调优 JVM 性能，还是试图理解 Go 并发垃圾收集的吞吐成本，这篇文章都将为你提供从理论支撑到代码实现的全景视角。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/agentic-api-in-action-qr.png" alt="" /></p>
<p>以下是译文全文：</p>
<hr />
<p>在过去的几年里，我的技术栈经历了从 Java 到 Go，再到 Rust，现在又回到了 Java 的过程。</p>
<p>在这些语言之间切换时，一直绕不开的一个话题就是垃圾回收（Garbage Collection, GC）。Java 和 Go 有 GC，而 Rust 没有。</p>
<p>在基准测试、延迟讨论以及“为什么这个服务变慢了”的对话中，GC 总会出现在某个角落。我经常听到关于 GC pauses（GC 停顿）、throughput overhead（吞吐量开销）和 write barriers（写屏障）的讨论，但我并不完全理解底层发生了什么。</p>
<p>在追溯起源时，我读到了 McCarthy 1960 年的论文，这篇论文因引入 Lisp 而闻名，但它也是首次描述 mark-and-sweep（标记-清除）的地方。</p>
<p>这又引导我阅读了 Wilson 1992 年的综述《Uniprocessor Garbage Collection Techniques》，该文将随后的所有发展组织成了一个清晰的分类学。</p>
<p>阅读这两篇文献让我更容易理解现代垃圾收集器，因为 G1GC、ZGC、Go 的并发收集器以及 CPython 的混合方案全都是这些论文所描述思想的变体。我还用 Go 编写了一个简单的玩具级 GC，以便亲自观察其机制。</p>
<p>以下是我在这一过程中的笔记。</p>
<h2>起源论文</h2>
<h3>McCarthy (1960): <a href="https://dl.acm.org/doi/10.1145/367177.367199">Recursive Functions of Symbolic Expressions and Their Computation by Machine</a></h3>
<p>这篇论文因引入 Lisp 而闻名，但垃圾回收器几乎是作为实现细节被埋藏在其中的。McCarthy 需要一种方法来管理符号表达式的内存。Lisp 程序操作的是嵌套的列表（lists of lists of lists），这种递归结构使得要求程序员手动释放内存变得不切实际。因此，他描述了一种自动执行此操作的机制。</p>
<p>该机制分为两个阶段。首先，从程序正在活跃使用的 root（根）变量开始，遍历它们引用的每一个对象，将每个对象标记为 reachable（可达）。其次，扫描所有内存。任何未被标记的对象都是垃圾。将它们重新添加回 free list（空闲列表）。</p>
<p>这就是 mark-and-sweep（标记-清除）。它能自然地处理 cycles（循环引用，因为不可达的循环永远不会被标记），不需要逐个对象的簿记工作，并让程序员可以完全忽略内存管理。</p>
<p>其代价是程序在收集器运行时必须完全停止。每一次分配、每一次计算，所有一切都会冻结，直到标记和清除完成。对于 McCarthy 在 1960 年编写的程序来说，这完全是合理的。</p>
<p>随着程序规模变大并进入对延迟敏感的环境（如处理每秒数千次请求的 Web 服务器），stop-the-world（全线停顿）成了一个难以接受的权衡。现代 GC 研究产生的大部分成果都是为了回答一个问题：如何在不停止世界的情况下进行垃圾内存回收？</p>
<h3>Wilson (1992): <a href="https://dl.acm.org/doi/10.5555/645648.664824">Uniprocessor Garbage Collection Techniques</a></h3>
<p>到 1992 年，三十年的 GC 研究已经产生了许多想法，但缺乏统一的词汇。Wilson 的综述论文将这一切组织了起来。它不是一种新算法，而是一个分类学，为散落在几十年论文中的思想赋予了名称和结构。</p>
<p>Wilson 正式确立了所有后续算法构建其上的三种经典算法。</p>
<p>第一种是 <strong>mark-and-sweep</strong>（标记-清除），即 McCarthy 的原始算法。从 roots 开始，遍历对象图，标记你能触达的所有内容，然后扫过堆并释放任何未标记的内容。它自然处理循环引用，实现简单。缺点是经过足够多的分配和回收循环后，堆会变得 fragmented（碎片化）。存活对象最终散落在各处，中间夹杂着细小的空闲间隙，分配器(allocator)必须更费力地寻找空间。</p>
<p>第二种是 <strong>copying</strong>（复制算法），有时被称为 semi-space（半空间）。其想法是将堆分成两个相等的部分。你在其中一半进行分配，当它填满时，将所有存活对象拷贝到另一半，然后将第一半完全丢弃。碎片消失了，因为存活对象在拷贝过程中被紧密排列在一起。分配速度很快，因为你只需移动一个 bump pointer（碰撞指针）。代价是有一半的内存始终处于空闲状态，等待成为下一次拷贝的目标。</p>
<p>第三种是 <strong>reference counting</strong>（引用计数）。每个对象都记录有多少个指针指向它。当创建一个新引用时，计数增加；当移除一个引用时，计数减少。当计数归零时，对象立即被释放。没有追踪过程，没有停顿，销毁是确定性的。问题在于 cycles（循环引用）。如果两个对象相互指向，即使程序中没有任何其他部分可以触达它们，它们的计数也至少为 1。仅靠引用计数，它们永远不会被释放。</p>
<p>除了这三种算法，Wilson 还探讨了现代垃圾回收器赖以生存的两个观察结果。</p>
<p>第一个是 <strong>generational hypothesis</strong>（分代假说）：大多数对象死得早。在实践中，程序分配的临时对象（中间值、请求作用域的缓冲区、循环变量）往往很快变成垃圾，而只有一小部分对象会贯穿整个程序生命周期。如果你频繁回收年轻对象，偶尔回收老对象，你就能将大部分工作集中在堆中主要是垃圾的部分，这比每次都扫描所有内容的代价要小得多。</p>
<p>第二个是 <strong>tricolor marking</strong>（三色标记），这是一种用于增量和并发收集的抽象。你不再简单地将对象标记为已访问或未访问，而是使用三种颜色：white（白色，尚未见到）、grey（灰色，已见到但子节点尚未扫描）和 black（黑色，已完全处理）。收集器一次处理一个灰色对象。结束时，白色对象即为垃圾。这种抽象使得收集器和应用程序可以同时运行，而不会破坏彼此对堆的视图。Go 的并发 mark-and-sweep 和 ZGC 的并发标记都是这一思想的直接后裔。</p>
<p>本文“现代 GC”部分中的所有内容都可以映射回 Wilson 的分类。工程实现已经变得更加复杂，但底层结构依然如故。</p>
<h2>两种基本方法</h2>
<p>几乎所有的垃圾回收器要么是 reference counting（引用计数），要么是 tracing（追踪），或者是两者的某种结合。Wilson 的论文围绕这一划分进行组织，三十年后依然成立。</p>
<h3>Reference Counting (引用计数)</h3>
<p>每个对象维护一个指向它的引用计数。当引用创建时，计数增加。当引用移除时，计数减少。当计数归零时，对象立即被释放。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-2.png" alt="" /></p>
<p>这是 CPython 所使用的其主要机制。它很简单，并能提供确定性的销毁。当指向文件句柄的最后一个引用消失时，<strong>del</strong> 运行，文件当场关闭，而不是在以后的某个 GC cycle中。</p>
<p>有两个问题使得引用计数无法独立胜任。</p>
<p><strong>Cycles (循环引用)。</strong> 如果对象 A 指向对象 B，且对象 B 指向 A，那么即使程序中没有任何其他部分能触达它们，两者的计数也至少为 1。两者都不会被释放。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-3.png" alt="" /></p>
<p>这并非理论上的边缘案例。循环引用在链表数据结构、父子关系、观察者模式和缓存中自然出现。稍后在介绍 CPython 的 GC 时，我将讨论 Python 如何处理这个问题。</p>
<p><strong>Per-mutation overhead (每次修改的开销)。</strong> 每次指针赋值都需要更新引用计数。在多线程程序中，这些必须是 atomic（原子）操作，成本昂贵得多。每当你将对象传递给函数、返回它或将其赋值给字段时，你都要支付这种代价。</p>
<h3>Tracing (追踪式，即 Mark-and-Sweep)</h3>
<p>追踪式收集器不跟踪单个引用，而是从一组已知的存活引用（称为 root set，根集合）开始，遍历整个对象图。它能触达的每个对象都被标记为存活。其他所有对象都被释放。</p>
<p>Root set 是起点，因此什么算作 root（根）至关重要。不同语言的答案是相同的：root 是 runtime（运行时）无需追踪就能找到的任何引用。这些指针锚定在程序当前的执行状态中，是在任何遍历开始之前你就知道是存活的东西。</p>
<p>在实践中，roots 分为以下几类。</p>
<p>每个活跃 stack frame（栈帧）中的 <strong>local variables</strong>（局部变量）和函数参数都是 roots。程序正在活跃地运行这些函数，因此它们引用的任何内容定义上都是在使用中的。</p>
<p><strong>Global and static variables</strong>（全局变量和静态变量）是 roots，因为它们在程序的整个生命周期内都存在。</p>
<p><strong>CPU registers</strong>（CPU 寄存器）是 roots。因为当 JIT 编译器优化一个热点方法时，它可能会将频繁访问的对象引用保留在 CPU 寄存器中，而不是写回栈。如果 GC 此时运行，寄存器保存着该对象的唯一存活引用。如果 GC 不扫描寄存器，它就会释放一个仍在使用中的对象。为了防止这种情况，运行时在代码中定义了 safe points（安全点），GC 只能在这些点发生，并且在这些点，它会快照寄存器状态以寻找持有的任何引用。</p>
<p><strong>Runtime（运行时）本身</strong>也持有与用户代码无关的 roots。在 JVM 中，class loaders 是 roots：你加载的每个类都由其类加载器引用，只要类加载器存活，它加载的每个类（包括它们的静态字段）就保持存活。Interned strings（常量池字符串）是 roots，因为 String.intern() 将字符串存储在 JVM 维护的共享池中。JNI handles 是 roots，因为当原生 C 或 C++ 代码通过 Java Native Interface 持有 Java 对象的引用时，该引用存在于 Java 堆外的句柄表中，GC 必须扫描它。每个活跃线程都是一个 root，其整个调用栈帧都是 root set 的一部分。</p>
<p>Go 的运行时遵循同样的原则。每个 goroutine 都有自己的栈，必须扫描所有 goroutine 栈以寻找 roots。运行时还跟踪自己的内部数据结构，例如 finalizer 队列，作为 root set 的一部分。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-4.png" alt="" /></p>
<p>核心见解是：roots 是由运行时在无需追踪的情况下就已经知道是存活的东西定义的。其他所有东西必须通过从 root 可达来证明自己的生存权。这就是为什么这个概念是与语言无关的。Java、Go 和 Python 之间的具体 roots 集合有所不同，但原则是一样的：从你知道是存活的地方开始，向外追踪，并回收其余部分。</p>
<p>循环引用被自然处理。如果 A 和 B 相互指向，但都无法从任何 root 到达，则标记阶段永远不会访问它们。它们保持未标记状态并被清除。</p>
<p>代价：朴素的 mark-and-sweep 必须在追踪堆时暂停整个程序。这种 stop-the-world（全线停顿）是早期垃圾回收器的核心问题，也是现代 GC 几十年来工程化改进的重点。</p>
<h3>为什么大多数现代 GC 都是追踪式的</h3>
<p>在具有高分配速率的服务器工作负载中，引用计数的逐次修改成本会积少成多。每次指针写入都会增减计数。在多线程程序中，这些更新必须是原子的，而原子操作很昂贵。在数十个线程中每秒进行数千次分配时，这种开销变得可衡量。此外，循环引用问题无论如何都需要一个补充的追踪步骤。而且追踪式收集器可以做成并发的，在应用程序运行的同时运行，只有简短的停顿。</p>
<p>Java 和 Go 使用追踪式收集器。Python 是一个显著的例外，它以引用计数为基础，并在此之上增加了一层用于追踪循环引用的检测器。</p>
<h2>追踪式的变体</h2>
<p>Wilson 的论文描述了实现追踪的四种方式，每种方式都有不同的权衡。</p>
<h3>Mark-Sweep (标记-清除)</h3>
<p>最简单的追踪式收集器。分为两个阶段：</p>
<ol>
<li><strong>Mark (标记)：</strong> 从 roots 开始，遍历对象图并在每个可达对象上设置标记位。</li>
<li><strong>Sweep (清除)：</strong> 遍历整个堆。任何没有标记位的对象都是垃圾。释放它并将内存添加回空闲列表。</li>
</ol>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-5.png" alt="" /></p>
<p>Mark-sweep 的主要问题是 fragmentation（碎片化）。经过足够的回收周期后，堆看起来就像瑞士奶酪：存活对象散布其间，中间有很小的空闲间隙。你总共可能有 100MB 空闲内存，但没有一个连续的块大到足以满足一次新分配。分配器必须维护一个 free list 并搜索合适的空间，随着堆变得碎片化，这会变慢。</p>
<h3>Copying (Semi-Space，复制算法/半空间)</h3>
<p>堆被分成两个相等的一半：from-space（源空间）和 to-space（目标空间）。分配发生在 from-space，使用简单的 bump pointer（碰撞指针）。当 from-space 填满时，收集器将所有存活对象拷贝到 to-space，更新所有指针，然后交换两者的角色。旧的 from-space 被完全丢弃。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-6.png" alt="" /></p>
<p>分配速度极快，因为它只是一个指针移动。Compaction（压缩）自然发生。代价是任何时候只有一半的堆可用。</p>
<h3>Mark-Compact (标记-整理)</h3>
<p>标记阶段与 mark-sweep 相同，但收集器不是简单地释放未标记的对象，而是将所有存活对象滑动到堆的一端。这消除了碎片，且没有复制算法 50% 的内存开销。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-7.png" alt="" /></p>
<p>缺点是整理需要对堆进行多次扫描：一次标记，一次计算新地址，一次更新所有指针，一次移动对象。</p>
<h3>The Generational Hypothesis (分代假说)</h3>
<p>Wilson 论文中最具影响力的观察之一是弱分代假说：大多数对象死得早。</p>
<p>在典型的 Web 服务器中，每个请求都会创建临时对象（解析器、中间字符串、响应构建器），它们只存活几毫秒。配置对象、连接池和缓存则贯穿整个应用程序生命周期。</p>
<p>分代收集器利用这一点，将堆划分为 generations（代）。新对象进入 young generation（年轻代）。如果它们在几次回收中幸存下来，就会被提升到 old generation（老年代）。年轻代回收频繁且速度快，因为那里的大多数对象已经死了。老年代回收较少发生。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-8.png" alt="" /></p>
<p><strong>Eden</strong> 是所有新对象出生的地方。每一个 new Object() 都去这里。它很快就会填满，因为大多数程序分配速率很高。</p>
<p><strong>S0 和 S1</strong> 是两个较小的 survivor spaces（幸存者空间）。当 Eden 填满并运行 minor GC（次要回收）时，收集器将 Eden 中的每个存活对象拷贝到其中一个空间（比如 S0）。下一次回收时，来自 Eden 和 S0 的幸存者被拷贝到 S1。再下一次，回到 S0。它们在每个周期轮换。这是年轻代中的复制算法：没有碎片，没有空闲列表，只有两半空间轮流充当目标。代价是你需要两个幸存者空间，但它们保持得很小，因为到回收运行时，Eden 中的大多数对象都已经死了。</p>
<p><strong>Promotion to old generation (提升到老年代)。</strong> 在对象在 S0 和 S1 之间反弹足够多次之后（JVM 中的默认阈值是 15 次），收集器认定它已赢得了一席之地，并将其提升到老年代。老年代回收频率低得多，并且使用更重的算法（标记-整理而非复制），因为那里的对象庞大且长寿。</p>
<p>关键的实现挑战是跟踪从老对象到新对象的引用。如果一个老对象指向一个年轻对象，即使没有年轻代 root 指向它，该年轻对象也绝不能被回收。这通过 write barrier（写屏障）解决，即在每次指针写入时注入的一小段代码，用于在 remembered set（记录集）中记录跨代引用。</p>
<h2>用 Go 构建一个玩具级 Mark-and-Sweep GC</h2>
<p>我写了一个极简的 mark-and-sweep 收集器来使这些概念具体化。它大约有 70 行代码，演示了完整循环：分配对象、构建对象图、从 roots 标记以及清除不可达对象。</p>
<pre><code>package main

import "fmt"

// Object 代表一个在堆上分配的对象。
type Object struct {
    name     string
    marked   bool
    children []*Object
}

// VM 是一个带有垃圾回收器的微型虚拟机。
type VM struct {
    heap  []*Object
    roots []*Object // 模拟栈变量和全局变量
}

// NewObject 在 VM 的堆上分配一个对象。
func (vm *VM) NewObject(name string) *Object {
    obj := &amp;Object{name: name}
    vm.heap = append(vm.heap, obj)
    return obj
}

// mark 从每个 root 开始遍历并标记所有可达对象。
func (vm *VM) mark() {
    for _, root := range vm.roots {
        vm.markObject(root)
    }
}

func (vm *VM) markObject(obj *Object) {
    if obj == nil || obj.marked {
        return
    }
    obj.marked = true
    for _, child := range obj.children {
        vm.markObject(child)
    }
}

// sweep 释放未标记的对象并重置幸存者的标记。
func (vm *VM) sweep() {
    alive := []*Object{}
    for _, obj := range vm.heap {
        if obj.marked {
            obj.marked = false // 为下一个 GC 周期重置
            alive = append(alive, obj)
        } else {
            fmt.Printf("  collected: %s\n", obj.name)
        }
    }
    vm.heap = alive
}

// GC 运行一次完整的 mark-and-sweep 回收。
func (vm *VM) GC() {
    fmt.Printf("gc: heap has %d objects\n", len(vm.heap))
    vm.mark()
    vm.sweep()
    fmt.Printf("gc: %d objects remain\n\n", len(vm.heap))
}

func main() {
    vm := &amp;VM{}

    a := vm.NewObject("A")
    b := vm.NewObject("B")
    c := vm.NewObject("C")
    _ = vm.NewObject("D") // 已分配但从未链接到任何东西

    // 构建图: A -&gt; B -&gt; C
    a.children = append(a.children, b)
    b.children = append(b.children, c)

    // 只有 A 是 root
    vm.roots = append(vm.roots, a)

    fmt.Println("=== GC #1: D is unreachable ===")
    vm.GC()

    // 创建循环: C -&gt; A, 然后移除所有 roots
    c.children = append(c.children, a)
    vm.roots = nil

    fmt.Println("=== GC #2: A-&gt;B-&gt;C-&gt;A cycle, no roots ===")
    vm.GC()
}
</code></pre>
<p>运行结果：</p>
<pre><code>=== GC #1: D is unreachable ===
gc: heap has 4 objects
  collected: D
gc: 3 objects remain

=== GC #2: A-&gt;B-&gt;C-&gt;A cycle, no roots ===
gc: heap has 3 objects
  collected: A
  collected: B
  collected: C
gc: 0 objects remain
</code></pre>
<p>第一次回收：A、B 和 C 通过 root A 可达。D 没有任何 root 路径，因此被回收。</p>
<p>第二次回收：A、B 和 C 形成了一个循环（A->B->C->A），但没有 roots。标记阶段从未访问过它们中的任何一个。所有三个都被清除了。这正是击败引用计数的场景。循环中的每个对象都有非零的引用计数，但没有一个能从 root 到达。</p>
<p><strong>追踪式 GC 不关心循环。它们只关心从 roots 开始的可达性。</strong></p>
<p>有一点需要注意：markObject 函数使用了递归，这在深层对象图上会耗尽栈空间。真实的垃圾回收器使用显式的 worklist（工作列表）而不是调用栈。</p>
<h2>现代 GC 实现</h2>
<p>上面的玩具收集器为了整个标记和清除过程停止了世界。现代 GC 已经进化到在应用程序持续运行的同时并发完成大部分工作。</p>
<h3>Go: 三色并发标记-清除 (Tri-Color Concurrent Mark-and-Sweep)</h3>
<p>Go 的垃圾回收器是非分代的、非整理的且并发的。它不按年龄区分对象，也不在内存中移动对象。其重点是保持低停顿时间。</p>
<p>收集器使用三色抽象（tri-color abstraction）进行并发标记。每个对象处于三种状态之一：</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-9.png" alt="" /></p>
<ul>
<li><strong>White (白色)</strong>: 尚未访问。标记结束时仍为白色的任何东西都是垃圾。</li>
<li><strong>Grey (灰色)</strong>: 已访问，但其子节点尚未全部扫描。遍历的前沿（frontier）。</li>
<li><strong>Black (黑色)</strong>: 已访问，所有子节点已扫描。确定存活。</li>
</ul>
<p>收集器开始时将所有对象设为白色，然后将 roots 设为灰色，并处理灰色对象直到不再剩余。所有仍为白色的内容都被清除。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-10.png" alt="" /></p>
<pre><code>开始: 所有对象为白色，roots 为灰色

步骤 1: 选取一个灰色对象，扫描其子节点
        - 将子节点标为灰色
        - 将扫描过的对象标为黑色

步骤 2: 重复直到没有灰色对象剩余

步骤 3: 所有白色对象都是垃圾

示例:

  Roots: [A]

  开始:      A(grey) --&gt; B(white) --&gt; D(white)
             A(grey) --&gt; C(white)

  扫描 A:    A(black) --&gt; B(grey) --&gt; D(white)
             A(black) --&gt; C(grey)

  扫描 B:    A(black) --&gt; B(black) --&gt; D(grey)
             A(black) --&gt; C(grey)

  扫描 C:    A(black) --&gt; B(black) --&gt; D(grey)
             A(black) --&gt; C(black)

  扫描 D:    A(black) --&gt; B(black) --&gt; D(black)
             A(black) --&gt; C(black)

  结果: 任何剩余的白色对象都是垃圾并被释放
</code></pre>
<p>难点在于应用程序在收集器遍历时持续运行并修改指针。这造成了一个需要仔细处理的正确性问题。</p>
<p>收集器认为黑色对象已完成。一旦对象变黑，收集器就不会再扫描它。它的所有子节点都已被访问并设为灰色。但是，如果应用程序在收集器仍在运行时，将一个指向白色对象的指针写入黑色对象，收集器就有麻烦了。黑色对象已经处理完了。该白色对象也无法从任何灰色对象触达。当标记阶段结束并清除运行时，该白色对象将被释放，即便有一个存活的黑色对象指向它。</p>
<p>这被称为 <strong>tricolor invariant</strong>（三色不变性）：黑色对象绝不能直接指向白色对象。如果发生了这种情况，白色对象对收集器是不可见的，会被错误释放。write barrier（写屏障）的存在专门用于在并发标记期间应用程序修改对象图时维护这一不变性。</p>
<p>Go 通过 <strong>hybrid write barrier</strong>（混合写屏障，Go 1.8 引入）解决了这个问题。要理解它为什么有效，看看它结合的两种旧屏障会有所帮助。</p>
<p><strong>Dijkstra’s 插入屏障 (1978)</strong>：每当一个指针被写入对象时，将新的被引用者设为灰色。如果一个黑色对象存储了对白色对象的引用，该白色对象会在收集器错过它之前变灰。这维护了三色不变性。</p>
<p>问题在于 goroutine 栈与堆对象不同。编译器在堆指针写入处注入写屏障，例如写入结构体字段或切片元素。栈写入是局部变量赋值，编译器对其分别处理。在每一个局部变量赋值上放屏障会使函数调用和基本操作变得极其昂贵，所以屏障不覆盖它们。这意味着在并发标记期间，goroutine 可以自由地将指向白色对象的指针写入局部变量，而没有屏障触发。收集器不知道发生了这事。</p>
<p>为了修复这一点，在并发标记结束时，Go 曾经必须停止世界并从头重新扫描每个 goroutine 的整个栈。重新扫描时发现的任何指向白色对象的指针都会变灰，防止它们被错误释放。此步骤的停顿时间随着 goroutine 数量和其栈大小而增加。拥有成千上万个 goroutine 的程序可能会看到数毫秒的 STW 停顿，仅仅是为了这次重新扫描。这是 Go 1.8 之前主要的 STW 停顿来源。</p>
<p><strong>Yuasa’s 删除屏障 (1990)</strong> 采取相反的方法：每当一个指针即将被覆盖时，在旧引用消失前将其变灰。这确保了在标记开始时可达的任何东西直到结束都保持可达，即便应用程序在标记期间丢弃了它的引用。缺点是标记期间死亡的一些对象会存活到下一个周期（floating garbage，浮动垃圾），因为屏障保守地让它们活着。</p>
<p><strong>Go 的混合屏障</strong>结合了两者。在堆写入时，它同时应用两种屏障：将旧引用变灰（Yuasa）并将新引用变灰（Dijkstra）。在栈写入时，不运行屏障，但栈上新分配的对象开始时就是黑色而不是白色。这种组合赋予了收集器足够强的不变性，使其在标记结束时永远不需要重新扫描栈。STW 停顿从几十毫秒降到了不到一毫秒。</p>
<pre><code>// 混合屏障在堆指针写入时的逻辑:
// *slot = new_ptr

shade(*slot)   // 将旧引用变灰 (Yuasa: 不要丢掉之前在那里的内容)
shade(new_ptr) // 将新引用变灰 (Dijkstra: 不要错过新到来的内容)
*slot = new_ptr
</code></pre>
<p>这就是并发垃圾回收的吞吐量成本：标记阶段的每一次堆指针写入都要运行此 shade 逻辑。单次操作开销虽小，但在高分配速率下会累积。权衡的结果是你获得了亚毫秒级的 STW 停顿，而不是几十毫秒。</p>
<p>Go 仅简短地停止世界以扫描 goroutine 栈并切换写屏障的开关。实际的标记和清除与应用程序并发进行。</p>
<p><strong>No compaction (无整理)。</strong> Go 在分配后不移动对象。相反，Go 使用 tcmalloc 风格的分配器，将内存划分为 size classes（大小类），并从每个处理器的缓存（per-processor caches）中分配。对象被分组为固定的大小类（8 字节、16 字节、32 字节，最高达 32 KB）。分配时从空闲列表中选取合适大小的槽。这减少了碎片而无需移动对象，但并不能完全消除碎片。</p>
<p><strong>No generational collection (无分代收集)。</strong> Go 团队的理由是，考虑到 Go 典型的带有 goroutine 和并发工作负载的分配模式，分代 GC 增加的复杂性（用于跟踪老到新指针的写屏障、提升逻辑、分代大小调优）带来的收益是不确定的。Go 通过使其并发标记器足够快来补偿，从而使额外的回收频率变得可以接受。</p>
<p><strong>关键里程碑：</strong></p>
<ul>
<li>Go 1.5 (2015)：引入并发 GC。在此之前，Go 使用全停顿收集器，停顿时间达 10-100ms 或更多。此版本使 Go 能够胜任延迟敏感型服务。</li>
<li>Go 1.8 (2017)：混合写屏障。降低了在并发标记期间维护三色不变性的开销。</li>
<li>Go 1.19 (2022)：GOMEMLIMIT。使 Go 程序能在容器环境的内存预算内工作。</li>
</ul>
<p><strong>GOGC 调节旋钮。</strong> Go 提供了一个主要的调优参数：GOGC。它控制在下一次 GC 触发之前堆可以增长多少。默认值是 100，意味着当堆自上次回收以来翻倍时触发 GC。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-11.png" alt="" /></p>
<pre><code>GOGC=100 (默认):
  GC 后，存活堆 = 500MB
  下次 GC 触发点: 500MB * (1 + 100/100) = 1000MB

GOGC=50 (更激进):
  GC 后，存活堆 = 500MB
  下次 GC 触发点: 500MB * (1 + 50/100) = 750MB

GOGC=200 (较保守):
  GC 后，存活堆 = 500MB
  下次 GC 触发点: 500MB * (1 + 200/100) = 1500MB
</code></pre>
<p>更低的 GOGC 意味着更频繁的回收（更低的内存占用，更高的 CPU 开销）。更高的 GOGC 意味着较少的回收（更高的内存占用，更低的 CPU 开销）。</p>
<p>Go 1.19 增加了 GOMEMLIMIT，这是一个软内存限制。在具有硬性内存预算的容器环境中，GOMEMLIMIT 告诉 GC pacer（步调算法）在内存使用接近限制时变得更加激进。</p>
<p><strong>亲自尝试：</strong></p>
<pre><code>package main

import (
    "fmt"
    "runtime"
    "time"
)

var longLived []*[1024 * 1024]byte

func main() {
    fmt.Println("Go version:", runtime.Version())

    for round := 0; round &lt; 50; round++ {
        // 短寿对象: 分配小对象，让它们死亡
        for i := 0; i &lt; 5000; i++ {
            _ = make([]byte, 1024)
        }

        // 长寿对象: 每 10 轮保留一个
        if round%10 == 0 {
            arr := new([1024 * 1024]byte)
            longLived = append(longLived, arr)
        }

        time.Sleep(50 * time.Millisecond)
    }

    var stats runtime.MemStats
    runtime.ReadMemStats(&amp;stats)
    fmt.Printf("Total GC cycles: %d\n", stats.NumGC)
    fmt.Printf("Total STW pause: %v\n", time.Duration(stats.PauseTotalNs))
    fmt.Printf("Long-lived objects: %d\n", len(longLived))
}
</code></pre>
<p>运行并开启 GC 追踪：</p>
<pre><code>GODEBUG=gctrace=1 go run gcdemo.go
</code></pre>
<p>观察输出内容：</p>
<pre><code>gc 1 @0.011s 1%: 0.044+0.56+0.13 ms clock, 0.62+0.21/0.57/0+1.8 ms cpu, 3-&gt;4-&gt;0 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 14 P
</code></pre>
<p>从左到右阅读：</p>
<ul>
<li>gc 1: GC 周期编号</li>
<li>@0.011s: 自程序启动的时间</li>
<li>1%: 到目前为止 GC 消耗的 CPU 百分比</li>
<li>
<p>0.044+0.56+0.13 ms clock: GC 周期的三个阶段：STW 标记开始 (0.044ms) + 并发标记和扫描 (0.56ms) + STW 标记结束 (0.13ms)。STW 停顿是 clock 字段中的第一个和第三个数字。在此例中，应用程序被冻结的总墙钟时间是 0.044 + 0.13 = 0.174ms。中间的 0.56ms 是并发的：你的应用程序一直在运行。在 Go 中，STW 停顿通常在 1ms 以下，往往远低于 0.1ms。</p>
</li>
<li>
<p>0.62+0.21/0.57/0+1.8 ms cpu: CPU 时间细目。格式为：STW-开始 + 辅助/背景/空闲 + STW-结束。每个数字代表：</p>
<ul>
<li>0.62ms — STW 标记开始时所有核心的 CPU 总时间。高于墙钟时间 (0.044ms)，因为 Go 会在多个核心上并行化初始栈扫描。</li>
<li>0.21ms — 应用程序 goroutine 执行 mutator assists（赋值器辅助）所花费的 CPU 时间。当某个 goroutine 分配速度超过 GC 处理速度时，它会被“征税”，必须在允许其分配之前自己做一些标记工作。</li>
<li>0.57ms — 专用背景 GC 工作 goroutine 执行并发标记所使用的 CPU 时间。</li>
<li>0 — 空闲 GC 工作者的 CPU 时间（仅在调度器没有其他任务运行时才领取 GC 任务的 goroutine）。此处为零意味着专用工作者处理了所有事情。</li>
<li>1.8ms — STW 标记结束时所有核心的 CPU 总时间。高于墙钟 (0.13ms)，因为多个核心并行工作以排空剩余任务并禁用写屏障。</li>
</ul>
</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-12.png" alt="" /></p>
<p>当多个核心并行工作时，CPU 时间可以超过墙钟时间。并发阶段的 CPU 时间可能少于墙钟时间，因为 GC 与你的应用程序共享核心。</p>
<ul>
<li>3->4->0 MB: GC 开始时的堆大小、GC 触发点的堆大小、GC 完成后的存活堆大小</li>
<li>4 MB goal: 下次 GC 触发前的目标堆大小（基于 GOGC 和当前存活堆）</li>
<li>0 MB stacks: goroutine 栈使用的内存</li>
<li>0 MB globals: 标记期间扫描的全局变量使用的内存</li>
<li>14 P: 逻辑处理器数量 (GOMAXPROCS)</li>
</ul>
<h3>Java: G1GC (Garbage First Collector)</h3>
<p>G1GC 自 JDK 9 以来一直是 Java 的默认垃圾回收器。它是一个分代的、基于区域（region）的收集器。它进行追踪、标记和整理，但它是增量式进行的，而不是一次性完成。</p>
<p><strong>Region layout (区域布局)。</strong> G1 将堆划分为大小相等的区域，通常每个区域为 1MB 到 32MB，取决于堆的大小。每个区域在任何时候扮演四种角色之一：Eden（伊甸园）、Survivor（幸存者）、Old（老年代）或 Humongous（巨型对象，用于超过半个区域大小的对象）。区域的角色可以在不同回收周期之间改变。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-13.png" alt="" /></p>
<p><strong>Young collection (次要 GC)。</strong> Eden 区域填满。G1 停止世界，使用并行多线程标记器标记 Eden 和 Survivor 区域中的存活对象，将幸存者拷贝到新的 Survivor 区域或提升到 Old 区域，并完全丢弃旧的 Eden 区域。这是一个并行的 STW 停顿，但很短，因为年轻代区域较小且年轻对象大多已死。</p>
<p><strong>Mixed collection (混合回收)。</strong> G1 周期性地运行并发标记周期，以找出哪些老年代区域包含的垃圾最多。然后运行混合回收：同时疏散（evacuating）年轻代区域和最具“盈利价值”的老年代区域。这就是“Garbage First”名称的由来。G1 总是优先选取垃圾密度最高的老年代区域，从而在单位停顿时间内实现最大的回收量。</p>
<p><strong>SATB (Snapshot-At-The-Beginning，起始快照)。</strong> 在并发标记期间，应用程序持续运行并修改对象图。G1 使用 SATB 维护正确性。在标记开始时，G1 对哪些对象存活进行逻辑快照。该快照中存活的对象在此周期被视为存活，即使应用程序在标记期间丢弃了它们。写屏障将修改字段的旧值记录到 SATB 队列中。这种做法是保守的（一些垃圾会存活到下个周期），但是正确的。</p>
<pre><code>并发标记正在运行。应用程序执行：
  obj.field = null   (原本指向 X)

没有 SATB: X 可能没有其他引用，未被标记，在使用中被释放。
有 SATB:    写屏障记录“此处曾有 X”，将 X 标为灰色。安全。
</code></pre>
<p><strong>Pause target (停顿目标)。</strong> 你可以通过 -XX:MaxGCPauseMillis 配置 G1 的目标最大停顿时间。默认值是 200ms。G1 通过调整区域数量、回收集合大小和时机，尝试将停顿保持在目标范围内。它并不总是能成功，特别是在 Full GC 期间，但它是主要的调优旋钮。</p>
<p><strong>亲自尝试：</strong></p>
<pre><code>import java.util.ArrayList;
import java.util.List;

public class GCDemo {
  static List&lt;byte[]&gt; longLived = new ArrayList&lt;&gt;();

  public static void main(String[] args) throws InterruptedException {
    System.out.println("Starting GC demo...");

    for (int round = 0; round &lt; 50; round++) {
      // 短寿对象：创建并立即丢弃
      for (int i = 0; i &lt; 1000; i++) {
        byte[] tmp = new byte[10 * 1024]; // 每个 10KB
      }

      // 长寿对象：保留一些对象以构建老年代
      if (round % 5 == 0) {
        longLived.add(new byte[1024 * 1024]); // 1MB
      }

      Thread.sleep(50);
    }

    System.out.println("Done. Long-lived objects: " + longLived.size());
  }
}
</code></pre>
<p>使用 G1GC 日志运行：</p>
<pre><code># 编译
javac GCDemo.java

# 使用 G1GC (Java 9+ 默认) 并开启 GC 日志
java -Xmx256m \
     -XX:+UseG1GC \
     "-Xlog:gc*:file=gc_g1.log:time,uptime,level,tags" \
     GCDemo

# 或者，使用简洁的一行输出
java -Xmx256m -Xlog:gc GCDemo
</code></pre>
<p>观察日志：</p>
<pre><code>[0.005s][info][gc] Using G1
[0.135s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 26M-&gt;3M(256M) 0.644ms
[0.812s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 132M-&gt;7M(256M) 0.707ms
[1.710s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 165M-&gt;13M(256M) 1.019ms
[2.528s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 171M-&gt;19M(256M) 0.964ms
</code></pre>
<p>阅读日志：</p>
<ul>
<li>Using G1: 确认 G1GC 是活跃收集器</li>
<li>Pause Young (Normal): 回收 Eden 和 Survivor 区域的次要 GC</li>
<li>G1 Evacuation Pause: G1 正在将存活对象从回收区域拷贝（疏散）到新区域</li>
<li>26M->3M(256M) 0.644ms: 堆之前是 26MB，之后是 3MB，总堆容量 256MB，停顿耗时 0.644ms</li>
<li>在 2.5 秒的运行时中进行了四个 GC 周期，每个周期在 1.1ms 内完成。大多数分配的对象都是短寿的，并在年轻代被回收。</li>
</ul>
<h3>Java: ZGC (Z Garbage Collector)</h3>
<p>ZGC 自 Java 11 起可用，并在 Java 15 中达到生产就绪状态。扩展了分代收集的 Generational ZGC 在 Java 21 中引入。ZGC 的目标是无论堆大小如何（包括数百 GB 的堆），停顿时间均保持在亚毫秒级。</p>
<p>G1 在年轻代回收时停顿较短，但随着堆的增长，在并发标记设置和混合回收期间会有更长的停顿。ZGC 的方法不同：它几乎将所有工作（标记、重定位、引用处理）并发进行，将 STW 工作降至最低。</p>
<p><strong>Colored pointers (有色指针)。</strong> ZGC 直接在指针位中编码 GC 元数据。在 64 位平台上，指针宽度为 64 位，但你实际上并不需要所有 64 位来寻址内存。2^42 就能给你 4TB 的可寻址空间，这超出了大多数应用程序的使用范围。这使得每个指针中留有超过 20 位空闲。ZGC 重新利用其中一些空闲位，直接在指针内部存储 GC 状态。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-14.png" alt="" /></p>
<p>每个元数据位都有特定用途：</p>
<ul>
<li>
<p><strong>M0 和 M1 (标记位)：</strong> 用于跟踪对象是否已被标记为存活。ZGC 在每个 GC 周期中交替使用 M0 和 M1。在周期 1，收集器对每个可达对象设置 M0。在周期 2，它改用 M1。这样收集器就能区分“本周期标记”和“上个周期标记”，而无需在周期之间清除所有标记位。</p>
</li>
<li>
<p><strong>Remap (R，重映射)：</strong> 此位跟踪在对象重定位（relocated）后指针是否已更新。在并发重定位期间，ZGC 将对象移动到新地址，但并不立即更新堆中的每一个指针。相反，它保留旧指针，并使 remap 位处于未设置状态。当应用程序加载这些过时指针之一时，load barrier（读屏障/加载屏障）会注意到 remap 位未设置，并对其进行修正。</p>
</li>
<li>
<p><strong>Finalizable (F)：</strong> 表示该对象具有一个需要在释放前运行的 finalizer。</p>
</li>
</ul>
<p>巧妙之处在于元数据随指针移动。GC 不需要一个单独的侧表来查找对象的 GC 状态。每个指针都已经携带了它。</p>
<p><strong>Load barriers (加载屏障)。</strong> 每次应用程序从堆加载引用时，ZGC 都会插入一个加载屏障。屏障检查指针的颜色位，如果它们不处于预期状态，则采取行动。</p>
<p>以下是实际操作中的情况。假设收集器在并发重定位阶段将一个对象从地址 0&#215;1000 移动到了 0&#215;2000。应用程序仍然持有一个地址为 0&#215;1000 且 remap 位未设置的指针。</p>
<pre><code>应用程序代码:
  Object x = obj.field;

实际执行的内容:
  raw_ptr = load obj.field           // raw_ptr = 0x1000, remap bit = 0
  if (raw_ptr.color != expected) {   // remap bit 为 0, expected 为 1 → 进入 slow path
      new_addr = forwarding_table[0x1000]  // 查找: 对象已移动到 0x2000
      raw_ptr = set_address(raw_ptr, 0x2000)
      raw_ptr = set_remap_bit(raw_ptr)
      obj.field = raw_ptr            // 就地修正指针，以便下次使用
  }
  x = raw_ptr                       // x 现在指向 0x2000
</code></pre>
<p>下次任何线程加载 obj.field 时，remap 位已经设置好了。屏障检查通过 fast path，没有额外工作。过时指针在第一次访问时被惰性修正。</p>
<p>这是关键机制。与其像 G1 在疏散期间那样让 GC 停止世界以一次性更新所有指向重定位对象的指针，ZGC 让应用程序在遇到指针时逐个修正。代价是每次指针加载都要支付屏障检查的开销，即便没有任何东西被重定位。在实践中，fast path（检查几位）执行代价足够小，与避免 STW 重定位停顿带来的收益相比，开销很小。</p>
<p><strong>Concurrent relocation (并发重定位)。</strong> G1 停止世界以将存活对象从回收区域中疏散。ZGC 在应用程序运行的同时重定位对象。它能做到这一点是因为加载屏障处理了指针修正。在启动和结束每个阶段（标记开始、标记结束、重定位开始）时有简短的 STW 停顿，但这些通常远低于 1ms。拷贝对象和修正指针的实际工作是并发发生的。</p>
<p><strong>Generational ZGC (Java 21+)。</strong> 最初的 ZGC 不按年龄划分堆。分代 ZGC 增加了年轻代和老年代，同时保留了亚毫秒级停顿的保证。它更频繁地回收年轻区域（垃圾最多的地方），较少回收老年代区域。加载屏障和有色指针机制被扩展以处理分代写屏障。</p>
<p><strong>何时使用 ZGC vs G1：</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-15.png" alt="" /></p>
<p><strong>亲自尝试：</strong></p>
<pre><code># 使用 ZGC 运行
java -Xmx256m \
     -XX:+UseZGC \
     "-Xlog:gc*:file=gc_zgc.log:time,uptime,level,tags" \
     GCDemo

# 使用分代 ZGC (Java 21+)
java -Xmx256m \
     -XX:+UseZGC -XX:+ZGenerational \
     -Xlog:gc \
     GCDemo
</code></pre>
<p>观察日志：</p>
<pre><code>[0.318s] GC(0) Garbage Collection (Warmup) 28M(11%)-&gt;12M(5%)
[0.321s] GC(0) Pause Mark Start 0.023ms
[0.489s] GC(0) Concurrent Mark 168.123ms
[0.491s] GC(0) Pause Mark End 0.019ms
[0.492s] GC(0) Concurrent Select Relocation Set 1.234ms
[0.502s] GC(0) Concurrent Relocate 10.456ms
</code></pre>
<p>STW 停顿是标记为“Pause”的行。其他所有内容都是并发的。将此处的停顿持续时间与 G1 的输出进行对比。</p>
<h3>Python: 引用计数加循环 GC</h3>
<p>CPython（Python 的参考实现）是“追踪式收集器占主导”模式的主要例外。它使用引用计数作为主要机制，并在之上增加了一层用于追踪循环引用的检测器。</p>
<p><strong>CPython 中的引用计数。</strong></p>
<p>每个 Python 对象都有一个 ob_refcnt 字段。Python 的 C API 在 Py_INCREF 时增加，在 Py_DECREF 时减少。当计数归零时，对象在 _Py_Dealloc 中被立即释放。这赋予了 Python 确定性的销毁：<strong>del</strong> 方法和上下文管理器的 <strong>exit</strong> 调用在最后一个引用掉落的那一刻发生。</p>
<pre><code>import sys

x = []
print(sys.getrefcount(x))  # 2: 1个来自x，1个来自getrefcount参数本身的临时引用

y = x
print(sys.getrefcount(x))  # 3: 1个x, 1个y, 1个getrefcount参数

del y
print(sys.getrefcount(x))  # 2: 回到1个x, 1个getrefcount参数
</code></pre>
<p><strong>循环引用问题。</strong> 仅靠引用计数无法回收循环垃圾。</p>
<pre><code>import gc

# 创建循环引用
class Node:
    def __init__(self, name):
        self.name = name
        self.ref = None

a = Node("A")
b = Node("B")
a.ref = b
b.ref = a   # cycle: A -&gt; B -&gt; A

# a 和 b 的计数都 &gt;= 1（由于相互引用）。
# 仅靠引用计数，两者都不会被释放。

del a
del b
# a 和 b 依然存活！Refcount: A 为 1 (来自 b.ref), B 为 1 (来自 a.ref)

# 显式触发循环检测器
collected = gc.collect()
print(f"Collected {collected} objects")  # 收集了 4 个对象 (2个node + 2个dict)
</code></pre>
<p>引用计数处理了常见情况，但它无法收集循环引用。CPython 的答案是运行在引用计数系统之上的独立循环检测器。其实现在 Modules/gcmodule.c 中。</p>
<p>循环检测器是一个追踪式收集器，但它并不追踪整个堆。它仅跟踪能够参与循环引用的对象：如列表、字典、集合及用户自定义类实例等容器对象。字符串和整数无法持有对其他对象的引用，因此无需跟踪它们。</p>
<p>与 Java 的收集器一样，循环检测器使用分代方法。共有三代，编号为 0、1 和 2。思路与我们之前讨论的分代假说相同：大多数对象死得早，所以经常检查年轻对象，少打扰老对象。默认阈值硬编码在 CPython 的 <a href="https://github.com/python/cpython/blob/v3.9.6/Modules/gcmodule.c#L137">Modules/gcmodule.c</a> 中：</p>
<pre><code>struct gc_generation generations[NUM_GENERATIONS] = {
    /* PyGC_Head,                                    threshold,    count */
    { {(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)},   700,        0},
    { {(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)},   10,         0},
    { {(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)},   10,         0},
};
</code></pre>
<p>你可以验证你的运行时实际使用的是什么：</p>
<pre><code>python3 -c "import gc; print(gc.get_threshold())"
# (700, 10, 10)
</code></pre>
<p>请注意，某些框架和发行版会在启动时通过 gc.set_threshold() 覆盖这些默认值，因此你的环境可能显示不同的值。</p>
<p>第 0 代持有新分配的容器对象。当自上次回收以来的新分配数量超过阈值（默认 700）时，回收第 0 代。幸存的对象被提升到第 1 代。在第 0 代被回收 10 次后，第 1 代被回收一次。幸存者移至第 2 代。在第 1 代被回收 10 次后，第 2 代被回收一次。</p>
<p>效果是第 0 代大约每 700 次分配回收一次，第 1 代大约每 7,000 次，第 2 代大约每 70,000 次。进入第 2 代的长寿对象几乎永远不会被打扰。检测器将其大部分时间花在最年轻的对象上，这些对象最有可能最近变成了垃圾。</p>
<p>你可以看到这些计数：</p>
<pre><code>import gc

# 当前各代阈值
print(gc.get_threshold())  # (700, 10, 10)

# 当前分配计数: (gen0分配, 自上次gen1回收以来的gen0回收数, 自上次gen2回收以来的gen1回收数)
print(gc.get_count())  # 例如 (342, 8, 2)

# 强制进行全量回收
gc.collect()

# 完全禁用循环检测器 (如果你确定代码中没有循环引用)
gc.disable()
</code></pre>
<p>当检测器在某一代码代上运行时，它需要找出哪些对象仅被循环引用保持存活。通过一个例子更容易理解算法。</p>
<p>假设检测器正在查看三个被跟踪的对象：X、Y 和 Z。X 指向 Y 和 Z。Y 指回 X。还有一个局部变量持有对 X 的引用。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-16.png" alt="" /></p>
<p>步骤 1：拷贝引用计数。X=2, Y=1, Z=1。</p>
<p>步骤 2：减去内部引用。Y 指向 X，所以从 X 的副本中减 1 (X 从 2 变为 1)。X 指向 Y，所以从 Y 的副本中减 1 (Y 从 1 变为 0)。X 指向 Z，所以从 Z 的副本中减 1 (Z 从 1 变为 0)。</p>
<p>步骤 3：检查剩余部分。X 的调整后计数为 1。被跟踪集合之外的某些东西（局部变量）仍然指向它。X 存活。Y 和 Z 虽然调整后计数为 0，但它们可以从 X 到达，因此它们也幸存下来。</p>
<p>现在想象局部变量消失了。X 的引用计数掉到 1 (只有 Y 指向它)。运行相同算法：拷贝 X=1, Y=1, Z=1。减去内部引用：X 变为 0, Y 变为 0, Z 变为 0。每个调整后的计数都是零。被跟踪集合之外没有任何东西指向它们。它们仅因彼此而存在。三者都是垃圾。</p>
<p>这就是核心思想。算法寻找那些存在的唯一理由是同一集合中其他对象的目标。</p>
<p>有一个边缘案例困扰了多年：finalizers（终结器）。</p>
<p>终结器是运行时在对象被销毁前调用的方法，给予其清理外部资源（如文件句柄或网络连接）的机会。在 Python 中，这就是 <strong>del</strong> 方法。</p>
<p>假设 A 和 B 处于循环中，且两者都有 <strong>del</strong> 方法。检测器知道它们是垃圾，但要释放它们，它需要打破循环。问题是：哪个 <strong>del</strong> 先运行？如果你先运行 A 的终结器，而它尝试使用 B，但 B 已经正在被销毁，你就会崩溃。如果你先运行 B 的，而它使用 A，同样的问题。没有安全的顺序。</p>
<p>在 Python 3.4 之前，CPython 直接放弃处理这些对象。它将它们放入名为 gc.garbage 的列表中，且永远不释放它们。如果你的代码创建了带有 <strong>del</strong> 的循环引用，你就会有一个静默的内存泄漏。PEP 442 通过在打破任何引用之前调用终结器修复了这个问题。当 A 和 B 的 <strong>del</strong> 运行时，两者都保持完整。只有在所有终结器执行完毕后，检测器才会打破循环并释放对象。</p>
<p>关于 CPython 的内存模型还有一件事值得了解。每当 Python 执行类似 x = some_object 的操作时，它会增加 some_object 的引用计数（C 语言中的 Py_INCREF）。每当变量超出作用域时，它减少计数 (Py_DECREF)。在 C 中这些是普通的整数操作：refcount += 1, refcount -= 1。没有锁，没有原子指令。</p>
<p>在多线程程序中，这是一个问题。两个线程可能同时增加同一个对象的引用计数。如果没有同步，一个增加操作会丢失（经典的竞态条件），之后该对象可能会在有人仍在使用时被释放。</p>
<p>GIL (全局解释器锁) 防止了这种情况。一次只有一个线程执行 Python 字节码，因此两个线程永远不会同时修改同一个引用计数。GIL 免费使所有引用计数操作变得安全，而无需任何原子指令。</p>
<p>这也是移除 GIL 如此困难的原因。如果拿掉它，整个代码库中的每一个 Py_INCREF 和 Py_DECREF 都需要变成原子操作。原子操作比普通整数增量要昂贵得多。Python 3.13 开始附带实验性的 free-threaded 模式，它使用 biased reference counting（偏向引用计数）来降低这种成本：创建对象的线程可以对引用计数进行廉价的非原子更新，只有访问该对象的其他线程才支付原子操作的代价。</p>
<h2>映射回 Wilson：全景图</h2>
<p>每一种现代垃圾回收器都可以映射回 Wilson 在 1992 年描述的两个家族。它们之间的区别在于关于如何最小化停顿、处理并发以及高效管理内存的工程决策。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/garbage-collectors-deep-dive-17.png" alt="" /></p>
<p>从这一对比中可以观察到几点：</p>
<p><strong>Wilson 的追踪式家族在服务器运行时占据主导地位。</strong> 引用计数用于 Swift、Python 和 Rust 的 Arc，但对于具有高分配速率的托管运行时，追踪式收集器是标准做法。循环引用问题无论如何都需要补充追踪步骤，这增加了复杂性，且无法消除每次修改时的引用计数开销。</p>
<p><strong>分代收集除 Go 以外随处可见。</strong> Java 重度利用了分代假说。Python 的循环检测器使用了三代。Go 最初选择不使用分代收集，因为跨代指针写屏障的开销对 Go 的典型工作负载来说不划算。这种情况可能正在改变：最近的 Go 版本中已经开发了实验性的分代支持。</p>
<p><strong>Compaction (整理) vs No compaction 是一个真正的设计分歧点。</strong> Java 收集器进行整理，这允许 bump-pointer 分配（非常快）并消除碎片。Go 不整理，这意味着它永远不需要更新指向已移动对象的指针（更简单的写屏障，无需读屏障以保证正确性）。Go 通过大小类分配器（size-class allocator）来补偿。这是经典的 Wilson 权衡：拷贝和整理收集器以内存开销和指针更新成本换取分配速度和碎片消除。</p>
<p><strong>ZGC 的有色指针是 Wilson 指针标记 (pointer-tagging) 思想的现代实现。</strong> Wilson 提到过在指针中使用位来存储 GC 元数据。ZGC 将此进一步发展，将标记状态、重映射状态和终结状态直接嵌入 64 位指针。在每次指针加载时检查这些位的加载屏障是 ZGC 为亚毫秒级停顿支付的代价。</p>
<p><strong>基本问题从未改变。</strong> 从 roots 开始追踪，标记存活内容，回收其余部分。自 1960 年以来的所有发展都是对 McCarthy 原始洞察的工程改进。</p>
<h2>参考资料</h2>
<ul>
<li><a href="https://dl.acm.org/doi/10.1145/367177.367199">McCarthy, J. (1960). Recursive functions of symbolic expressions and their computation by machine, Part I</a></li>
<li><a href="https://www.cs.rice.edu/~javaplt/311/Readings/wilson92uniprocessor.pdf">Wilson, P. R. (1992). Uniprocessor Garbage Collection Techniques. IWMM ‘92</a></li>
<li><a href="https://tip.golang.org/doc/gc-guide">A Guide to the Go Garbage Collector</a></li>
<li><a href="https://go.dev/blog/ismmkeynote">Getting to Go: The Journey of Go’s Garbage Collector</a></li>
<li><a href="https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md">Proposal: Eliminate STW stack re-scanning &#8211; Austin Clements (2016)</a></li>
<li><a href="https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html">Java Garbage Collection: The G1 Garbage Collector</a></li>
<li><a href="https://openjdk.org/jeps/333">ZGC: The Z Garbage Collector &#8211; JEP 333</a></li>
<li><a href="https://openjdk.org/jeps/439">Generational ZGC &#8211; JEP 439</a></li>
<li><a href="https://peps.python.org/pep-0442/">PEP 442: Safe object finalization</a></li>
</ul>
<hr />
<p><strong>你的“停顿”时刻</strong></p>
<p>GC 的艺术在于平衡。在你的开发生涯中，是否遇到过因为 GC 停顿导致的生产事故？你是倾向于 Go 的极致低延迟，还是 Java G1GC 的高吞吐？<br />
欢迎在评论区分享你的调优经历或吐槽！</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等，掌握 AI 时代新技能。 </li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg" alt="img{512x368}" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/04/07/garbage-collectors-deep-dive/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>拒绝 Rust 的复杂，跨越 Go 的极简：Zig 会是系统级编程的最终答案吗？</title>
		<link>https://tonybai.com/2026/02/26/rust-complexity-go-minimalism-vs-zig-ultimate-answer/</link>
		<comments>https://tonybai.com/2026/02/26/rust-complexity-go-minimalism-vs-zig-ultimate-answer/#comments</comments>
		<pubDate>Thu, 26 Feb 2026 00:31:01 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Allocator]]></category>
		<category><![CDATA[AsyncIO]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[CompiletimeExecution]]></category>
		<category><![CDATA[comptime]]></category>
		<category><![CDATA[ComptimeGenerics]]></category>
		<category><![CDATA[Comptime泛型]]></category>
		<category><![CDATA[ConcurrencyModel]]></category>
		<category><![CDATA[DeveloperExperience]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguage]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[interoperability]]></category>
		<category><![CDATA[LearningCurve]]></category>
		<category><![CDATA[ManualMemoryManagement]]></category>
		<category><![CDATA[MemoryLeak]]></category>
		<category><![CDATA[MemoryManagement]]></category>
		<category><![CDATA[odin]]></category>
		<category><![CDATA[porting]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[Segfault]]></category>
		<category><![CDATA[SystemsControl]]></category>
		<category><![CDATA[SystemsProgramming]]></category>
		<category><![CDATA[toolchain]]></category>
		<category><![CDATA[Zig]]></category>
		<category><![CDATA[ZLS]]></category>
		<category><![CDATA[互操作性]]></category>
		<category><![CDATA[代码移植]]></category>
		<category><![CDATA[借用检查器]]></category>
		<category><![CDATA[内存泄漏]]></category>
		<category><![CDATA[内存管理]]></category>
		<category><![CDATA[分配器]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[学习曲线]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[并发模型]]></category>
		<category><![CDATA[开发者体验]]></category>
		<category><![CDATA[异步IO]]></category>
		<category><![CDATA[手动内存管理]]></category>
		<category><![CDATA[段错误]]></category>
		<category><![CDATA[系统控制力]]></category>
		<category><![CDATA[系统级编程]]></category>
		<category><![CDATA[编译期计算]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5950</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/02/26/rust-complexity-go-minimalism-vs-zig-ultimate-answer 大家好，我是Tony Bai。 在当前的后端与系统级编程领域，开发者似乎总是面临着一种“非此即彼”的艰难抉择：要么选择 Go 语言，拥抱其极致的极简主义、高效的并发模型和无处不在的垃圾回收（GC），但往往需要在底层内存控制上做出妥协；要么投向 Rust 的怀抱，追求绝对的内存安全和零成本抽象，却不得不常年与“借用检查器（Borrow Checker）”搏斗，忍受陡峭得令人绝望的学习曲线。 然而，在这两大巨头的光环之外，一门名为 Zig 的语言正在悄然崛起。它没有隐式的控制流，没有隐藏的内存分配，甚至没有预处理器和宏，却提供了无与伦比的 C 语言互操作性和强大的编译期计算能力。近日，在Reddit技术社区 r/Zig 上，一位资深 Go 开发者分享了他将一个核心项目从 Go 迁移到即将发布的 Zig 0.16 版本的全过程。他的经历既是一次跨越语言壁垒的技术冒险，更为我们揭示了一个深刻的问题：在拒绝了 Rust 的复杂、看透了 Go 的局限之后，Zig 会是我们苦苦寻找的那个系统级编程的最终答案吗？ 在本文中，我们将跟随这位开发者的脚步，深度剖析这次从 Go 到 Zig 的“系统级”降维打击，探讨内存管理、并发演进以及新兴语言的生态阵痛。 语言选择的罗曼史：为什么是 Zig？ 对于任何一位有着丰富经验的开发者来说，选择一门新的编程语言绝非心血来潮。在这位开发者长长的技术履历中，我们看到了一条清晰的“硬核化”演进路线：Python -> Rust -> Go -> Odin -> Zig。 这条路线背后，折射出的是当代开发者对“开发效率”与“系统控制力”双重渴望的矛盾与挣扎： 逃离 Python 的脆弱：动态类型的 Python 常常伴随着难以预料的运行时错误，加上令人抓狂的虚拟环境（venv/pip）管理，促使他开始向底层探索。 被 Rust 劝退的恐惧：开发者坦言，“Rust [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/rust-complexity-go-minimalism-vs-zig-ultimate-answer-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/02/26/rust-complexity-go-minimalism-vs-zig-ultimate-answer">本文永久链接</a> &#8211; https://tonybai.com/2026/02/26/rust-complexity-go-minimalism-vs-zig-ultimate-answer</p>
<p>大家好，我是Tony Bai。</p>
<p>在当前的后端与系统级编程领域，开发者似乎总是面临着一种“非此即彼”的艰难抉择：要么选择 Go 语言，拥抱其极致的<a href="https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/">极简主义</a>、高效的<a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzIyNzM0MDk0Mg==&amp;action=getalbum&amp;album_id=4105816518230016005#wechat_redirect">并发模型</a>和无处不在的<a href="https://tonybai.com/2025/10/31/deep-into-go-green-tea-gc">垃圾回收（GC）</a>，但往往需要在底层内存控制上做出妥协；要么投向 Rust 的怀抱，追求绝对的内存安全和零成本抽象，却不得不常年与“借用检查器（Borrow Checker）”搏斗，忍受陡峭得令人绝望的学习曲线。</p>
<p>然而，在这两大巨头的光环之外，一门名为 Zig 的语言正在悄然崛起。它没有隐式的控制流，没有隐藏的内存分配，甚至没有预处理器和宏，却提供了无与伦比的 C 语言互操作性和强大的编译期计算能力。近日，在Reddit技术社区 r/Zig 上，一位资深 Go 开发者<a href="https://www.reddit.com/r/Zig/comments/1rd0fsz/thoughts_after_porting_a_project_from_go_to_zig/">分享了他将一个核心项目从 Go 迁移到即将发布的 Zig 0.16 版本的全过程</a>。他的经历既是一次跨越语言壁垒的技术冒险，更为我们揭示了一个深刻的问题：在拒绝了 Rust 的复杂、看透了 Go 的局限之后，Zig 会是我们苦苦寻找的那个系统级编程的最终答案吗？</p>
<p>在本文中，我们将跟随这位开发者的脚步，深度剖析这次从 Go 到 Zig 的“系统级”降维打击，探讨内存管理、并发演进以及新兴语言的生态阵痛。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/system-programming-in-go-pr.png" alt="" /></p>
<h2>语言选择的罗曼史：为什么是 Zig？</h2>
<p>对于任何一位有着丰富经验的开发者来说，选择一门新的编程语言绝非心血来潮。在这位开发者长长的技术履历中，我们看到了一条清晰的“硬核化”演进路线：<strong>Python -> Rust -> Go -> Odin -> Zig</strong>。</p>
<p>这条路线背后，折射出的是当代开发者对“开发效率”与“系统控制力”双重渴望的矛盾与挣扎：</p>
<ol>
<li>逃离 Python 的脆弱：动态类型的 Python 常常伴随着难以预料的运行时错误，加上令人抓狂的虚拟环境（venv/pip）管理，促使他开始向底层探索。</li>
<li>被 Rust 劝退的恐惧：开发者坦言，“Rust 是我尝试过的最复杂的语言”。尽管他勉强写出了 Rust 代码，但他自知那是“糟糕的 Rust”。面对陡峭的学习曲线和心智负担，他的结论异常真实：“Rust 可能很容易学，但我不想再哭一次了（don&#8217;t want to cry again）”。</li>
<li>Go 语言的温柔乡：在众多高级语言中，Go 成了他最钟爱的归宿。他将 Go 评价为“最低级别的高级语言（lowest of the high level languages）”。对于 Web 服务和后端开发，Go 的极简语法、成熟的生态和开箱即用的特性，使其成为默认的终极选择。他甚至感慨：“我真希望我一开始就是用 Go 学编程的。”</li>
<li>Odin 的中道崩殂：在追求比 Go 更底层的控制力时，他曾短暂尝试过 Odin（一门常与 Zig 齐名的面向数据设计的系统级语言）。Odin 在语法上介于 Go 和 Zig 之间，看似完美的平衡却被糟糕的工具链打破。频繁崩溃的 LSP（Language Server Protocol）、不完善的文档以及诡异的编译器指令，最终将他推开了。</li>
<li>情定 Zig：最终，Zig 成为了他的驻足之地。Zig 既提供了不输于 C 语言的底层掌控力，又通过创新的语法和工具链，避开了 Rust 复杂的生命周期管理。</li>
</ol>
<p>从中我们也可以看出当下系统级编程领域的一道缩影：开发者们渴望获得底层控制权，但不想为此付出丧失开发体验的代价。</p>
<h2>移植实战：从 1 周到 2 个月的“阵痛与重塑”</h2>
<p>纸上得来终觉浅。这位开发者决定动真格：将一个由 Go 编写的基于内存互斥锁（Mutex）的键值对存储（Key/Value Store）及配套的通道预写日志（channel WAL）项目，完整地移植到 Zig 0.16 中（包括使用 LZ4 压缩和导出 Parquet 格式的功能）。</p>
<p>原计划只需要 1 周的迁移工作，最终演变成了一场长达 1.5 到 2 个月的持久战。为什么会这么耗时？</p>
<h3>代码规模与表达力：意外的对等</h3>
<p>令人惊讶的是，尽管 Zig 需要手动管理内存，但迁移后的代码量（约 750 行）与原先的 Go 代码几乎持平。开发者指出，虽然 Zig 的代码在视觉上“更宽”（得益于其极其丰富的表达能力），但行数并没有膨胀。这归功于 Zig 中 Unions（联合体）、Enums（枚举）、Errors（错误处理）和 Structs（结构体）的完美组合。</p>
<h3>拥抱 Comptime：降维打击的“超能力”</h3>
<p>在 Go 语言中，泛型（Generics）直到 1.18 版本才姗姗来迟，且其能力受到诸多限制。而在 Zig 中，开发者体验到了真正的震撼——Comptime（编译期执行）。</p>
<p>他将处理结构体类型的泛型能力称为“疯狂的超能力”。在编译期间执行任意 Zig 代码的能力，使得开发者能够以极低的运行时开销，实现高度动态和灵活的类型处理。这种对类型的编译期反射和操作，是 Go 语言开发者难以想象的体验。</p>
<h3>代码组织方式的颠覆</h3>
<p>Go 语言习惯于将不同的接口、结构体分散在多个文件中，利用包（Package）级别来进行组织。但在 Zig 中，开发者发现了一种全新的心智模型：将所有想法放入一个文件中，并通过结构体（Struct）进行分组。当代码在编辑器中折叠后，这种高度内聚的设计显得极其清晰且易于导航。</p>
<h2>内存管理的洗礼：脱离 GC 后的生存法则</h2>
<p>从自带垃圾回收（GC）的 Go 语言跨越到需要显式传递分配器（Allocator）的 Zig，是此次移植中最痛苦，也是收获最大的部分。</p>
<p>没有了 Go 运行时的庇护，开发者必须直面内存的生与死。在经历了无数次内存泄漏后，他总结出了针对 Go 开发者转战 Zig 的七条黄金生存法则：</p>
<ol>
<li>
<p>返回内存的函数，必须接收 Allocator：在 Go 中，函数可以随意返回指针或切片，GC 会负责善后。在 Zig 中，任何产生新内存分配的函数，其签名中必须显式包含一个 Allocator 参数。</p>
</li>
<li>
<p>严格区分不可变与可变：[]const u8 表示你绝不会修改这块内存（只读切片），而 []u8 则意味着你承诺你会去修改这块内存。这种显式的意图声明，在 Go 的 []byte 中是缺失的，Go 开发者往往需要通过文档或约定来判断切片是否会被修改。而在 Zig 中，类型系统替你守住了这道防线。</p>
</li>
<li>
<p>所有权与复制 (allocator.dupe)：在 Go 中，传递指针或切片非常廉价，垃圾回收器（GC）会处理共享引用的生命周期。但在 Zig 中，如果你需要保留传入的数据并在函数返回后继续使用，你必须使用 allocator.dupe 进行深拷贝。</p>
</li>
<li>
<p>内存分配失败是常态：任何分配都可能失败。在 Zig 中，这意味着你必须处理 Error Union。而在 Go 中，make 或 new 失败通常意味着程序崩溃（panic），大多数业务代码从不处理 OOM（内存溢出）。</p>
</li>
<li>
<p>测试即救赎 (std.testing.allocator)：“不写测试，就等着受苦”。Zig 的标准库测试运行器内置了内存泄漏检测功能。使用 std.testing.allocator 运行测试，如果你的代码有泄漏，测试会直接失败并报告。这对于习惯了“分配后即遗忘”的 Go 开发者来说，简直是当头棒喝，但也是养成良好习惯的最佳工具。</p>
</li>
<li>
<p>源码即文档：遇到疑问时，直接读标准库源码 (std)。Go 的标准库以清晰著称，但 Zig 的标准库源码同样展示了惊人的可读性。由于没有隐藏的控制流和宏，你看到的即是实际发生的。</p>
</li>
</ol>
<h2>并发模型之争：Goroutine 的舒适区 vs Zig 的显式控制</h2>
<p>Go 语言最大的护城河无疑是 Goroutine 和 Channel。这种 CSP（通信顺序进程）模型的极简实现，让并发编程变得唾手可得。然而，当这位开发者试图在 Zig 中复刻这一模式时，遭遇了不小的挑战。</p>
<h3>误用 std.Thread 的代价</h3>
<p>在移植过程中，他试图使用 Zig 的 std.Thread 配合 std.Thread.RwLock 来模拟 Go 的并发模式。然而，一位社区专家指出，这种做法在 Zig 的异步 I/O 体系下是危险且低效的。</p>
<p>Zig 的并发哲学与 Go 不同。Go 将同步（阻塞）代码在运行时自动调度到异步执行，而 Zig 则提供了显式的 async/await（注：Zig 的异步机制在不同版本间变动较大，0.16 预览版中正在重构）和基于事件循环的 IO 模型。</p>
<h3>io.Queue 与 Channel 的缺失</h3>
<p>为了实现类似 Go Channel 的功能，开发者不得不自己实现了一套基于 Mutex 的通知机制，或者使用第三方库。他坦言：“我不仅想念 Go 的 GC，也想念它的 Channel。”</p>
<p>虽然 Zig 提供了强大的底层原语，但在构建像 Go 那样开箱即用的高并发 Web 服务时，Zig 目前仍缺乏统一且成熟的标准范式（Standard Pattern）。对于习惯了 go func() 的开发者来说，这需要巨大的心智转换。</p>
<h2>工具链与生态的阵痛：先行者的代价</h2>
<p>如果你已经被 Zig 的性能和控制力打动，那么接下来的内容可能是你需要冷静思考的“劝退”环节。</p>
<h3>版本的混沌：0.15 vs 0.16</h3>
<p>Zig 尚未发布 1.0 版本，这意味着破坏性更新（Breaking Changes）是家常便饭。该开发者在尝试迁移到 Zig 0.16（开发版）时，遇到了 ZLS（Zig Language Server）的版本兼容性问题。编辑器报错、高亮失效、自动补全崩溃，这些在 Go 这种成熟语言中几乎不存在的问题，在 Zig 的日常开发中却是必须忍受的噪音。</p>
<h3>文档的匮乏</h3>
<p>“当有疑问时，请检查 Zig 的内置函数（Builtin functions），那里有很多东西。”这句话的潜台词是：不要指望有详尽的官方文档网站。与 Go 丰富且结构化的 pkg.go.dev 相比，Zig 目前更多依赖于阅读源码和社区碎片化的教程。对于习惯了 StackOverflow 复制粘贴的开发者，这无疑是一个巨大的门槛。</p>
<h3>“Segmentation Fault” 的回归</h3>
<p>正如社区评论所言：“你必须爱上 Segfaults（段错误）。”</p>
<p>Go 语言的运行时捕获了绝大多数底层错误，将其转化为 Panic。而在 Zig 中，尽管有安全模式（ReleaseSafe），但在处理底层指针操作时，你依然可能遇到这一古老的梦魇。开发者回忆道：“我在 2008 年写 C 语言时经常遇到这些，现在我必须重新学会如何调试它们。”</p>
<h2>小结：Go 依然是王者，但 Zig 代表了未来？</h2>
<p>回到最初的问题：<strong>Zig 会是系统级编程的最终答案吗？</strong></p>
<p>通过这次深刻的迁移实战，我们可以得出以下结论：</p>
<ol>
<li>Go 的地位难以撼动：对于绝大多数 Web 后端、微服务和云原生应用，Go 依然是“性价比之王”。它在开发效率、运行时性能和维护成本之间找到了完美的平衡点。正如作者所说，“Go 是最高级语言中的最底层”，这个定位极其精准。</li>
<li>Rust 并非唯一解：对于那些需要更高性能、更低内存占用，却被 Rust 陡峭的学习曲线和复杂的借用检查器劝退的开发者，Zig 提供了一个极具吸引力的第三选项。它证明了不引入复杂的生命周期注解，依然可以写出安全且高效的系统级代码。</li>
<li>Zig 的甜点区：如果你的项目涉及大量的内存密集型操作、需要极致的启动速度、或者需要与 C 库进行深度交互，Zig 可能比 Go 更合适，也比 Rust 更易上手。</li>
</ol>
<p><strong>给 Go 开发者的建议：</strong></p>
<p>如果你仅仅是对 Go 的某些性能瓶颈感到不满，不妨先通过 FFI 调用 Zig 编写的库来解决关键路径的性能问题，而不是全面重写。Zig 极其优秀的 C 互操作性，使其成为 Go 语言的最佳“外挂”。</p>
<p>随着 Zig 0.16 及后续版本的发布，特别是异步 IO 模型和包管理器的成熟，我们有理由相信，Zig 将在系统编程领域占据一席之地。它不会取代 Go，但它可能会成为那些追求极致掌控力的极客们手中的那把“光剑”。</p>
<p>资料链接：https://www.reddit.com/r/Zig/comments/1rd0fsz/thoughts_after_porting_a_project_from_go_to_zig/</p>
<hr />
<p><strong>聊聊你的选择</strong></p>
<p>你会因为 Go 的 GC 开销而考虑尝试 Zig 吗？还是你宁愿忍受 Rust 的编译器也不愿自己管理内存？欢迎在评论区分享你的看法！</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/02/26/rust-complexity-go-minimalism-vs-zig-ultimate-answer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>金融级基础设施重构：放弃 Rust 拥抱 Go，务实主义的最终胜利？</title>
		<link>https://tonybai.com/2026/02/23/financial-infrastructure-rust-to-go-pragmatism-victory/</link>
		<comments>https://tonybai.com/2026/02/23/financial-infrastructure-rust-to-go-pragmatism-victory/#comments</comments>
		<pubDate>Mon, 23 Feb 2026 01:09:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BackendDevelopment]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CognitiveLoad]]></category>
		<category><![CDATA[ConcurrencyModel]]></category>
		<category><![CDATA[Correctness]]></category>
		<category><![CDATA[DevelopmentEfficiency]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[DistributedSystems]]></category>
		<category><![CDATA[FinancialInfrastructure]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GoLanguage]]></category>
		<category><![CDATA[Go语言]]></category>
		<category><![CDATA[HFT]]></category>
		<category><![CDATA[InterfaceDesign]]></category>
		<category><![CDATA[latency]]></category>
		<category><![CDATA[machinelearning]]></category>
		<category><![CDATA[MemorySafety]]></category>
		<category><![CDATA[ML]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Pragmatism]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[ROI]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[SoftwareEngineering]]></category>
		<category><![CDATA[TeamBuilding]]></category>
		<category><![CDATA[TechSelection]]></category>
		<category><![CDATA[借用检查器]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[分布式系统]]></category>
		<category><![CDATA[务实主义]]></category>
		<category><![CDATA[后端开发]]></category>
		<category><![CDATA[团队建设]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[并发模型]]></category>
		<category><![CDATA[延迟]]></category>
		<category><![CDATA[开发效率]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[技术选型]]></category>
		<category><![CDATA[投入产出比]]></category>
		<category><![CDATA[接口设计]]></category>
		<category><![CDATA[机器学习]]></category>
		<category><![CDATA[正确性]]></category>
		<category><![CDATA[认知负荷]]></category>
		<category><![CDATA[软件工程]]></category>
		<category><![CDATA[金融级基础设施]]></category>
		<category><![CDATA[高频交易]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5934</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/02/23/financial-infrastructure-rust-to-go-pragmatism-victory 大家好，我是Tony Bai。 在系统级编程语言的版图上，Go 与 Rust 的对比与争论从未停歇。一个是崇尚大道至简、开发效率极高的“云原生时代王者”；另一个则是以内存安全、零成本抽象和极致性能著称的“极客新宠”。当这两种哲学碰撞在对安全性、稳定性和低延迟要求极高的金融/交易基础设施领域时，开发者该如何抉择？ 近日，在 Reddit 的 r/golang 社区中，一场由 Python 开发者发起的关于“金融基础设施长期演进：Go 还是 Rust？”的技术讨论引发了广泛关注。这位开发者试图为机器学习（ML）流水线、分布式后端和内部 DevOps 工具选择一门强类型语言，并一度陷入了“是否应该同时学习两者”的焦虑中。 这场社区讨论不仅揭示了两种语言在现代架构中的真实定位，更展现了 Go 社区一贯的“务实主义”工程哲学。本文将深度提炼这场讨论的核心观点，为正处于技术选型十字路口的架构师和开发者提供极具价值的参考。 核心探讨：金融系统中的“快”与“对” 在金融科技（FinTech）和交易系统中，有两个指标至关重要：性能（Performance/Latency）与 正确性（Correctness）。这恰好对应了系统级语言常常被审视的两个维度。 Rust 的诱惑：绝对的控制与“编译即正确” 许多开发者最初被 Rust 吸引，正是因为其在金融领域展现出的“绝对严谨”。 代数数据类型与状态机：社区用户指出，Rust 的表达能力极强。在处理复杂的金融业务逻辑（如订单状态流转、复杂的税务和结算规则）时，Rust 的枚举（Enum）和模式匹配可以迫使开发者在编译期处理所有可能的边缘情况，实现所谓的“使无效状态不可表达”（Make invalid states unrepresentable）。 无数据竞争（Data Race Free）：借用检查器（Borrow Checker）和所有权模型在根本上杜绝了多线程环境下的数据竞争。对于处理资金流水的并发程序而言，这种内存安全性能够极大地降低睡眠被报警惊醒的概率。 无 GC 延迟：针对极度敏感的场景（如做市商系统），Rust 摆脱了垃圾回收器（Garbage Collector）的不可预测性，能够提供稳定、可预测的尾部延迟（Tail Latency）。 然而，正如资深工程师在讨论中指出的：“Rust 的高壁垒不仅体现在初始学习成本上，更体现在它持续要求你的大脑处于高速运转状态。” 在编写普通业务代码时，开发者需要不断与编译器“搏斗”，这在无形中拖慢了业务交付（Shipping）的速度。 Go 的底气：“80% 的性能，20% 的精力” 面对 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/financial-infrastructure-rust-to-go-pragmatism-victory-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/02/23/financial-infrastructure-rust-to-go-pragmatism-victory">本文永久链接</a> &#8211; https://tonybai.com/2026/02/23/financial-infrastructure-rust-to-go-pragmatism-victory</p>
<p>大家好，我是Tony Bai。</p>
<p>在系统级编程语言的版图上，Go 与 Rust 的对比与争论从未停歇。一个是崇尚大道至简、开发效率极高的“云原生时代王者”；另一个则是以内存安全、零成本抽象和极致性能著称的“极客新宠”。当这两种哲学碰撞在对安全性、稳定性和低延迟要求极高的金融/交易基础设施领域时，开发者该如何抉择？</p>
<p>近日，在 Reddit 的 r/golang 社区中，一场由 Python 开发者发起的关于“<a href="https://www.reddit.com/r/golang/comments/1ra0dza/go_vs_rust_for_longterm_systemsfinance/">金融基础设施长期演进：Go 还是 Rust？</a>”的技术讨论引发了广泛关注。这位开发者试图为机器学习（ML）流水线、分布式后端和内部 DevOps 工具选择一门强类型语言，并一度陷入了“是否应该同时学习两者”的焦虑中。</p>
<p>这场社区讨论不仅揭示了两种语言在现代架构中的真实定位，更展现了 Go 社区一贯的“务实主义”工程哲学。本文将深度提炼这场讨论的核心观点，为正处于技术选型十字路口的架构师和开发者提供极具价值的参考。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/system-programming-in-go-pr.png" alt="" /></p>
<h2>核心探讨：金融系统中的“快”与“对”</h2>
<p>在金融科技（FinTech）和交易系统中，有两个指标至关重要：性能（Performance/Latency）与 正确性（Correctness）。这恰好对应了系统级语言常常被审视的两个维度。</p>
<h3>Rust 的诱惑：绝对的控制与“编译即正确”</h3>
<p>许多开发者最初被 Rust 吸引，正是因为其在金融领域展现出的“绝对严谨”。</p>
<ul>
<li>代数数据类型与状态机：社区用户指出，Rust 的表达能力极强。在处理复杂的金融业务逻辑（如订单状态流转、复杂的税务和结算规则）时，Rust 的枚举（Enum）和模式匹配可以迫使开发者在编译期处理所有可能的边缘情况，实现所谓的“使无效状态不可表达”（Make invalid states unrepresentable）。</li>
<li>无数据竞争（Data Race Free）：借用检查器（Borrow Checker）和所有权模型在根本上杜绝了多线程环境下的数据竞争。对于处理资金流水的并发程序而言，这种内存安全性能够极大地降低睡眠被报警惊醒的概率。</li>
<li>无 GC 延迟：针对极度敏感的场景（如做市商系统），Rust 摆脱了垃圾回收器（Garbage Collector）的不可预测性，能够提供稳定、可预测的尾部延迟（Tail Latency）。</li>
</ul>
<p>然而，正如资深工程师在讨论中指出的：“Rust 的高壁垒不仅体现在初始学习成本上，更体现在它持续要求你的大脑处于高速运转状态。” 在编写普通业务代码时，开发者需要不断与编译器“搏斗”，这在无形中拖慢了业务交付（Shipping）的速度。</p>
<h3>Go 的底气：“80% 的性能，20% 的精力”</h3>
<p>面对 Rust 强大的理论优势，Go 社区给出的回应并不是在极限性能上去硬碰硬，而是打出了一张工程学上的王牌：投入产出比（ROI）。</p>
<ul>
<li>极速的开发与迭代：“如果你的目标是尽快发布产品（Ship fast），同时保持系统的可靠性，Go 是完美的折中。” Go 语言的语法极简，没有复杂的生命周期标注，这使得开发者可以把 100% 的精力放在业务逻辑和系统架构上，而不是讨好编译器。</li>
<li>完美的 I/O 并发模型：金融系统的很大一部分工作并非重度 CPU 计算，而是网络 I/O（如对接外部交易所 API、读取数据库、微服务间通信）。Go 内置的 goroutine 提供了极其廉价的上下文切换机制。一位用户精辟地总结：“在处理高度并发或重度 I/O 阻塞的操作时，Go 是无敌的。而在 Rust 中构建高并发的异步（Async）应用，需要极高的经验值，但在 Go 中这就像呼吸一样自然。”</li>
<li>足够好的性能与 GC：虽然 Go 有垃圾回收机制，但经过十多年的演进，Go 的 GC 停顿时间已经达到了亚毫秒级。对于 99% 的金融应用（如支付网关、账单系统、风控后端）来说，Go 的性能已经“快到了性能盈余”的地步。社区用户坦言：“除非你是在证券交易所做内部的高频交易（HFT），否则 Go 的速度绝对绰绰有余。”</li>
</ul>
<h2>领域决定边界：基础设施与业务逻辑的解耦</h2>
<p>讨论中一个非常核心的洞见是：不要试图用一种语言解决所有问题，而是要看清具体领域的边界。楼主的背景是 Python，主要涉及 ML 流水线。这引出了现代架构中非常经典的一种组合模式。</p>
<h3>Python + Go：现代数据驱动架构的“王炸”组合</h3>
<ul>
<li>Python 主宰数据与模型：在机器学习、量化分析和数据科学领域，Python 的生态（Pandas, NumPy, PyTorch）具有不可撼动的统治地位。强行用 Go 或 Rust 去重写模型训练或复杂的矩阵运算，被社区公认为“过早优化”和“重复造轮子”。</li>
<li>Go 主宰服务与编排：当模型训练完成需要部署上线，或者需要构建处理海量请求的 API 网关、数据搬运管道、以及后端微服务时，Python 的 GIL（全局解释器锁）和性能瓶颈就会显现。此时，引入 Go 作为基础设施层（Infrastructure Layer）是最完美的互补。</li>
</ul>
<p>这种架构下，系统被清晰地划分为：Go 负责将数据又快又稳地搬运和路由，Python（在底层 C/C++ 的加持下）负责纯粹的数学和模型计算。这种解耦使得整个系统既享受了 Python 的生态红利，又获得了 Go 在分布式系统上的强悍工程能力。</p>
<h3>真正的 HFT（高频交易）属于谁？</h3>
<p>不可忽视的是，当讨论深入到金融领域的最底端——高频交易（HFT）时，社区展现出了极度客观的技术视野。</p>
<p>多位业内人士指出，在纳秒必争的超低延迟交易领域，C++ 依然是绝对的霸主。尽管 Rust 在试图切入这一市场，但 C++ 在传统金融领域积累的庞大库、成熟的生态以及直接操作硬件的能力，短期内难以被撼动。因此，如果业务的核心真的是 HFT，那么 Go 和 Rust 可能都不是最优解。这就进一步确认了 Go 的主战场：<strong>高吞吐的分布式后端与云原生基础设施。</strong></p>
<h2>隐性成本：认知负荷、团队建设与代码维护</h2>
<p>在架构决策中，语言的特性往往只占 50%，另外 50% 则是<strong>关于人的管理</strong>。这也是本次社区讨论中，Go 获得压倒性支持的关键原因。</p>
<h3>代码的生命周期与可修改性</h3>
<p>“在商业应用中，我更看重随着时间的推移，修改代码有多难。业务需求在不断变化，代码也必须随之改变。”</p>
<ul>
<li>Go 的修改成本极低：Go 的代码结构扁平，没有复杂的隐式抽象。这使得重构和修改极其快速。Go 的接口（Interface）设计是隐式的（Duck Typing），在拆分微服务或调整模块时，不需要像严格继承体系那样大动干戈。</li>
<li>Rust 的“牵一发而动全身”：Rust 高度严格的类型系统是一把双刃剑。虽然它保证了修改后的代码几乎不会出错，但在快速迭代期，添加一个新功能往往意味着要重构一大部分的生命周期标注和类型关系，这对于需要快速响应市场变化的初创项目来说是致命的。</li>
</ul>
<h3>团队招聘与代码交接</h3>
<p>“如果你用 Rust 构建了一个工具，当系统在半夜发生故障时，团队里的其他人能轻易地看懂代码并修复它吗？”</p>
<p>Go 的创造者之一 Rob Pike 曾明确表示，Go 的设计初衷就是为了解决 Google 内部大型团队的协作问题。Go 的语法少、规范统一（gofmt），被称为“没有魔法的语言”。一个有其他语言基础的程序员，通常只需一两周就能熟练上手 Go 并提交生产代码。</p>
<p>相比之下，熟练的 Rust 开发者在市场上不仅稀缺，而且薪资高昂。对于一家非底层技术驱动的金融公司而言，使用 Go 可以极大地降低招聘门槛和团队代码交接的风险。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/financial-infrastructure-rust-to-go-pragmatism-victory-2.png" alt="" /></p>
<h2>小结：务实主义的胜利</h2>
<p>回到这位发帖者的终极问题：“我应该同时深入学习 Go 和 Rust 吗？”</p>
<p>社区给出的答案异常一致：<strong>绝对不要。</strong> 尤其是在项目初期。同时学习两门底层逻辑截然不同的语言，不仅会带来巨大的认知撕裂，还会严重拖慢项目进度（Shipping speed）。</p>
<p>最终，这位发帖者更新了他的决定：<strong>选择 Go。</strong></p>
<blockquote>
<p>“我不想在开始阶段就陷入困境，既然我是独立开发，我开始觉得 Go 才是正道。对于沉重的数学计算，我会继续让 Python 负责。我意识到 Go 真的非常好用，只要我懂得正确使用它，它能在所有的用例中大显身手。此外，Go 社区是我见过最友好的社区之一，你们太棒了！”</p>
</blockquote>
<p>在 AI、区块链、量化金融等技术泡沫层出不穷的今天，技术选型很容易陷入“追逐时髦”（Hype Driven Development）的陷阱。Rust 无疑是一门伟大的语言，代表了系统编程的未来探索。然而，Go 语言的伟大之处在于它始终保持着<strong>极其清醒的工程边界感</strong>。</p>
<p>它不追求类型理论的极致完美，也不苛求消除最后百分之一的性能损耗，它追求的是：在开发者心智负担、编译速度、运行性能、并发模型和部署便利性之间，找到一个无可挑剔的全局最优解。</p>
<p>对于现代分布式系统、网络服务和金融后端基础设施而言，Go 依然是那个能够让你“早点下班、安心睡觉”的最优选择。这也是务实主义在工程世界里，又一次漂亮的胜利。</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1ra0dza/go_vs_rust_for_longterm_systemsfinance/</p>
<hr />
<p><strong>你怎么选？</strong></p>
<p>软件工程永远是权衡的艺术。在你看来，对于非高频交易的后端业务，Rust 带来的安全性是否足以抵消它的开发成本？如果你现在接手一个新项目，你会优先选择“能让你早点下班”的 Go 吗？</p>
<p>欢迎在评论区分享你的选型“心法”！</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/02/23/financial-infrastructure-rust-to-go-pragmatism-victory/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TypeScript 编译器 Go 重写版提速 10 倍：微软团队深度揭秘幕后工程细节</title>
		<link>https://tonybai.com/2026/01/27/typescript-compiler-go-rewrite-10x-speed-microsoft-details/</link>
		<comments>https://tonybai.com/2026/01/27/typescript-compiler-go-rewrite-10x-speed-microsoft-details/#comments</comments>
		<pubDate>Mon, 26 Jan 2026 23:21:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AbstractSyntaxTree]]></category>
		<category><![CDATA[ast]]></category>
		<category><![CDATA[async]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[Concurrency]]></category>
		<category><![CDATA[Consstring]]></category>
		<category><![CDATA[CrossPlatform]]></category>
		<category><![CDATA[electron]]></category>
		<category><![CDATA[Figma]]></category>
		<category><![CDATA[FunctionColoring]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[GenericMethods]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[GopherCon2025]]></category>
		<category><![CDATA[IndependentCheckers]]></category>
		<category><![CDATA[JakeBailey]]></category>
		<category><![CDATA[JIT优化]]></category>
		<category><![CDATA[MemoryManagement]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[NativeCode]]></category>
		<category><![CDATA[NilSafety]]></category>
		<category><![CDATA[node.js]]></category>
		<category><![CDATA[NullCoalescing]]></category>
		<category><![CDATA[OOM]]></category>
		<category><![CDATA[OptionalChaining]]></category>
		<category><![CDATA[PerformanceLeap]]></category>
		<category><![CDATA[Rewrite]]></category>
		<category><![CDATA[Selfhosting]]></category>
		<category><![CDATA[shadowing]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[slack]]></category>
		<category><![CDATA[StructEmbedding]]></category>
		<category><![CDATA[TernaryOperator]]></category>
		<category><![CDATA[ts-to-go]]></category>
		<category><![CDATA[TypeChecking]]></category>
		<category><![CDATA[TypeScript]]></category>
		<category><![CDATA[TypeScript7.0]]></category>
		<category><![CDATA[UnionTypes]]></category>
		<category><![CDATA[vscode]]></category>
		<category><![CDATA[wasm]]></category>
		<category><![CDATA[WebAssembly]]></category>
		<category><![CDATA[三元运算符]]></category>
		<category><![CDATA[上位]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内存拷贝]]></category>
		<category><![CDATA[内存管理]]></category>
		<category><![CDATA[内存限制]]></category>
		<category><![CDATA[函数着色]]></category>
		<category><![CDATA[单线程]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[多核CPU]]></category>
		<category><![CDATA[字符串拼接]]></category>
		<category><![CDATA[学习曲线]]></category>
		<category><![CDATA[实用主义]]></category>
		<category><![CDATA[对象模型]]></category>
		<category><![CDATA[工程挑战]]></category>
		<category><![CDATA[平滑上手]]></category>
		<category><![CDATA[并发设计]]></category>
		<category><![CDATA[开发成本]]></category>
		<category><![CDATA[异步]]></category>
		<category><![CDATA[影子变量]]></category>
		<category><![CDATA[微软]]></category>
		<category><![CDATA[心智负担]]></category>
		<category><![CDATA[性能天花板]]></category>
		<category><![CDATA[性能提升]]></category>
		<category><![CDATA[技术细节]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[机器码]]></category>
		<category><![CDATA[泛型方法]]></category>
		<category><![CDATA[测试用例]]></category>
		<category><![CDATA[独立检查器]]></category>
		<category><![CDATA[电子表格]]></category>
		<category><![CDATA[破坏性变更]]></category>
		<category><![CDATA[空值合并]]></category>
		<category><![CDATA[空值安全]]></category>
		<category><![CDATA[符号绑定]]></category>
		<category><![CDATA[等价交换]]></category>
		<category><![CDATA[类型检查]]></category>
		<category><![CDATA[结构体嵌入]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[联合类型]]></category>
		<category><![CDATA[胖指针]]></category>
		<category><![CDATA[自举]]></category>
		<category><![CDATA[自动化工具]]></category>
		<category><![CDATA[范本]]></category>
		<category><![CDATA[设计]]></category>
		<category><![CDATA[语法糖]]></category>
		<category><![CDATA[跨平台]]></category>
		<category><![CDATA[软件规范]]></category>
		<category><![CDATA[运行速度]]></category>
		<category><![CDATA[重写]]></category>
		<category><![CDATA[链式调用]]></category>
		<category><![CDATA[静态分析]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5776</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/27/typescript-compiler-go-rewrite-10x-speed-microsoft-details 大家好，我是Tony Bai。 “JavaScript 是一门很棒的语言，但它并不是为了编写编译器而设计的。” 备受瞩目的 TypeScript 编译器 Go 重写版（代号 TypeScript 7.0）已经取得了惊人的 10 倍性能提升。在最近的 GopherCon 2025 上，来自 Microsoft TypeScript 团队的 Jake Bailey 带来了一场干货满满的分享，深度揭秘了这场跨语言大迁徙背后的工程挑战与技术细节。 为什么最终选择了 Go？庞大的 AST 如何在 Go 中高效表达？又是如何通过并发设计打破 Node.js 的性能枷锁的？本文将带你深入编译器内部，一探究竟。 缘起：当 JavaScript 触碰到天花板 TypeScript 自 2012 年发布以来，一直采用“自举” (Self-hosting) 的方式，即用 TypeScript 编写 TypeScript 编译器。这带来了巨大的好处：团队能第一时间吃自己的狗粮，社区贡献也极其方便。 然而，JavaScript 并不是为了编写高性能编译器而设计的。随着代码库规模的爆炸式增长（如 VS Code 的 150 万行代码），基于 Node.js 的编译器逐渐触碰到了性能天花板： [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/27/typescript-compiler-go-rewrite-10x-speed-microsoft-details">本文永久链接</a> &#8211; https://tonybai.com/2026/01/27/typescript-compiler-go-rewrite-10x-speed-microsoft-details</p>
<p>大家好，我是Tony Bai。</p>
<p>“JavaScript 是一门很棒的语言，但它并不是为了编写编译器而设计的。”</p>
<p>备受瞩目的 <a href="https://tonybai.com/2025/03/13/interview-with-anders-hejlsberg">TypeScript 编译器 Go 重写版</a>（代号 TypeScript 7.0）已经取得了惊人的 10 倍性能提升。在最近的 GopherCon 2025 上，来自 Microsoft TypeScript 团队的 Jake Bailey 带来了<a href="https://www.youtube.com/watch?v=PZm_YbE3fcA">一场干货满满的分享</a>，深度揭秘了这场跨语言大迁徙背后的工程挑战与技术细节。</p>
<p>为什么最终选择了 Go？庞大的 AST 如何在 Go 中高效表达？又是如何通过并发设计打破 Node.js 的性能枷锁的？本文将带你深入编译器内部，一探究竟。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="img{512x368}" /></p>
<h2>缘起：当 JavaScript 触碰到天花板</h2>
<p>TypeScript 自 2012 年发布以来，一直采用“自举” (Self-hosting) 的方式，即用 TypeScript 编写 TypeScript 编译器。这带来了巨大的好处：团队能第一时间吃自己的狗粮，社区贡献也极其方便。</p>
<p>然而，JavaScript 并不是为了编写高性能编译器而设计的。随着代码库规模的爆炸式增长（如 VS Code 的 150 万行代码），基于 Node.js 的编译器逐渐触碰到了性能天花板：</p>
<ul>
<li><strong>单线程与内存限制</strong>：JavaScript 无法高效利用多核 CPU，且 Node.js 构建环境（如 Electron）常常面临 4GB 内存上限，导致大型项目编译时频繁 OOM。</li>
<li><strong>昂贵的对象模型</strong>：JavaScript 的对象模型开销巨大，而编译器需要创建数以百万计的 AST 节点，这对内存和 GC 都是沉重的负担。</li>
<li><strong>异步的代价</strong>：async/await 虽然方便，但带来了著名的“函数着色”问题，且 Promise 对象的分配本身就有非零的运行时开销。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-2.png" alt="" /></p>
<p>尽管团队已经用尽了 JIT 优化、缓存、单态化 (monomorphization) 等高级手段，但性能提升的边际效应越来越小，OOM 问题依然挥之不去。移植到另外一种语言，成为了打破僵局的唯一选择。</p>
<h2>明确目标：新编译器的硬性指标</h2>
<p>既然决定要移植到新语言，那么新语言必须解决 JavaScript 的痛点，同时不能丢失现有的优势。团队列出了几条不可妥协的硬性指标：</p>
<ol>
<li><strong>极致速度</strong>：必须编译为原生机器码 (Native Code)，摆脱解释器和 JIT 的预热开销。</li>
<li><strong>共享内存并发</strong>：这是性能翻盘的关键。新语言必须对多线程共享内存有强力支持，以便充分压榨多核性能。</li>
<li><strong>跨平台支持</strong>：必须能运行在所有主流操作系统上，最重要的是——<strong>必须能编译为 WebAssembly</strong>，以确保在浏览器环境（如 vscode.dev）中的体验。</li>
<li><strong>无缝移植</strong>：鉴于 TypeScript 没有正式的语言规范（Spec），现有的编译器实现就是事实上的规范。因此，新语言必须能够最大程度地保留原有代码的结构和逻辑，以确保行为的一致性。</li>
</ol>
<p>正是这几条苛刻的标准，将选型的范围迅速缩小。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-4.png" alt="" /></p>
<h2>选型：为什么是 Go？</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-3.png" alt="" /></p>
<p>在考察了 Rust、C#、Zig 等语言后，Go 脱颖而出。Jake 透露了核心的决策逻辑：</p>
<ol>
<li><strong>带 GC 的内存管理</strong>：编译器涉及大量复杂的、循环引用的数据结构（如 AST 节点），“手动”管理内存（如 Rust）会带来巨大的心智负担和开发成本。Go 的 GC 完美契合这一需求。</li>
<li><strong>结构相似性</strong>：TypeScript 的代码风格（无类、大量函数和接口）与 Go 非常相似。这使得“移植”而非“重写”成为可能。</li>
<li><strong>学习曲线平缓</strong>：团队中大部分是 TypeScript 专家而非系统编程专家。Go 的简单性让团队能迅速上手。</li>
<li><strong>跨平台与性能</strong>：Go 编译为原生机器码，天生支持高并发，且能轻松跨平台（包括编译为 WASM）。</li>
</ol>
<p>Go完美地契合了TypeScript编译器移植的需求！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-5.png" alt="" /></p>
<h2>早期验证：手写原型与意外惊喜</h2>
<p>在决定全面转向 Go 之前，团队并未贸然行动，而是采取了稳健的“原型验证”策略。</p>
<p>他们从编译器的最底层——<strong>扫描器 (Scanner) 和解析器 (Parser)</strong>——开始，尝试手工将 TypeScript 代码逐行“翻译”为 Go 代码。与此同时，为了确保决策万无一失，还有几位成员试探性地尝试了其他语言方案。</p>
<p>结果令人振奋：即使是初步的手写 Go 代码，<strong>解析速度也达到了原版的 5 倍左右！</strong></p>
<p>更重要的是，团队惊喜地发现，<strong>手写的 Go 代码在结构和逻辑上与原始的 TypeScript 代码惊人地相似</strong>。这种代码形态上的高度一致性，不仅验证了 Go 是正确的选择，更为后续大规模自动化工具的开发注入了强心剂。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-7.png" alt="" /></p>
<h2>移植实战：从 ts-to-go 到并发革命</h2>
<h3>1. 自动化移植工具：ts-to-go</h3>
<p>为了加速迁移，Jake 编写了一个 <a href="https://github.com/jakebailey/ts-to-go">ts-to-go</a> 工具，能将 TypeScript 代码“直译”为 Go 代码。</p>
<ul>
<li>TS 的 interface -> Go 的 interface</li>
<li>TS 的 class -> Go 的 struct + methods</li>
<li>复杂的位运算和逻辑判断 -> 自动转换为 Go 的等价写法</li>
</ul>
<p>虽然不能 100% 完美转换，但这让团队在初期就能获得一个“虽然丑但能跑”的版本，极大加速了进程。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-6.png" alt="" /></p>
<h3>2. 数据结构的重新设计</h3>
<p>在 JavaScript 中，对象是动态的；在 Go 中，一切皆有类型。团队不得不对 AST 的数据结构进行大刀阔斧的改革。</p>
<ul>
<li><strong>消除 interface 滥用</strong>：最初的移植版本大量使用 interface 来模拟 TS 的多态，导致了巨大的内存开销（胖指针）和 nil 检查地狱。</li>
<li><strong>拥抱 struct 嵌入</strong>：最终，他们设计了一个基础 Node 结构体，并将其嵌入到所有具体的 AST 节点中。这不仅减少了内存占用，还彻底解决了 nil 接口的问题。</li>
</ul>
<h3>3. 并发：性能提升的核心引擎</h3>
<p>这是 Go 带来的最大红利。旧的 TS 编译器是单线程的，解析、绑定、检查、生成都在一条线上排队。</p>
<p>而在 Go 版本中：</p>
<ul>
<li><strong>解析 (Parsing)</strong>：每个文件可以独立解析，完全并行。</li>
<li><strong>绑定 (Binding)</strong>：每个文件的符号绑定也是独立的，完全并行。</li>
<li><strong>类型检查 (Type Checking)</strong>：这是最难的部分，因为文件间存在复杂的依赖。团队采用了<strong>“独立检查器” (Independent Checkers)</strong> 的模式，为每组文件分配一个独立的检查器，虽然会有少量重复工作，但实现了高度的并行化。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-9.png" alt="" /></p>
<p>结果是惊人的：<strong>VS Code 的编译时间从 80 秒缩短到了 7 秒，速度提升超过 10 倍！</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-8.png" alt="" /></p>
<h2>踩坑与优化：Go 也没那么简单</h2>
<p>当然，移植过程并非一帆风顺。Jake 分享了几个典型的“水土不服”案例：</p>
<ul>
<li><strong>影子变量 (Shadowing)</strong>：Go 允许在内层作用域遮蔽外层变量（如 err、result等），这导致了无数隐蔽的 Bug。Jake 甚至为此专门写了一个静态分析工具(https://jakebailey.dev/posts/go-shadowing)来抓这些虫子。</li>
<li><strong>方法值的分配</strong>：在 Go 中，将方法作为值传递（如 parser.LookAhead）会产生一次内存分配。在一个频繁调用的紧密循环中，这带来了 17% 的性能损耗。解决方案是改回显式的函数调用。</li>
<li><strong>字符串拼接</strong>：JavaScript 引擎对字符串拼接有深度优化（Cons-string），而 Go 的 + 操作符则是实打实的内存拷贝。这导致初期的移植版本在处理大量字符串时性能惨不忍睹。</li>
</ul>
<h2>遗憾与取舍：那些我们怀念的 TypeScript 特性</h2>
<p>正如 Jake 在演讲中所言，这次迁移是一场巨大的工程胜利，但也是一次充满妥协的旅程。从表达力丰富的 TypeScript 转向“极简主义”的 Go，团队不得不忍痛割爱，放弃了许多令人怀念的语言特性：</p>
<ul>
<li><strong>编译期空值安全 (Compile-time nil safety)</strong>：这是团队最怀念的特性。在 Go 中，空指针异常（Panic）依然是悬在头顶的达摩克利斯之剑，而在 TypeScript 中，null/undefined 是类型系统的一部分，能被编译器严格检查。</li>
<li><strong>空值合并与链式调用 (??, ?.)</strong>：Go 缺乏这些语法糖，使得代码中充斥着冗长的 if x != nil 检查，远不如 TypeScript 优雅。</li>
<li><strong>联合类型与类型收窄 (Union types, narrowing)</strong>：TypeScript 强大的联合类型让数据建模极其灵活，而在 Go 中，这不得不退化为接口或带有大量字段的结构体。</li>
<li><strong>泛型方法与三元运算符</strong>：这些“现代化”特性的缺失，让从前端背景转过来的工程师们颇感不适。</li>
</ul>
<p>然而，对于编译器团队来说，<strong>为了性能，这一切“阵痛”都是值得的</strong>。他们用语法的繁琐换取了运行时的极速，这正是工程世界中最经典的“等价交换”。</p>
<blockquote>
<p>注：<a href="https://tonybai.com/2026/01/24/go-generics-finally-supports-generic-methods">关于泛型方法，Go团队很大可能将在Go 1.27支持！</a></p>
</blockquote>
<h2>未来展望：TypeScript 7.0</h2>
<p>目前，Go 版本的编译器已经能通过 10 万个测试用例，并在 Slack、Figma 等大厂的内部构建中试运行（Slack 的构建时间从 6 分钟降至 40 秒）。</p>
<p>Microsoft 计划在 TypeScript 6.0 中开始引入一些破坏性变更，为 Go 版本的上位做铺垫。而那个完全由 Go 驱动、极速的编译器，预计将被命名为 <strong>TypeScript 7.0</strong>。</p>
<p>这场从 Node.js 到 Go 的大迁徙，不仅证明了 Go 在复杂编译器领域的工程能力，也为所有面临类似性能瓶颈的团队，提供了一个极具参考价值的范本。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/typescript-compiler-go-rewrite-10x-speed-microsoft-details-10.png" alt="" /></p>
<blockquote>
<p>注：微软在2025年12月初发布了TypeScript 7.0的最新进展，大家可以在 https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/ 这里了解详情。</p>
</blockquote>
<p>资料链接：https://www.youtube.com/watch?v=PZm_YbE3fcA</p>
<hr />
<p><strong>你的“重写”冲动</strong></p>
<p>微软用 Go 重写 TS 编译器，是一次壮士断腕般的成功尝试。<strong>在你维护的项目中，是否有那个让你想要“推倒重来”的性能瓶颈？如果让你选，你会<br />
用 Go 还是 Rust 来重写它？</strong></p>
<p><strong>欢迎在评论区分享你的重构经历或选型思考！</strong> 让我们一起探讨如何在性能与开发效率之间找到平衡。</p>
<p><strong>如果这篇文章让你对 Go 在大型项目中的潜力有了新的认识，别忘了点个【赞】和【在看】，并转发给你的架构师朋友！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/27/typescript-compiler-go-rewrite-10x-speed-microsoft-details/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go, Rust 还是 Zig？一场关于“简单”与“控制”的灵魂拷问</title>
		<link>https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/</link>
		<comments>https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/#comments</comments>
		<pubDate>Fri, 16 Jan 2026 23:38:52 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[BuildToolchain]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[comptime]]></category>
		<category><![CDATA[Control]]></category>
		<category><![CDATA[enum]]></category>
		<category><![CDATA[Explicit]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[Implicit]]></category>
		<category><![CDATA[lifetimes]]></category>
		<category><![CDATA[ManualMemoryManagement]]></category>
		<category><![CDATA[MemoryManagement]]></category>
		<category><![CDATA[MemorySafety]]></category>
		<category><![CDATA[option]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Productivity]]></category>
		<category><![CDATA[Result]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[simplicity]]></category>
		<category><![CDATA[SystemProgramming]]></category>
		<category><![CDATA[TechnicalSelection]]></category>
		<category><![CDATA[ZerocostAbstraction]]></category>
		<category><![CDATA[Zig]]></category>
		<category><![CDATA[借用检查器]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[内存布局]]></category>
		<category><![CDATA[内存管理]]></category>
		<category><![CDATA[异步模型]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[手动内存管理]]></category>
		<category><![CDATA[技术选型]]></category>
		<category><![CDATA[控制]]></category>
		<category><![CDATA[显式]]></category>
		<category><![CDATA[构建工具链]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[生产力]]></category>
		<category><![CDATA[生命周期]]></category>
		<category><![CDATA[简单]]></category>
		<category><![CDATA[系统编程]]></category>
		<category><![CDATA[编译期执行]]></category>
		<category><![CDATA[表达力]]></category>
		<category><![CDATA[认知负荷]]></category>
		<category><![CDATA[隐式]]></category>
		<category><![CDATA[零成本抽象]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5733</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control 大家好，我是Tony Bai。 在系统编程的世界里，开发者似乎总是面临着一个残酷的二选一：是选择极致的简单与生产力，还是选择绝对的控制与零成本抽象？ 这种纠结在 Go 与 Rust 的长期对峙中体现得淋漓尽致。然而，近日一位拥有十年 Go 经验的资深开发者在Zig社区的分享，似乎为这场二元对立的战争撕开了一道口子。他从 Go 迁移到 Zig 的经历，既是一个技术选型的故事，也是一场关于“我们到底需要什么样的编程语言”的深度辩论。 Go 的困境：当“简单”成为一种束缚 对于许多 Gopher 来说，Go 的简单是其最大的武器，但也是最深的痛点。 这位楼主坦言，尽管他深爱 Go 的简单，但在编写某些复杂系统时，这种“过度简化”让他感觉语言本身存在缺陷。 表达力的缺失：Go 缺乏像 Rust 那样的 Enum (带数据的枚举)、Option 和 Result 类型。在处理复杂状态和错误流时，Go 的代码往往显得啰嗦且缺乏约束力。 “差不多”的无奈：为了保持简单，Go 在很多地方做了折中（比如 GC，比如泛型的实现方式）。当你需要榨干硬件性能或追求极致的内存布局时，Go 显得力不从心。 Rust 的围城：控制的代价是复杂度 如果嫌 Go 太简单，Rust 似乎是理所当然的替代者。但对于很多习惯了 Go “写完即运行”体验的开发者来说，Rust 的门槛是一堵高墙。 楼主表示，他喜欢 Rust 的核心概念（Structs, Enums, Option），但 Rust [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/go-rust-zig-simplicity-vs-control-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control">本文永久链接</a> &#8211; https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control</p>
<p>大家好，我是Tony Bai。</p>
<p>在系统编程的世界里，开发者似乎总是面临着一个残酷的二选一：是选择<strong>极致的简单与生产力</strong>，还是选择<strong>绝对的控制与零成本抽象</strong>？</p>
<p>这种纠结在 Go 与 Rust 的长期对峙中体现得淋漓尽致。然而，近日一位拥有十年 Go 经验的资深开发者<a href="https://www.reddit.com/r/Zig/comments/1q38e50/im_really_surprised_by_how_simple_it_is_to/">在Zig社区的分享</a>，似乎为这场二元对立的战争撕开了一道口子。他从 Go 迁移到 Zig 的经历，既是一个技术选型的故事，也是一场关于<strong>“我们到底需要什么样的编程语言”</strong>的深度辩论。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="" /></p>
<h2>Go 的困境：当“简单”成为一种束缚</h2>
<p>对于许多 Gopher 来说，Go 的简单是其最大的武器，但也是最深的痛点。</p>
<p>这位楼主坦言，尽管他深爱 Go 的简单，但在编写某些复杂系统时，这种“过度简化”让他感觉语言本身存在缺陷。</p>
<ul>
<li><strong>表达力的缺失</strong>：Go 缺乏像 Rust 那样的 Enum (带数据的枚举)、Option 和 Result 类型。在处理复杂状态和错误流时，Go 的代码往往显得啰嗦且缺乏约束力。</li>
<li><strong>“差不多”的无奈</strong>：为了保持简单，Go 在很多地方做了折中（比如 GC，比如泛型的实现方式）。当你需要榨干硬件性能或追求极致的内存布局时，Go 显得力不从心。</li>
</ul>
<h2>Rust 的围城：控制的代价是复杂度</h2>
<p>如果嫌 Go 太简单，Rust 似乎是理所当然的替代者。但对于很多习惯了 Go “写完即运行”体验的开发者来说，Rust 的门槛是一堵高墙。</p>
<p>楼主表示，他喜欢 Rust 的核心概念（Structs, Enums, Option），但 Rust 为了内存安全而引入的<strong>借用检查器、生命周期</strong>以及<strong>复杂的异步模型</strong>，让他感觉“像是面对另一个 C++”。</p>
<p>这是一场灵魂拷问：<strong>为了获得控制权，我们真的需要背负如此沉重的认知包袱吗？</strong></p>
<h2>Zig 的破局：在“简单”与“控制”之间走钢丝</h2>
<p>Zig 的出现，似乎精准地击中了 Go 与 Rust 之间的那个真空地带。对于这位 Gopher 来说，Zig 让他感到了久违的“刚刚好”：</p>
<ol>
<li><strong>显式的哲学（像 Go）</strong>：Zig 没有隐式内存分配，没有隐藏的控制流，也没有预处理器。这种“所见即所得”的代码风格，与 Go 的可读性哲学高度共鸣。</li>
<li><strong>现代的类型系统（像 Rust）</strong>：Zig 提供了 comptime（编译期执行）和丰富的类型系统，弥补了 Go 在表达力上的短板，却又没有引入 Rust 那样复杂的生命周期概念。</li>
<li><strong>对 C 的降维打击</strong>：Zig 不仅是一门语言，更是一个强大的 C/C++ 构建工具链。它允许你无缝地与 C 交互，逐步迁移遗留代码，这是 Go (CGO) 和 Rust 都难以做到的顺滑体验。</li>
</ol>
<h2>社区的冷思考：没有免费的午餐</h2>
<p>当然，这场灵魂拷问没有标准答案。社区的讨论也极其理性地指出了选择 Zig 的代价：</p>
<ul>
<li><strong>生态的荒原</strong>：与 Go 庞大的“标准库+第三方库”相比，Zig 的生态仍处于拓荒期。你可能需要自己造很多轮子。</li>
<li><strong>内存管理的回归</strong>：Zig 没有 GC，也没有 Rust 的所有权模型。这意味着你回到了手动管理内存的时代（尽管有 defer 和 arena 等工具辅助）。对于习惯了 GC 的 Gopher 来说，这是一个必须跨越的心理门槛。</li>
<li><strong>稳定性的豪赌</strong>：Zig 尚未发布 1.0，语言特性仍在变动。选择 Zig，意味着你愿意陪它一起成长，也愿意承担变动的风险。</li>
</ul>
<h2>小结：你的灵魂属于哪里？</h2>
<p>这场讨论最终指向了开发者内心的自我定位：</p>
<ul>
<li>如果你追求<strong>高效交付、团队协作和工业级的稳定性</strong>，Go 依然是不可撼动的王者。</li>
<li>如果你追求<strong>数学般的严谨、绝对的安全和零成本抽象</strong>，且不介意陡峭的学习曲线，Rust 是你的圣杯。</li>
<li>而如果你渴望<strong>掌控底层、厌倦了复杂的抽象、却又想要现代化的开发体验</strong>，Zig 也许就是你一直在寻找的那个“刚刚好”。</li>
</ul>
<p><strong>简单还是控制？这不仅是语言的选择，更是你作为工程师，想要如何与机器对话的选择。</strong></p>
<p>资料链接：https://www.reddit.com/r/Zig/comments/1q38e50/im_really_surprised_by_how_simple_it_is_to/</p>
<hr />
<p><strong>你的“灵魂选择”</strong></p>
<p>在“简单”与“控制”的天平上，<strong>你的心偏向哪一边？如果让你现在开始一个新项目，你会毫不犹豫地选择 Go，还是想尝尝 Zig 的鲜，亦或是死磕 Rust？</strong></p>
<p><strong>欢迎在评论区投出你的一票，并分享你的理由！</strong> 让我们看看谁才是开发者心中的“白月光”。</p>
<p><strong>如果这篇文章引发了你的选型思考，别忘了点个【赞】和【在看】，并转发给那个还在纠结学什么语言的朋友！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/17/go-rust-zig-simplicity-vs-control/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>内存去哪儿了？一个让大多数 Gopher 都无法清晰回答的问题</title>
		<link>https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question/</link>
		<comments>https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question/#comments</comments>
		<pubDate>Thu, 15 Jan 2026 00:21:39 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Backpressure]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[closure]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[FireandForget]]></category>
		<category><![CDATA[GarbageCollector]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[GlobalVariable]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[heap]]></category>
		<category><![CDATA[LifeCycle]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[MemoryAnchor]]></category>
		<category><![CDATA[MemoryLeak]]></category>
		<category><![CDATA[MentalModel]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[ownership]]></category>
		<category><![CDATA[pprof]]></category>
		<category><![CDATA[References]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Stack]]></category>
		<category><![CDATA[全局变量]]></category>
		<category><![CDATA[内存去哪儿了]]></category>
		<category><![CDATA[内存泄漏]]></category>
		<category><![CDATA[内存锚点]]></category>
		<category><![CDATA[即发即忘]]></category>
		<category><![CDATA[反压]]></category>
		<category><![CDATA[垃圾回收器]]></category>
		<category><![CDATA[引用]]></category>
		<category><![CDATA[心智模型]]></category>
		<category><![CDATA[所有权]]></category>
		<category><![CDATA[所有权设计]]></category>
		<category><![CDATA[生命周期]]></category>
		<category><![CDATA[诊断工具]]></category>
		<category><![CDATA[运行状态]]></category>
		<category><![CDATA[闭包]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5727</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question 大家好，我是Tony Bai。 “我的服务内存又在缓慢增长了，pprof 显示不出明显的泄漏点……内存到底去哪儿了？” 这句午夜梦回的拷问，或许是许多 Go 开发者心中最深的恐惧。 这一切的根源，可能始于一个你自以为早已掌握的基础问题：“Go 的状态 (state) 存在哪里？” Go 开发者 Abhishek Singh之前断言：“我保证，一大半的 Go 开发者都无法清晰地回答这个问题。” 你的答案是什么？“在 goroutine 里”？“在栈上”？“由 Go runtime 管理”？ 如果你的脑中闪过的是这些模糊的念头，那么你可能就找到了“内存失踪案”的“第一案发现场”。这个看似不起眼的认知模糊，正是导致无数生产环境中“内存缓慢泄露”、“goroutine 永不消亡”、“随机延迟飙升”等“灵异事件”的根源。 本文，将为你揭示这个问题的精确答案，并以此为起点，修复你关于 Go 内存管理的“心智模型”，让你从此能够清晰地回答：“内存，到底去哪儿了？” 揭晓答案与核心心智模型 首先，那个简单而重要的正确答案是： Go 的状态，就是由 Go runtime 管理的内存，它要么在栈 (stack) 上，要么在堆 (heap) 上。 然而，知道这个答案只是第一步。真正关键的，是摒弃那个导致所有问题的错误直觉，转而建立如下正确的核心心智模型： Goroutine 不拥有内存，引用 (References) 才拥有。 一个 Goroutine 的退出，并不会释放内存。 当一个 goroutine 结束时，它仅仅是停止了执行。它所创建或引用的任何内存，只要仍然被其他东西持有着引用，就永远不会被垃圾回收器 (GC) 回收。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/where-did-the-memory-go-gopher-unanswered-question-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question">本文永久链接</a> &#8211; https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question</p>
<p>大家好，我是Tony Bai。</p>
<blockquote>
<p>“我的服务内存又在缓慢增长了，pprof 显示不出明显的泄漏点……<strong>内存到底去哪儿了？</strong>”</p>
</blockquote>
<p>这句午夜梦回的拷问，或许是许多 Go 开发者心中最深的恐惧。</p>
<p>这一切的根源，可能始于一个你自以为早已掌握的基础问题：<strong>“Go 的状态 (state) 存在哪里？”</strong> Go 开发者 Abhishek Singh之前断言：“我保证，一大半的 Go 开发者都无法清晰地回答这个问题。”</p>
<p>你的答案是什么？“在 goroutine 里”？“在栈上”？“由 Go runtime 管理”？</p>
<p>如果你的脑中闪过的是这些模糊的念头，那么你可能就找到了“内存失踪案”的“第一案发现场”。这个看似不起眼的认知模糊，正是导致无数生产环境中“内存缓慢泄露”、“goroutine 永不消亡”、“随机延迟飙升”等“灵异事件”的根源。</p>
<p>本文，将为你揭示这个问题的<strong>精确答案</strong>，并以此为起点，修复你关于 Go 内存管理的“心智模型”，让你从此能够清晰地回答：“内存，到底去哪儿了？”</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="" /></p>
<h2>揭晓答案与核心心智模型</h2>
<p>首先，那个简单而重要的<strong>正确答案</strong>是：</p>
<blockquote>
<p><strong>Go 的状态，就是由 Go runtime 管理的内存，它要么在栈 (stack) 上，要么在堆 (heap) 上。</strong></p>
</blockquote>
<p>然而，知道这个答案只是第一步。真正关键的，是<strong>摒弃</strong>那个导致所有问题的<strong>错误直觉</strong>，转而建立如下<strong>正确的核心心智模型</strong>：</p>
<blockquote>
<p><strong>Goroutine 不拥有内存，引用 (References) 才拥有。</strong><br />
  <strong>一个 Goroutine 的退出，并不会释放内存。</strong></p>
</blockquote>
<p>当一个 goroutine 结束时，它仅仅是停止了执行。它所创建或引用的任何内存，只要仍然被<strong>其他东西</strong>持有着引用，就<strong>永远不会</strong>被垃圾回收器 (GC) 回收。</p>
<p>这些“其他东西”，就是你程序中的<strong>“内存锚点”</strong>，它们包括：</p>
<ul>
<li>一个全局变量</li>
<li>一个 channel</li>
<li>一个闭包</li>
<li>一个 map</li>
<li>一个被互斥锁保护的结构体</li>
<li>一个未被取消的 context</li>
</ul>
<p><strong>这，就是几乎所有“Go 内存泄漏”的根本原因。</strong> “内存去哪儿了？”——它被这些看不见的“锚点”，牢牢地拴在了堆上。</p>
<h2>三大“内存锚点”——Goroutine 泄漏的元凶</h2>
<p>Abhishek 将那些导致内存无法被回收的“引用持有者”，形象地称为“内存锚点”。其中，最常见、也最隐蔽的有三种。</p>
<h3>“永生”的 Goroutine：被遗忘的循环</h3>
<p>创建 goroutine 很廉价，但泄漏它们却极其昂贵。一个典型的“生命周期 Bug”：</p>
<pre><code class="go">// 经典错误：启动一个运行无限循环的 goroutine
go func() {
    for {
        work() // 假设 work() 会引用一些数据
    }
}()
</code></pre>
<p>这个 goroutine <strong>永远不会退出</strong>。它会永久地持有 work() 函数所引用的任何数据，阻止 GC 回收它们。如果你在每个 HTTP 请求中都启动一个这样的“即发即忘”(fire-and-forget) 的 goroutine，你的服务内存将会线性增长，直至崩溃。</p>
<p>这不是内存泄漏，是你设计了一个“不朽的工作负载”。</p>
<h3>Channel：不止传递数据，更持有引用</h3>
<p>Channel 不仅仅是数据的搬运工，它们更是<strong>强力的引用持有者</strong>。</p>
<pre><code class="go">ch := make(chan *BigStruct)
go func() {
    // 这个 goroutine 阻塞在这里，等待向 channel 发送数据
    ch &lt;- &amp;BigStruct{...}
}()

// 如果没有其他 goroutine 从 ch 中接收数据...
</code></pre>
<p>那么：</p>
<ul>
<li>那个 &amp;BigStruct{&#8230;} 将<strong>永久地</strong>被 ch 持有。</li>
<li>那个发送数据的 goroutine 将<strong>永久地</strong>阻塞。</li>
<li>GC <strong>永远无法</strong>回收 BigStruct 和这个 goroutine 的栈。</li>
</ul>
<p>这告诉我们：<strong>无缓冲或未被消费的 Channel，是缓慢的死亡。</strong> 它们会像“锚”一样，将数据和 goroutine 牢牢地钉在内存中。</p>
<h3>context：被忽视的生命周期边界</h3>
<p>context 包是 Go 中定义生命周期边界的“标准语言”。然而，一个常见的错误是，启动一个 goroutine 时，向其传递了一个<strong>永远不会被取消</strong>的 context。</p>
<p><strong>错误模式</strong>：</p>
<pre><code class="go">// 传递一个 background context，等于没有传递任何“停止信号”
go doWork(context.Background())
</code></pre>
<p>这个 doWork goroutine，一旦启动，就没有任何机制可以通知它停止。如果它内部是一个 for-select 循环，它就会永远运行下去。</p>
<p><strong>正确的模式</strong>：</p>
<pre><code class="go">// 从父 context 创建一个可取消的 context
ctx, cancel := context.WithCancel(parentCtx)
// 确保在函数退出时，无论如何都会调用 cancel
defer cancel() 

go doWork(ctx)
</code></pre>
<p>没有 cancel，就没有清理 (No cancel -> no cleanup)。context 不会“魔法般地”自己取消。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-context-explained-pr.png" alt="" /></p>
<h2>“不是 Bug，是生命周期”——如何诊断与思考</h2>
<p>Abhishek 强调，我们习惯于称之为“泄漏”的许多问题，实际上并非 Go 语言的 Bug，而是我们自己设计的<strong>“生命周期 Bug”</strong>。</p>
<h3>诊断“三板斧”</h3>
<ol>
<li>
<p><strong>pprof (无可争议)</strong>：这是你的第一、也是最重要的工具。通过 import _ “net/http/pprof” 引入它，并重点关注：</p>
<ul>
<li>堆内存增长 (heap profile)</li>
<li>内存分配热点 (allocs profile)</li>
<li>goroutine 数量随时间的变化</li>
</ul>
</li>
<li>
<p><strong>Goroutine Dumps</strong>: 通过 curl http://localhost:6060/debug/pprof/goroutine?debug=2 获取所有 goroutine 的详细堆栈信息。如果 goroutine 的数量只增不减，你就找到了泄漏的“犯罪现场”。</p>
</li>
<li>
<p><strong>灵魂三问 (The Ownership Question)</strong>：在审查任何一段持有状态的代码时，问自己三个问题：</p>
<ul>
<li>谁拥有这段内存？(Who owns this memory?)</li>
<li>它应该在什么时候消亡？(When should it die?)</li>
<li>是什么引用，让它得以存活？(What reference keeps it alive?)</li>
</ul>
</li>
</ol>
<h3>那些我们不愿承认的“泄漏”</h3>
<ul>
<li>即发即忘的 goroutine</li>
<li>没有消费者的 channel</li>
<li>永不取消的 context</li>
<li>用作缓存却没有淘汰策略的 map</li>
<li>捕获了巨大对象的闭包</li>
<li>为每个请求启动的、永不退出的后台 worker</li>
</ul>
<h2>真正的教训 —— Go 奖励那些思考“责任”的工程师</h2>
<blockquote>
<p><strong>Go 并没有隐藏内存，它暴露了责任。</strong><br />
  <strong>GC 无法修复糟糕的所有权设计。</strong></p>
</blockquote>
<p>这是本篇最核心、也最深刻的结论。Go 的垃圾回收器，为你解决了“何时 free”的机械问题，但它将一个更高级、也更重要的责任，交还给了你——<strong>设计清晰的“所有权”和“生命周期”</strong>。</p>
<p>Goroutine 不会自动清理自己，Channel 不会自动排空自己，Context 不会自动取消自己。这些都不是语言的缺陷，而是其<strong>设计哲学</strong>的体现。</p>
<p><strong>Go 奖励那些能够思考以下问题的工程师：</strong></p>
<ul>
<li>生命周期 (Lifetimes)：这个 goroutine 应该在什么时候开始，什么时候结束？</li>
<li>所有权 (Ownership)：这份数据由谁创建，由谁负责，最终应该由谁来释放对其的最后一个引用？</li>
<li>反压 (Backpressure)：当消费者处理不过来时，生产者是否应该被阻塞？我的 channel 是否应该有界？</li>
</ul>
<p><strong>你不需要成为一名 Go 运行时专家</strong>，你只需要开始用“生命周期”的视角，去设计你的并发程序，并偶尔用 pprof 来验证你的设计。</p>
<p>这，就是修复 Go 内存问题“心智模型”的终极之道。</p>
<p>资料链接：https://x.com/0xlelouch_/status/2000485400884785320</p>
<hr />
<p><strong>你的“捉鬼”经历</strong></p>
<p>内存泄漏就像幽灵，看不见摸不着却真实存在。<strong>在你的 Go 开发生涯中，是否也曾遇到过让你抓狂的内存泄漏或 Goroutine 暴涨？最终你是如何定位并解决的？</strong></p>
<p><strong>欢迎在评论区分享你的“捉鬼”故事和独门排查技巧！</strong> 让我们一起守护服务的稳定性。</p>
<p><strong>如果这篇文章帮你修复了关于内存的心智模型，别忘了点个【赞】和【在看】，并转发给你的团队，让大家一起避坑！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/15/where-did-the-memory-go-gopher-unanswered-question/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 语言的“舒适区”：为何在这张“鄙视链”金字塔中，Go 仅次于 C？</title>
		<link>https://tonybai.com/2026/01/07/go-language-comfort-zone-in-contempt-chain-pyramid/</link>
		<comments>https://tonybai.com/2026/01/07/go-language-comfort-zone-in-contempt-chain-pyramid/#comments</comments>
		<pubDate>Wed, 07 Jan 2026 00:16:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Abomination]]></category>
		<category><![CDATA[asm]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CognitiveBurden]]></category>
		<category><![CDATA[ComfortZone]]></category>
		<category><![CDATA[ContemptChain]]></category>
		<category><![CDATA[DataOrientedDesign]]></category>
		<category><![CDATA[DOD]]></category>
		<category><![CDATA[Elixir]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[HTMX]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[lifetimes]]></category>
		<category><![CDATA[Lua]]></category>
		<category><![CDATA[MemeLanguages]]></category>
		<category><![CDATA[MemorySafety]]></category>
		<category><![CDATA[MentalModel]]></category>
		<category><![CDATA[metaprogramming]]></category>
		<category><![CDATA[Minimalism]]></category>
		<category><![CDATA[NecessaryEvil]]></category>
		<category><![CDATA[Nononsense]]></category>
		<category><![CDATA[Overengineering]]></category>
		<category><![CDATA[Pragmatism]]></category>
		<category><![CDATA[programminglanguage]]></category>
		<category><![CDATA[Pyramid]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[SoftwareEngineering]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[TotalFailure]]></category>
		<category><![CDATA[TypeScript]]></category>
		<category><![CDATA[WildPointer]]></category>
		<category><![CDATA[Zig]]></category>
		<category><![CDATA[元编程]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[实用主义]]></category>
		<category><![CDATA[心智模型]]></category>
		<category><![CDATA[拒绝废话]]></category>
		<category><![CDATA[掌控感]]></category>
		<category><![CDATA[极简主义]]></category>
		<category><![CDATA[现代化C]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[舒适区]]></category>
		<category><![CDATA[认知负担]]></category>
		<category><![CDATA[软件工程]]></category>
		<category><![CDATA[过度设计]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[鄙视链]]></category>
		<category><![CDATA[野指针]]></category>
		<category><![CDATA[金字塔]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5684</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/07/go-language-comfort-zone-in-contempt-chain-pyramid 大家好，我是Tony Bai。 最近，一张“编程语言分级图”在技术社区引发大家热议。它没有参考 TIOBE 排名，也不看 GitHub Star 数，而是完全基于一种简单粗暴的价值观：谁最不折腾人？ 在这张金字塔中，C 语言高居神坛（The one and only），而 Java、Python、C++ 被踩在最底层的“憎恶（Abomination）”泥潭里。甚至连备受推崇的 Rust，也被归入了“彻底失败（Total failure）”。 ** Go 语言则稳稳地站在了 T1 梯队——“No nonsense（拒绝废话）”。** 这张图看似偏激，却也道出了一些资深开发者的心声。它揭示了 Go 语言最大的魅力：在混沌的软件工程世界里，Go 为我们圈出了一块难得的“舒适区”。 鄙视链解构：极简主义者的“神曲” 这张图从上到下，宛如但丁的《神曲》，描绘了从天堂到地狱的编程世界观。meme图的作者显然是一位厌恶抽象、崇尚掌控机器、鄙视过度设计的硬核程序员。让我们逐层拆解： 塔尖：The one and only（唯一的真神） C。 C 是编程界的拉丁语。它直接映射硬件，没有隐藏的运行时，没有 GC。它是操作系统和驱动的基石，是所有软件的“第一推动力”。在极简主义眼中，只有 C 是纯粹的。 T1 梯队：No nonsense（拒绝废话 / 实干家） Go、OCaml（骆驼）、Lua、ASM（芯片/汇编）、Erlang（红色e）。 这一层是“干活”的语言。它们专注解决问题、务实、没有过度设计。 Go：带 GC 的 C，工业界的实干家。 Lua &#38; [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/go-language-comfort-zone-in-contempt-chain-pyramid-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/07/go-language-comfort-zone-in-contempt-chain-pyramid">本文永久链接</a> &#8211; https://tonybai.com/2026/01/07/go-language-comfort-zone-in-contempt-chain-pyramid</p>
<p>大家好，我是Tony Bai。</p>
<p>最近，一张“编程语言分级图”在技术社区引发大家热议。它没有参考 TIOBE 排名，也不看 GitHub Star 数，而是完全基于一种简单粗暴的价值观：<strong>谁最不折腾人？</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/go-language-comfort-zone-in-contempt-chain-pyramid-2.png" alt="" /></p>
<p>在这张金字塔中，C 语言高居神坛（The one and only），而 Java、Python、C++ 被踩在最底层的“憎恶（Abomination）”泥潭里。甚至连备受推崇的 Rust，也被归入了“彻底失败（Total failure）”。</p>
<p>** Go 语言则稳稳地站在了 T1 梯队——“No nonsense（拒绝废话）”。**</p>
<p>这张图看似偏激，却也道出了一些资深开发者的心声。它揭示了 Go 语言最大的魅力：在混沌的软件工程世界里，Go 为我们圈出了一块难得的<strong>“舒适区”</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/distributed-system-guide-qr.png" alt="img{512x368}" /></p>
<h2>鄙视链解构：极简主义者的“神曲”</h2>
<p>这张图从上到下，宛如但丁的《神曲》，描绘了从天堂到地狱的编程世界观。meme图的作者显然是一位厌恶抽象、崇尚掌控机器、鄙视过度设计的硬核程序员。让我们逐层拆解：</p>
<ol>
<li>
<p><strong>塔尖：The one and only（唯一的真神）</strong></p>
<ul>
<li><strong>C</strong>。</li>
<li>C 是编程界的拉丁语。它直接映射硬件，没有隐藏的运行时，没有 GC。它是操作系统和驱动的基石，是所有软件的“第一推动力”。在极简主义眼中，只有 C 是纯粹的。</li>
</ul>
</li>
<li>
<p><strong>T1 梯队：No nonsense（拒绝废话 / 实干家）</strong></p>
<ul>
<li><strong>Go</strong>、<strong>OCaml</strong>（骆驼）、<strong>Lua</strong>、<strong>ASM</strong>（芯片/汇编）、<strong>Erlang</strong>（红色e）。</li>
<li>这一层是“干活”的语言。它们专注解决问题、务实、没有过度设计。
<ul>
<li><strong>Go</strong>：带 GC 的 C，工业界的实干家。</li>
<li><strong>Lua &amp; ASM</strong>：极致的小巧与极致的控制。</li>
<li><strong>OCaml &amp; Erlang</strong>：虽然是函数式或特定领域，但以实用和高可靠性著称，不搞虚头巴脑的学术概念。</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>T2 梯队：Meme languages（网红/小众神教）</strong></p>
<ul>
<li><strong>Odin</strong>、<strong>Jai</strong>（绿色文字）、<strong>HolyC</strong>（黄色十字六边形）、<strong>Elixir</strong>（紫色水滴）、<a href="https://tonybai.com/2024/09/20/htmx-gopher-perfect-partner-for-full-stack"><strong>HTMX</strong></a>（激光眼马）。</li>
<li>我敢保证这一层的很多语言你都没有听过，我也是查了很久才对号入座，这也说明原meme图的作者在编程语言方面涉猎甚广。这一层的语言通常具有“网红”属性，或者带有强烈的“亚文化/宗教”色彩。它们在特定圈子（如独立游戏开发、TempleOS 粉丝）中声量巨大，但在主流工业界存在感稀薄。
<ul>
<li><strong>Odin &amp; Jai</strong>：这两者常被绑定提及，代表了“Handmade”社区（手工造轮子）的价值观。它们试图取代 C++ 用于游戏开发，强调面向数据设计（DOD）。Odin 虽好但小众，Jai 则因长期未公开发布而被调侃为“幻之语言”。</li>
<li><strong>HolyC</strong>：这是“上帝的程序员”Terry Davis 为 TempleOS 创造的语言，在技术宅圈子中是神一般的存在（Meme 之神），但几乎没有实际生产用途。</li>
<li><strong>Elixir &amp; HTMX</strong>：前者是 Erlang VM 上的“时髦文青”，后者是最近在推特上掀起“回归 HTML”运动的网红库。</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>T3 梯队：Necessary evil（必要之恶）</strong></p>
<ul>
<li><strong>JS</strong>、<strong>CSS</strong>、<strong>Bash</strong>、<strong>Swift</strong>、<strong>TeX</strong>、<strong>SQL</strong>。</li>
<li>你很讨厌它们，但你离不开它们。因为它们垄断了特定领域（浏览器、终端、苹果生态、论文排版、数据库）。你用它们不是因为爱，而是因为别无选择。</li>
</ul>
</li>
<li>
<p><strong>T4 梯队：Total failure（彻底失败 / 认知灾难）</strong></p>
<ul>
<li><strong>Haskell</strong>、<strong>Rust</strong>（齿轮）、<strong>Zig</strong>（橙色Z）、<strong>Scala</strong>、<strong>Racket</strong>、<strong>Kotlin</strong>。</li>
<li>这是最引战的一层。这里的“失败”指的不是技术失败，而是<strong>“在追求简单的道路上失败了”</strong>。
<ul>
<li><strong>Rust</strong>：为了内存安全或零开销抽象，引入了极其复杂的心智负担（生命周期、编译期计算）。作者认为让程序员当编译器的奴隶是一种失败。</li>
<li><strong>Zig</strong>：虽然标榜是 C 的继承者，但它要求显式管理所有资源（到处传递 Allocator），且引入了强大的 comptime 元编程。在作者看来，这并没有真正降低 C 的心智负担，反而换了一种方式折腾大脑，且至今仍未发布正式版（1.0）。</li>
<li><strong>Haskell &amp; Scala</strong>：学术概念堆砌，Monad 满天飞，导致代码难以阅读和维护。</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>底层：Abomination（憎恶 / 不可名状之物）</strong></p>
<ul>
<li><strong>C++</strong>、<strong>C#</strong>、<strong>Java</strong>、<strong>PHP</strong>、<strong>TS</strong>、<strong>Python</strong>、<strong>Ruby</strong>。</li>
<li>地狱最底层。它们犯了<strong>“过度设计”、“臃肿”、“慢”</strong>的原罪。
<ul>
<li><strong>C++</strong>：特性大杂烩，学习曲线陡峭。</li>
<li><strong>Java/C#</strong>：企业级官僚主义，层层叠叠的抽象工厂。</li>
<li><strong>Python/Ruby/PHP</strong>：解释执行慢，动态类型在大型工程中是维护灾难。</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2>神坛之下的第一人：Go 是“带了安全带的 C”</h2>
<p>在这张图中，C 是唯一的“神”。为什么？因为 C 诚实。它与机器直接对话，没有中间商赚差价。但 C 也是危险的，内存泄漏和野指针是每个 C 程序员的噩梦。</p>
<p><strong>Go 为什么紧随其后？</strong></p>
<p>因为 Go 完美地继承了 C 的“诚实”，同时补上了“安全”的短板。</p>
<p>在“No nonsense”这一层，Go 与 Lua（极简脚本）、ASM（汇编）并列。这说明在作者眼中，<strong>Go 的本质不是“简化的 Java”，而是“现代化的 C”。</strong></p>
<ul>
<li><strong>舒适在“透明”</strong>：看到一行 Go 代码，你基本能准确预估它的运行代价。没有隐式类型转换，没有构造函数里的黑魔法。代码写成什么样，逻辑就怎么跑。</li>
<li><strong>舒适在“克制”</strong>：Go 只有 25 个关键字。它拒绝了许多“看起来很酷”的特性（如三元运算符、复杂的元编程），只为了让你在读代码时，不需要在大脑里运行一个复杂的解析器。</li>
</ul>
<p>Go 处于这个位置，是因为它保留了 C 的<strong>掌控感</strong>，同时剔除了 C 的<strong>恐惧感</strong>（内存泄漏、野指针）。</p>
<h2>下层的窒息感：为何 Java 和 C++ 是“憎恶”？</h2>
<p>再往下看，最底层的“Abomination”包含了 C++、Java、Python 等工业界巨头。这并非说它们不能干活，而是说用它们干活<strong>“很不舒服”</strong>。</p>
<p>在这个“极简主义”的评价体系里，这些语言代表了<strong>“过度设计”</strong>的极端：</p>
<ul>
<li><strong>C++ 的认知负担</strong>：你想写个 Hello World，却迷失在模板元编程、右值引用和 20 种初始化方式的迷宫里。</li>
<li><strong>Java 的官僚主义</strong>：AbstractSingletonProxyFactoryBean……你写的不是代码，是填空题。层层叠叠的抽象，让代码与其运行的硬件彻底失联。</li>
</ul>
<p><strong>Go 的舒适区，建立在对这种“复杂性”的拒绝之上。</strong> 在 Go 里，你不需要画 UML 图，不需要背诵设计模式，你只需要关注：数据怎么流，逻辑怎么走。</p>
<h2>侧面的焦虑感：为何 Rust 是“彻底失败”？</h2>
<p>这是最引发争议的一点。Rust 被归为“Total failure”。这显然不是指 Rust 的技术失败，而是指它<strong>违背了“No nonsense”的初衷</strong>。</p>
<p>Rust 为了追求内存安全和零成本抽象，引入了极高的认知成本（生命周期、借用检查）。这导致写 Rust 代码时，开发者往往在与编译器搏斗，而不是在解决业务问题。</p>
<p><strong>Go 的舒适，是一种“妥协的艺术”。</strong></p>
<p>Go 承认：与其让人脑去计算每一个变量的生命周期（Rust 的做法），不如让 CPU 多跑几毫秒来做 GC（Go 的做法）。</p>
<p>在这个算力过剩而人脑算力稀缺的时代，Go 选择了<strong>让人舒服</strong>，而不是让机器舒服。</p>
<h2>小结：拒绝废话，回归本质</h2>
<p>这张图之所以能引起共鸣，是因为它精准地击中了现代软件工程的痛点：<strong>我们花了太多时间在对付语言特性、框架和工具链，却忘了我们最初只是想写程序解决问题。</strong></p>
<p>Go 语言处于 <strong>No nonsense</strong> 这一层，恰恰证明了它的核心价值：</p>
<p>它不追求“纯粹”的完美（像 Haskell），也不追求“极致”的性能（像 Rust），更不追求“大而全”的框架（像 Java）。</p>
<p><strong>Go 只是想让你舒服地、直白地、没有废话地，把代码写出来，然后按时下班。</strong></p>
<p>在当今这个充满焦虑的技术世界里，这难道不是最顶级的“舒适区”吗？^_^</p>
<hr />
<p><strong>你的“鄙视链”排位</strong></p>
<p>这张图虽然偏激，但确实代表了一些人心中的极简主义的审美。<strong>在你心中的编程语言金字塔里，谁是那个“唯一的真神”？谁又是让你痛苦不堪的“不可名状之物”？你认同把 Rust 放在“彻底失败”这一层吗？</strong></p>
<p><strong>欢迎在评论区晒出你的“私房排位表”，或者为你的本命语言辩护！</strong> (请文明交流，勿伤和气~ )</p>
<p><strong>如果这篇文章戳中了你的笑点或痛点，别忘了点个【赞】和【在看】，看看你的朋友圈里有多少“极简主义者”！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/07/go-language-comfort-zone-in-contempt-chain-pyramid/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>高并发后端：坚守 Go，还是拥抱 Rust？</title>
		<link>https://tonybai.com/2025/12/30/high-concurrency-backend-go-vs-rust/</link>
		<comments>https://tonybai.com/2025/12/30/high-concurrency-backend-go-vs-rust/#comments</comments>
		<pubDate>Tue, 30 Dec 2025 00:12:10 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BorrowChecker]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[cloudnative]]></category>
		<category><![CDATA[ControlPlane]]></category>
		<category><![CDATA[DataPlane]]></category>
		<category><![CDATA[EngineeringEfficiency]]></category>
		<category><![CDATA[FastDelivery]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[HighConcurrencyBackend]]></category>
		<category><![CDATA[LatencySensitive]]></category>
		<category><![CDATA[LearningCurve]]></category>
		<category><![CDATA[MemorySafety]]></category>
		<category><![CDATA[MixedArchitecture]]></category>
		<category><![CDATA[P99]]></category>
		<category><![CDATA[Productivity]]></category>
		<category><![CDATA[QPS]]></category>
		<category><![CDATA[ResourceControl]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[SystemComplexity]]></category>
		<category><![CDATA[TeamCollaboration]]></category>
		<category><![CDATA[Tradeoffs]]></category>
		<category><![CDATA[云原生]]></category>
		<category><![CDATA[借用检查器]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[团队协作]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[学习曲线]]></category>
		<category><![CDATA[工程效率]]></category>
		<category><![CDATA[工程权衡]]></category>
		<category><![CDATA[延迟敏感]]></category>
		<category><![CDATA[快速交付]]></category>
		<category><![CDATA[控制面]]></category>
		<category><![CDATA[数据面]]></category>
		<category><![CDATA[混合架构]]></category>
		<category><![CDATA[生产力]]></category>
		<category><![CDATA[系统复杂度]]></category>
		<category><![CDATA[资源控制]]></category>
		<category><![CDATA[高并发后端]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5624</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/12/30/high-concurrency-backend-go-vs-rust 大家好，我是Tony Bai。 在高并发后端开发领域，Go 语言曾是当之无愧的“默认选项”。然而，随着 Rust 生态的成熟和性能神话的普及，越来越多的架构师开始动摇：是继续坚守 Go 的高效与简洁，还是拥抱 Rust 的极致性能与零成本抽象？ 近日，r/golang 社区的一场热议将这一抉择摆上了台面。这不仅是语言之争，更是关于工程效率、系统复杂度与团队协作的深度博弈。本文将基于这场高质量的社区讨论，为你梳理出理性决策的核心逻辑。 坚守 Go 的理由——“早点下班”的生产力 在讨论中，尽管 Rust 呼声很高，但支持坚守 Go 的声音依然占据了工程实践的主流。理由惊人地一致：生产力 (Productivity)。 “可用的软件 > 早期的优化” 一位Reddit 用户 的高赞回答道出了软件工程的真谛：“使用让你高效的工具。可用的软件 > 早期的优化。” 对于绝大多数后端业务来说，瓶颈往往在于数据库、网络 I/O 或者架构设计，而不是语言本身的 CPU 执行效率。Go 语言的设计初衷就是为了解决谷歌规模的软件工程问题——快速编译、快速部署、易于阅读、易于维护。选择 Go，意味着选择了更快的交付速度。 “足够好”的并发性能 Go 的 goroutine 和 channel 使得并发编程变得前所未有的简单。正如一位用户所言：“Go 依然是处理高并发请求的王者，因为它简单、易于测试、易于优化。” 在 99% 的场景下（例如 QPS &#60; 100k），Go 的性能已经绰绰有余。为了追求 Rust [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/high-concurrency-backend-go-vs-rust-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/12/30/high-concurrency-backend-go-vs-rust">本文永久链接</a> &#8211; https://tonybai.com/2025/12/30/high-concurrency-backend-go-vs-rust</p>
<p>大家好，我是Tony Bai。</p>
<p>在高并发后端开发领域，Go 语言曾是当之无愧的“默认选项”。然而，随着 Rust 生态的成熟和性能神话的普及，越来越多的架构师开始动摇：是继续坚守 Go 的高效与简洁，还是拥抱 Rust 的极致性能与零成本抽象？</p>
<p>近日，<a href="https://www.reddit.com/r/golang/comments/1pi3914/is_go_still_the_best_choice_for_highconcurrency/">r/golang 社区的一场热议</a>将这一抉择摆上了台面。这不仅是语言之争，更是关于<strong>工程效率、系统复杂度与团队协作</strong>的深度博弈。本文将基于这场高质量的社区讨论，为你梳理出理性决策的核心逻辑。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/inside-goroutine-scheduler-qr.png" alt="img{512x368}" /></p>
<h2>坚守 Go 的理由——“早点下班”的生产力</h2>
<p>在讨论中，尽管 Rust 呼声很高，但支持坚守 Go 的声音依然占据了工程实践的主流。理由惊人地一致：<strong>生产力 (Productivity)</strong>。</p>
<h3>“可用的软件 > 早期的优化”</h3>
<p>一位Reddit 用户 的高赞回答道出了软件工程的真谛：“<strong>使用让你高效的工具。可用的软件 > 早期的优化。</strong>”</p>
<p>对于绝大多数后端业务来说，瓶颈往往在于数据库、网络 I/O 或者架构设计，而不是语言本身的 CPU 执行效率。Go 语言的设计初衷就是为了解决谷歌规模的软件工程问题——<strong>快速编译、快速部署、易于阅读、易于维护</strong>。选择 Go，意味着选择了更快的交付速度。</p>
<h3>“足够好”的并发性能</h3>
<p>Go 的 goroutine 和 channel 使得并发编程变得前所未有的简单。正如一位用户所言：“Go 依然是处理高并发请求的王者，因为它简单、易于测试、易于优化。”</p>
<p>在 99% 的场景下（例如 QPS &lt; 100k），Go 的性能已经<strong>绰绰有余</strong>。为了追求 Rust 那最后 5% 的性能提升，而牺牲 50% 的开发效率，对于大多数追求商业闭环的项目来说，是一笔亏本买卖。</p>
<h3>人才与生态的护城河</h3>
<p>“如果你不是在造火箭，Go 是大多数公司的最佳选择。” Go 拥有庞大且成熟的云原生生态系统（Docker, K8s, Etcd&#8230;），以及大量(相对于Rust)容易招聘的工程师。相比之下，Rust 的学习曲线陡峭，人才库相对较小，且招聘与薪资成本更高。</p>
<h2>拥抱 Rust 的动力——当“每一字节”都至关重要</h2>
<p>当然，Rust 的崛起并非空穴来风。社区也客观地分析了拥抱 Rust 的必要场景——那些 <strong>Go 力不从心</strong> 的极端领域。</p>
<h3>极致的资源控制</h3>
<p>当你的应用对<strong>延迟极其敏感</strong>（P99 要求极高），或者需要处理<strong>海量数据</strong>且对内存占用有严格要求时（例如高频交易、嵌入式系统、数据库内核），Go 的 GC (Garbage Collection) 带来的停顿就成了无法忽视的痛点。此时，Rust 的无 GC 特性就成为了杀手锏。</p>
<p>一位用户指出：“当 QPS 超过 100k，或者你需要榨干硬件的每一个周期时，Go 的 GC 可能会成为瓶颈，这时 Rust（或 C++）才是更好的选择。”</p>
<h3>“编译期正确”的安全性</h3>
<p>Rust 的借用检查器虽然让初学者头疼，但它在编译期就消灭了数据竞争和内存安全问题。对于那些<strong>绝对不能崩溃</strong>的关键基础设施（如数据平面代理），Rust 提供了比 Go 更强的安全保证。拥抱 Rust，意味着用编译时的痛苦换取运行时的安心。</p>
<h2>工程视角的理性决策</h2>
<p>这场讨论最终回归到了<strong>工程权衡 (Trade-offs)</strong> 上。我们不应在真空中做选择，而应根据业务场景裁决：</p>
<ul>
<li><strong>业务开发</strong>：<strong>坚守 Go</strong>。CRUD、微服务、Web API……Go 写起来快，改起来也快，心智负担低，是构建业务逻辑的首选。</li>
<li><strong>基础设施</strong>：<strong>分层选择</strong>。Go 依然是控制面（Control Plane）的主流（看看 K8s），但在更底层的数据平面（Data Plane，如 Envoy, Linkerd 的代理部分），拥抱 Rust 正在成为趋势。</li>
<li><strong>混合架构</strong>：一种越来越流行的模式是——<strong>用 Go 写控制面和业务逻辑，用 Rust 写核心的高性能组件</strong>。正如一位用户所分享：“我用 Rust 写内核模块和 IO 密集型组件，用 Go 写扩展性后端和 OLAP 管道。”</li>
</ul>
<h2>小结：服务于目标的决策</h2>
<p>高并发后端的选择，本质上不是非黑即白的站队，而是对项目目标的精准匹配。</p>
<ul>
<li>如果你追求<strong>快速交付</strong>、<strong>易于维护</strong>、<strong>团队协作顺畅</strong>，Go 依然是后端开发的<strong>默认选项</strong>。</li>
<li>如果你遇到了<strong>极端的性能瓶颈</strong>，或者需要<strong>极致的内存安全</strong>，那么 Rust 是你强大的<strong>特种武器</strong>。</li>
</ul>
<p>不要为了技术而技术。正如一位智者所言：“<strong>Done is better than perfect.</strong>” (完成比完美更重要)。在你的产品还没遇到 Go 的性能瓶颈之前，先用 Go 把它做出来吧！</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1pi3914/is_go_still_the_best_choice_for_highconcurrency</p>
<hr />
<p><strong>你的选择是？</strong></p>
<p>在这场“生产力”与“极致性能”的博弈中，<strong>你的团队选择了哪条路？</strong> 是坚守 Go 的高效交付，还是为了 5% 的性能提升而转向 Rust？又或者，你们已经开始了“混合架构”的尝试？</p>
<p><strong>欢迎在评论区分享你的选型逻辑和实战经验！</strong> 让我们一起看看大家都在怎么选。</p>
<p><strong>如果这篇文章帮你在技术选型上理清了思路，别忘了点个【赞】和【在看】，并转发给还在纠结的架构师朋友！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/12/30/high-concurrency-backend-go-vs-rust/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Go 安全新提案：runtime/secret 能否终结密钥残留的噩梦？</title>
		<link>https://tonybai.com/2025/12/05/proposal-runtime-secret/</link>
		<comments>https://tonybai.com/2025/12/05/proposal-runtime-secret/#comments</comments>
		<pubDate>Fri, 05 Dec 2025 07:55:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[CompilerOptimization]]></category>
		<category><![CDATA[DanielMorsing]]></category>
		<category><![CDATA[explicit_bzero]]></category>
		<category><![CDATA[ForwardSecrecy]]></category>
		<category><![CDATA[GarbageCollection]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.26]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[JasonADonenfeld]]></category>
		<category><![CDATA[KeyResidual]]></category>
		<category><![CDATA[memguard]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[runtime/secret]]></category>
		<category><![CDATA[SafeSandbox]]></category>
		<category><![CDATA[secret.Do]]></category>
		<category><![CDATA[SensitiveData]]></category>
		<category><![CDATA[ShadowStack]]></category>
		<category><![CDATA[SignalProcessing]]></category>
		<category><![CDATA[StackCopying]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[WireGuard]]></category>
		<category><![CDATA[zx2c4]]></category>
		<category><![CDATA[信号处理]]></category>
		<category><![CDATA[内存擦除]]></category>
		<category><![CDATA[前向保密]]></category>
		<category><![CDATA[动态作用域]]></category>
		<category><![CDATA[反射]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[堆栈复制]]></category>
		<category><![CDATA[安全沙箱]]></category>
		<category><![CDATA[密钥残留]]></category>
		<category><![CDATA[影子栈]]></category>
		<category><![CDATA[提案]]></category>
		<category><![CDATA[敏感数据]]></category>
		<category><![CDATA[编译器优化]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5483</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/12/05/proposal-runtime-secret 大家好，我是Tony Bai。 “如果你的服务器被攻破，攻击者能否拿到内存中残留的私钥，进而解密过去两年的所有通信记录？” 这是一个让所有安全工程师夜不能寐的问题。为了防止这种情况，现代加密协议（如 TLS 1.3, WireGuard）都强调前向保密 (Forward Secrecy)：使用临时的、一次性的密钥，并在使用后立即销毁。 然而，在 Go 语言中，“立即销毁”这个看似简单的动作，却是一个巨大的技术难题。由于垃圾回收 (GC)、堆栈复制、以及缺乏对内存的底层控制，Go 程序很难保证敏感数据被彻底擦除。 针对这一痛点，Go 社区大神 Jason A. Donenfeld(WireGuard作者，ID: zx2c4) 发起了一项长达数年的提案——引入 runtime/secret 包。近日，该提案已进入实现阶段，有望在Go 1.26版本中落地，并彻底改变 Go 处理敏感数据的方式。 核心痛点：为什么 memset(0) 在 Go 中不够用？ 在 C 语言中，我们可以调用 explicit_bzero 来擦除内存。但在 Go 中，情况要复杂得多： 隐式拷贝：Go 的切片操作、函数传参、甚至简单的赋值，都可能在堆或栈上留下数据的副本。你擦除了一份，却可能漏掉了其他三份。 GC 的不确定性：垃圾回收器何时运行？被回收的内存是否会被立即归零？这些都是未知的。 堆栈扩容：当 goroutine 栈空间不足时，Go 运行时会分配一个更大的新栈，并将旧栈的数据拷贝过去。旧栈中的敏感数据就此残留，且不再被追踪。 编译器优化：简单的“写入零值”操作可能会被编译器视为“死代码”而优化掉。 正如 WireGuard 的 Go 实现中遇到的尴尬局面：为了擦除一个 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/proposal-runtime-secret-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/12/05/proposal-runtime-secret">本文永久链接</a> &#8211; https://tonybai.com/2025/12/05/proposal-runtime-secret</p>
<p>大家好，我是Tony Bai。</p>
<p>“如果你的服务器被攻破，攻击者能否拿到内存中残留的私钥，进而解密过去两年的所有通信记录？”</p>
<p>这是一个让所有安全工程师夜不能寐的问题。为了防止这种情况，现代加密协议（如 TLS 1.3, WireGuard）都强调<strong>前向保密 (Forward Secrecy)</strong>：使用临时的、一次性的密钥，并在使用后立即销毁。</p>
<p>然而，在 Go 语言中，“立即销毁”这个看似简单的动作，却是一个巨大的技术难题。由于垃圾回收 (GC)、堆栈复制、以及缺乏对内存的底层控制，Go 程序很难保证敏感数据被彻底擦除。</p>
<p>针对这一痛点，Go 社区大神 <a href="https://github.com/zx2c4">Jason A. Donenfeld(WireGuard作者，ID: zx2c4)</a> 发起了<a href="https://github.com/golang/go/issues/21865">一项长达数年的提案</a>——<strong>引入 runtime/secret 包</strong>。近日，该提案已进入实现阶段，有望在Go 1.26版本中落地，并彻底改变 Go 处理敏感数据的方式。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-crypto-101-qr.png" alt="img{512x368}" /></p>
<h2>核心痛点：为什么 memset(0) 在 Go 中不够用？</h2>
<p>在 C 语言中，我们可以调用 explicit_bzero 来擦除内存。但在 Go 中，情况要复杂得多：</p>
<ol>
<li><strong>隐式拷贝</strong>：Go 的切片操作、函数传参、甚至简单的赋值，都可能在堆或栈上留下数据的副本。你擦除了一份，却可能漏掉了其他三份。</li>
<li><strong>GC 的不确定性</strong>：垃圾回收器何时运行？被回收的内存是否会被立即归零？这些都是未知的。</li>
<li><strong>堆栈扩容</strong>：当 goroutine 栈空间不足时，Go 运行时会分配一个更大的新栈，并将旧栈的数据拷贝过去。旧栈中的敏感数据就此残留，且不再被追踪。</li>
<li><strong>编译器优化</strong>：简单的“写入零值”操作可能会被编译器视为“死代码”而优化掉。</li>
</ol>
<p>正如 <a href="https://tonybai.com/2020/03/29/hello-wireguard">WireGuard</a> 的 Go 实现中遇到的尴尬局面：为了擦除一个 AEAD 对象中的密钥，开发者不得不使用反射 (Reflection) 这种“旁门左道”来重置其内部字段，既不优雅也不可靠。</p>
<h2>提案及演进：从 SetZeroOnGC 到 secret.Do</h2>
<p>这项提案的讨论过程，简直是一部 Go 运行时机制的“解剖学教程”。</p>
<h3>早期尝试：SetZeroOnGC</h3>
<p>最初的设想是让用户标记某个对象，告诉 GC 在回收它时必须将其内存归零。</p>
<p>但这无法解决栈上数据的残留问题，也无法处理那些在函数调用过程中产生的临时副本。</p>
<h3>中期探索：自定义分配器与 SetFinalizer</h3>
<p>有人提议使用 <a href="https://github.com/awnumar/memguard">memguard</a> 等库，通过 mmap 分配不受 GC 管理的内存。</p>
<p>但这需要重写所有加密库的 API，使其接受自定义分配器，工程量巨大且不兼容现有生态。</p>
<h3>最终方案：runtime/secret 包</h3>
<p>经过反复权衡，Go 团队和社区最终汇聚到了一个基于<strong>动态作用域</strong>的解决方案上。提案的核心 API 极其简洁：</p>
<pre><code class="go">package secret

// Do 执行函数 f。
// 当 secret.Do 返回时：
//   - 清除函数 f 执行期间创建的所有栈帧（stack frames）。
//   - 清除所有可能包含secret的寄存器。
//   - 在secret模式下栈增长时，清除旧的栈。
//   - secret模式下，在 f 执行期间分配的所有堆对象，会被标记为“敏感”，并在 GC 回收时被安全擦除。
//   - 如果函数出现panic，则将该panic提升为来自 secret.Do 的异常。这会从回溯中移除有关secret函数的任何信息。

func Do(f func())
</code></pre>
<p>这个设计不仅解决了堆内存的问题，更关键的是，它提供了一个<strong>“安全沙箱”</strong>。在这个沙箱内，你可以放心地进行加密计算，Go 运行时会负责清理你在栈上留下的所有痕迹。</p>
<h2>使用场景：WireGuard 与 TLS</h2>
<p>想象一下 WireGuard 的握手过程：</p>
<pre><code class="go">func handleHandshake() {
    secret.Do(func() {
        // 1. 生成临时私钥 (在栈上或堆上)
        ephemeralPrivateKey := generateKey()

        // 2. 计算共享密钥 (产生大量中间计算结果)
        sharedKey := computeSharedKey(ephemeralPrivateKey, peerPublicKey)

        // 3. 使用共享密钥进行加密操作
        // ...

        // 函数返回时：
        // - ephemeralPrivateKey 所在的栈帧被立即擦除
        // - sharedKey 等堆对象被标记，GC 回收时自动擦除
    })
}
</code></pre>
<p>开发者不需要手动追踪每一个变量，也不需要担心 copy 操作泄露数据。只要在 secret.Do 的闭包内，一切都是安全的。</p>
<h2>深水区的挑战：信号、GC 与汇编</h2>
<p>虽然 API 设计看似完美，但实现起来却是困难重重。今年的最新讨论揭示了几个令人头秃的底层挑战：</p>
<ol>
<li>
<p><strong>信号处理 (Signals)</strong>：如果程序在 secret.Do 执行期间收到系统信号，CPU 寄存器中的敏感数据会被操作系统保存到“信号栈”中。这相当于泄露了数据！</p>
</li>
<li>
<p><strong>垃圾回收器 (GC)</strong>：GC 在扫描内存时，可能会将敏感指针加载到自己的寄存器或栈中。如何确保 GC 线程本身不泄露数据？这是一个极其棘手的工程问题。</p>
</li>
<li>
<p><strong>汇编代码</strong>：Go 的加密库大量使用了汇编优化。如何确保这些汇编代码在使用完寄存器后正确地将其清零？</p>
</li>
</ol>
<p>当然，目前该提案的开发者 Daniel Morsing 已经逐个克服了上述挑战，比如针对信号处理的问题，他提出了一种巧妙的“影子栈”方案，试图在信号处理返回前拦截并擦除这些数据。Daniel Morsing针对该提案的<a href="https://go-review.googlesource.com/c/go/+/704615">cl 704615</a> 近期已经被merge，有望在Go 1.26落地。</p>
<p>不过目前，该secret包仅在linux for arm64 and amd64上有实现。</p>
<h2>小结：安全是场持久战</h2>
<p>runtime/secret 提案的推进，标志着 Go 语言在系统级安全领域迈出了重要一步。它不仅回应了高安全等级应用（如金融、国防）的需求，也体现了 Go 团队在面对复杂底层问题时的务实与坚持。</p>
<p>虽然已经被merge，但历史经验告诉我们，距离该功能成熟可能还有一段路要走，后续仍在会有一些问题和实现细节需要解决，但它所传达的信号是明确的：<strong>Go 正在成为编写安全基础设施的首选语言之一。</strong></p>
<p>对于我们普通开发者而言，虽然我们未必会在业务代码中直接 import 这个包(runtime/secret)，但关注这个提案的进展，不仅能让我们见证 Go 语言如何填补安全拼图中至关重要的一角，更能让我们在“围观”其解决信号处理、GC 交互等硬核挑战的过程中，完成一次对 Go 运行时底层机制的深度认知升级。当这一基础设施最终就位时，我们将能以更强的信心，站在更坚固的安全基石之上构建应用。</p>
<p>资料链接：https://github.com/golang/go/issues/21865</p>
<hr />
<p><strong>聊聊你眼中的 Go 安全基石</strong></p>
<p>runtime/secret 提案的推进，为 Go 在高安全等级场景的应用补上了一块关键的拼图。<strong>你在日常的 Go 开发中，是否也曾为如何安全地处理密钥、Token 等敏感数据而感到困扰？除了内存残留问题，你认为 Go 在安全方面还有哪些亟待完善的“深水区”？</strong></p>
<p>或者，你对 secret.Do 这种通过“安全沙箱”来解决问题的方式有何看法？它是否是你心中理想的解决方案？</p>
<p><strong>欢迎在评论区分享你的实战经验、安全痛点，或对 Go 语言安全生态的任何期待与建议！</strong> 让我们一起探讨，共同构建一个更安全的 Go 世界。</p>
<p><strong>如果这篇文章让你对 Go 的底层安全有了新的认识，别忘了点个【赞】和【在看】，并分享给更多关注 Go 安全的同伴！</strong></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p><strong>想系统学习Go，构建扎实的知识体系？</strong></p>
<p>我的新书《<a href="https://book.douban.com/subject/37499496/">Go语言第一课</a>》是你的首选。源自2.4万人好评的极客时间专栏，内容全面升级，同步至Go 1.24。首发期有专属五折优惠，不到40元即可入手，扫码即可拥有这本300页的Go语言入门宝典，即刻开启你的Go语言高效学习之旅！</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/go-primer-published-4.png" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/12/05/proposal-runtime-secret/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Brad Fitzpatrick 也等不及了！sync.Map 的泛型进化与 sync/v2 的诞生之路</title>
		<link>https://tonybai.com/2025/12/01/proposal-sync-v2/</link>
		<comments>https://tonybai.com/2025/12/01/proposal-sync-v2/#comments</comments>
		<pubDate>Mon, 01 Dec 2025 00:42:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[any]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Boxing]]></category>
		<category><![CDATA[BradFitzpatrick]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[IanLanceTaylor]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[iter.Seq2]]></category>
		<category><![CDATA[Iterator]]></category>
		<category><![CDATA[Mutex]]></category>
		<category><![CDATA[Once]]></category>
		<category><![CDATA[standardlibrary]]></category>
		<category><![CDATA[sync.Map]]></category>
		<category><![CDATA[sync.Pool]]></category>
		<category><![CDATA[sync/v2]]></category>
		<category><![CDATA[typealias]]></category>
		<category><![CDATA[TypeSafety]]></category>
		<category><![CDATA[waitgroup]]></category>
		<category><![CDATA[并发编程]]></category>
		<category><![CDATA[性能]]></category>
		<category><![CDATA[接口]]></category>
		<category><![CDATA[提案]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[核心团队]]></category>
		<category><![CDATA[泛型]]></category>
		<category><![CDATA[生态分裂]]></category>
		<category><![CDATA[类型别名]]></category>
		<category><![CDATA[类型安全]]></category>
		<category><![CDATA[装箱]]></category>
		<category><![CDATA[迁移]]></category>
		<category><![CDATA[迭代器]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5461</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/12/01/proposal-sync-v2 大家好，我是Tony Bai。 当 Go 核心团队前成员、著名 Gopher、net/http包的设计者 Brad Fitzpatrick 在 GitHub 上留下上图中的这句评论并甩出一个自己移植的库时，我们知道，sync/v2 的到来不仅仅是一个提案，更是一种迫切的刚需。 随着 math/rand/v2 在 Go 1.22, json/v2 在 Go 1.25 中的成功落地，Go 标准库的 v2 化进程似乎已经按下了加速键。今年1月份，Go 核心团队成员 Ian Lance Taylor 就提交了sync/v2 的提案 (#71076)。 这可不仅仅是一次简单的版本号升级，它标志着 Go 语言最核心的并发原语包，也终于要拥抱泛型，告别 interface{} 时代了。 在本文中，我们将深入剖析这份提案的核心内容，探讨它将如何重塑 Go 的并发编程体验，以及社区为此展开的激烈辩论。 核心痛点：any 的原罪 目前的 sync 包，特别是 sync.Map 和 sync.Pool，设计于 Go 支持泛型之前。它们被迫使用 any (即 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/proposal-sync-v2-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/12/01/proposal-sync-v2">本文永久链接</a> &#8211; https://tonybai.com/2025/12/01/proposal-sync-v2</p>
<p>大家好，我是Tony Bai。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/proposal-sync-v2-2.png" alt="" /></p>
<p>当 Go 核心团队前成员、著名 Gopher、net/http包的设计者 Brad Fitzpatrick 在 GitHub 上留下上图中的这句评论并甩出一个自己移植的库时，我们知道，sync/v2 的到来不仅仅是一个提案，更是一种迫切的刚需。</p>
<p>随着 math/rand/v2 在 <a href="https://tonybai.com/2024/02/18/some-changes-in-go-1-22">Go 1.22</a>, <a href="https://tonybai.com/2025/05/15/go-json-v2/">json/v2</a> 在 <a href="https://tonybai.com/2025/08/15/some-changes-in-go-1-25">Go 1.25</a> 中的成功落地，Go 标准库的 v2 化进程似乎已经按下了加速键。今年1月份，Go 核心团队成员 Ian Lance Taylor 就提交了<a href="https://github.com/golang/go/issues/71076"><strong>sync/v2</strong> 的提案 (#71076)</a>。</p>
<p>这可不仅仅是一次简单的版本号升级，它标志着 Go 语言最核心的并发原语包，也终于要拥抱泛型，告别 interface{} 时代了。</p>
<p>在本文中，我们将深入剖析这份提案的核心内容，探讨它将如何重塑 Go 的并发编程体验，以及社区为此展开的激烈辩论。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/google-adk-in-action-qr.png" alt="" /></p>
<h2>核心痛点：any 的原罪</h2>
<p>目前的 sync 包，特别是 sync.Map 和 sync.Pool，设计于 Go 支持泛型之前。它们被迫使用 any (即 interface{}) 来处理各种类型的数据。这带来了两个无法忽视的问题：</p>
<ol>
<li><strong>类型安全缺失</strong>：编译器无法阻止你往一个本该只存字符串的 sync.Map 里塞进一个整数，或者从 sync.Pool 里取出一个你以为是 []byte 实际上是 *bytes.Buffer 的东西。所有的错误只能在运行时通过 panic 暴露。</li>
<li><strong>性能损耗</strong>：将非指针类型（如 int、string）存入 any 类型的容器，必须进行装箱（boxing），这不仅增加了 CPU 开销，更重要的是会产生额外的内存分配，加重 GC 负担。对于追求极致性能的并发场景，这是不可接受的。</li>
</ol>
<p>sync/v2 的提案，就是要通过泛型彻底解决这些问题。</p>
<h2>sync/v2 的新面貌：类型安全与 API 进化</h2>
<p>根据提案，sync/v2 将不仅是 sync 的泛型翻版，它还趁机对 API 进行了现代化的打磨。</p>
<h3>Map[K, V]：终于等到了你</h3>
<p>新的 sync.Map 将拥有两个类型参数 K (comparable) 和 V (any)。</p>
<pre><code class="go">// sync/v2
type Map[K comparable, V any] struct { ... }

// 方法签名变得清晰且类型安全
func (m *Map[K, V]) Load(key K) (value V, ok bool)
func (m *Map[K, V]) Store(key K, value V)
</code></pre>
<p>此外，提案还计划顺应时代潮流，移除了老旧的 Range 方法，取而代之的是返回迭代器的 All 方法：</p>
<pre><code class="go">func (m *Map[K, V]) All() iter.Seq2[K, V]
</code></pre>
<h3>Pool[T]：更安全的资源复用</h3>
<p>sync.Pool 的改造稍微复杂一些。目前的 Pool 有一个导出的 New 字段，这很容易被误用。v2 版的提案曾经历过一次修改，最终方案倾向于移除导出的 New 字段，转而通过构造函数来设定：</p>
<pre><code class="go">type Pool[T any] struct { ... }

// 通过构造函数传入创建新对象的逻辑
func NewPool[T any](newf func() T) *Pool[T]

func (p *Pool[T]) Get() T
func (p *Pool[T]) Put(x T)
</code></pre>
<h2>社区的激辩：v2 真的必要吗？</h2>
<p>提案虽然诱人，但也引发了社区关于 Go 语言演进哲学的激烈讨论。</p>
<p><strong>反方：分裂生态的担忧</strong></p>
<p>有声音质疑：sync 包的大部分类型（如 Mutex, WaitGroup, Once）并不需要泛型。如果为了 Map 和 Pool 而引入整个 sync/v2，会不会导致生态分裂？以后我们是不是要在同一个项目里同时维护 v1 和 v2 的锁？</p>
<p>对此，Ian Lance Taylor 及其支持者给出的方案是：sync/v2 将包含 sync 包的所有类型。对于不需要泛型的类型（如 Mutex），通过<strong>类型别名 (Type Alias)</strong> 将其指向 v1 版本，或者保持 API 完全一致。这样，用户可以平滑迁移，最终完全切换到 v2，而无需混用。</p>
<p><strong>正方：性能与体验的刚需</strong></p>
<p>支持者们（包括 Brad Fitzpatrick）则指出，泛型带来的性能提升和开发体验改善是巨大的。特别是对于 Pool[[]byte] 这样的高频场景，避免每次 Put/Get 时的切片头分配，是实打实的性能红利。</p>
<h2>小结：不仅是代码的升级，更是理念的升级</h2>
<p>sync/v2 的提案目前仍在活跃讨论中，尚未尘埃落定。但它释放了一个明确的信号：<strong>Go 团队正在审慎而坚定地推动标准库的现代化</strong>。</p>
<p>对于我们 Gopher 而言，这意味着：</p>
<ol>
<li><strong>拥抱泛型</strong>：这不再是尝鲜，而很可能是未来的标准范式。</li>
<li><strong>关注性能</strong>：标准库的升级将带来免费的性能提升，特别是对于重度依赖 sync.Map 和 sync.Pool 的项目。</li>
<li><strong>准备迁移</strong>：虽然 Go 承诺兼容性，但 v2 包的引入意味着我们需要开始思考如何优雅地过渡。</li>
</ol>
<p>Brad Fitzpatrick 的“等不及”或许代表了许多资深开发者的心声。让我们拭目以待，看 sync/v2 将如何重塑 Go 的并发编程体验。</p>
<hr />
<p><strong>你的选择是？</strong></p>
<p>面对 sync/v2 带来的泛型红利和潜在的迁移成本，你更倾向于第一时间拥抱它，还是持观望态度？在你的项目中，sync.Map 或 sync.Pool 的性能瓶颈是否真的困扰过你？</p>
<p>欢迎在评论区留下你的看法，让我们一起探讨 Go 标准库的未来！ </p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/12/01/proposal-sync-v2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
