Go 实验特性详解
本机暂存
<p>Go 在发布新版本时经常会附带<strong>实验性特性(experimental features)</strong>。</p><p>这些实验性特性有不同的形式:有时是标准库中全新的包,有时是编译器或运行时的改动,偶尔也可能是对 Go 行为的破坏性变更。</p><p>大多数情况下,实验性特性的目的是在某个功能正式进入 <strong>通用可用(general availability)</strong> 阶段、成为 Go 的永久组成部分之前,从用户那里获取真实世界的反馈。如果该特性导致性能退化,或收到社区的负面反馈,它可以在最终定稿前被修改——甚至被完全放弃。</p><span id="more"></span><h2 id="一些示例"><a href="#一些示例" class="headerlink" title="一些示例"></a>一些示例</h2><p>让我们看几个近期的例子,来说明 Go 实验特性可能涉及的内容类型。</p><ul><li><p>Go 1.24 发布时附带了新的 <code>testing/synctest</code> 包的实验性支持(该包提供了测试并发代码的支持)。在收到反馈后,该包的 API 略有调整,并在 Go 1.25 中正式进入通用可用阶段。</p></li><li><p>Go 1.25 发布时附带了<a href="https://github.com/golang/go/issues/73581">新的垃圾回收器</a>设计的实验性支持,具有更好的性能。在吸收反馈之后,新的垃圾回收器在 Go 1.26 中成为默认选项。</p></li><li><p>Go 1.21 发布时附带了<a href="https://go.dev/wiki/LoopvarExperiment">循环变量语义行为变更</a>的实验性支持。这一变更消除了 Go 代码中一个以前常见的 bug,但在技术上是对语言的破坏性变更。将该变更作为实验发布,让人们在 Go 1.22 中该行为成为默认之前有机会测试自己的代码。</p></li></ul><h2 id="实验特性的生命周期"><a href="#实验特性的生命周期" class="headerlink" title="实验特性的生命周期"></a>实验特性的生命周期</h2><p>实验特性并没有单一固定的生命周期,但有一些常见的模式。</p><p>大多数实验特性最初以**默认关闭(off-by-default)**的方式发布。你需要显式选择加入(opt-in)来试用该功能,通常是通过设置 <code>GOEXPERIMENT</code> 环境变量(稍后会详细讨论)。</p><p>如果一切顺利,经过一到两个版本之后,实验特性被最终确定,进入通用可用阶段,并变为<strong>默认开启(on-by-default)</strong>。</p><p>如果一个实验特性影响了某些行为,那么它在进入通用可用阶段后,有时——但并非总是——会有一个过渡性的宽限期,在此期间可以临时禁用该特性并使用旧行为。例如,在 Go 1.26 中,新的垃圾回收器设计(上面简要提到过)进入了通用可用阶段并默认开启,但如果需要,仍然可以禁用它并使用旧的垃圾回收器。</p><p>以上就是最常见的模式,但有时候事情会更漫长或走向不同的方向。例如:</p><ul><li>Go 1.22 发布时附带了编译器内联逻辑的实验性实现,两年多过去了,它仍然默认关闭,处于评估之中。</li><li>同一次发布还附带了一个<strong>内存 arena</strong> 实验。在收到用户的负面反馈和顾虑之后,它仍然默认关闭,处于<a href="https://avittig.medium.com/golangs-big-miss-on-memory-arenas-f1375524cc90">无限期搁置</a>状态,最终可能会被完全移除。</li></ul><p>或者,当 Go 团队对某个变更足够有信心时,他们可能会跳过反馈阶段,直接进入通用可用阶段……但仍然可能有一个过渡性的宽限期,在此期间可以禁用它。</p><p>一个很好的例子是 Go 1.24 将其 map 实现改为使用 <a href="https://go.dev/blog/swisstable">Swiss tables</a>。Go 团队对实现及其性能优势足够有信心,因此直接进入通用可用阶段并默认开启,但——至少目前——如果你愿意,仍然可以选择退出并使用旧的 map 实现。</p><p>所以在实践中,实验特性实际上有三种大致状态:</p><ul><li>默认关闭,评估中</li><li>默认关闭,搁置/休眠中</li><li>默认开启,但有临时退出选项</li></ul><h2 id="永久实验特性"><a href="#永久实验特性" class="headerlink" title="永久实验特性"></a>永久实验特性</h2><p>Go 还有一些实验性功能,它们并非通常意义上的"实验"。</p><p>这些功能默认关闭,但它们不在评估中,不在寻求反馈,也没有预期它们最终会进入通用可用阶段并变为默认开启。</p><p>尽管它们通过 <code>GOEXPERIMENT</code> 环境设置以与其他实验相同的方式控制,但实际上它们更像是你可能在特定场景下想要使用的可选 Go 功能。</p><p>在下文中,我将这些称为"永久实验特性(permanent experiments)"。</p><p>例如,有一个 <a href="https://codereview.appspot.com/6749064">field tracking</a> 诊断功能,用于追踪哪些结构体字段被访问。它已经存在十年了,并且<a href="https://github.com/golang/go/issues/42712#issuecomment-737414957">没有意图</a>让它进入通用可用阶段。还有一个 <a href="https://go.googlesource.com/go/+/0a820007e70fdd038950f28254c6269cd9588c02">static lock ranking</a> 功能,这是一个用于查找 Go 运行时中潜在死锁的诊断工具。</p><h2 id="当前有哪些可用的实验特性?"><a href="#当前有哪些可用的实验特性?" class="headerlink" title="当前有哪些可用的实验特性?"></a>当前有哪些可用的实验特性?</h2><p>要了解当前有哪些可用的实验特性及其状态,出奇地困难。</p><p>遗憾的是,Go 官方文档或 <a href="https://go.dev/wiki/All">Go Wiki</a> 中没有一个页面来追踪实验特性的状态,为了写这篇文章,我不得不从各处拼凑信息。如果你想做同样的事:</p><ul><li>可以运行 <code>$ go doc goexperiment.Flags</code> 获取所有可用实验的列表。</li><li>可以通过阅读 <code>src/internal/buildcfg/exp.go</code> 的源码——特别关注 <code>ParseGOEXPERIMENT()</code> 函数中的 <code>baseline</code> 变量声明——来找出哪些实验是默认开启的。</li><li>可以将实验名称与 Go 发布说明交叉对照,并搜索 GitHub issues 来尝试弄清当前的状态。</li></ul><p>据我所知,截至 Go 1.26,以下是当前可用的永久实验特性:</p><table><thead><tr><th>实验名称</th><th>描述</th><th>状态</th></tr></thead><tbody><tr><td><code>FieldTrack</code></td><td>追踪哪些结构体字段被访问的诊断工具</td><td>默认关闭,<a href="https://github.com/golang/go/issues/42712#issuecomment-737414957">永久存在</a></td></tr><tr><td><code>StaticLockRanking</code></td><td>验证锁获取顺序以捕获死锁的诊断工具</td><td>默认关闭,永久存在</td></tr><tr><td><code>CgoCheck2</code></td><td><a href="https://tip.golang.org/doc/go1.21#runtimepkgruntime">检查 cgo 指针传递规则</a>的诊断工具;运行时开销太大,不适合默认开启</td><td>默认关闭,永久存在</td></tr><tr><td><code>BoringCrypto</code></td><td>用 FIPS 认证的 BoringSSL 替换 Go 的 crypto;自 <a href="https://go.dev/doc/go1.24#fips140">Go 1.24</a> 起已不再需要</td><td>默认关闭,<a href="https://github.com/golang/go/issues/42712#issuecomment-737414957">永久存在</a>,但<a href="https://go.dev/blog/fips140">很快将被移除</a></td></tr><tr><td><code>PreemptibleLoops</code></td><td>允许调度器在循环<a href="https://github.com/golang/go/issues/10958">回边处抢占 goroutine</a>;自 Go 1.14 起通常不再需要,但在<a href="https://go.dev/doc/go1.14#runtime">不支持抢占的平台上</a>可能仍然有用</td><td>默认关闭,永久存在</td></tr></tbody></table><p>以下是当前默认关闭的实验特性及其状态:</p><table><thead><tr><th>实验名称</th><th>描述</th><th>状态</th></tr></thead><tbody><tr><td><code>HeapMinimum512KiB</code></td><td>将最小堆大小从 4MB 减少到 512KiB;可能在受限环境中很有用</td><td>默认关闭,<a href="https://github.com/golang/go/commit/c5c1955077cb94736b0f311b3a02419d166f45ac">可能已休眠</a></td></tr><tr><td><code>Arenas</code></td><td><a href="https://uptrace.dev/blog/golang-memory-arena">内存 arena</a> 实现</td><td>默认关闭,收到负面反馈后<a href="https://github.com/golang/go/issues/51317">已搁置</a></td></tr><tr><td><code>NewInliner</code></td><td>重写的编译器内联器,具有更好的调用点启发式算法</td><td>默认关闭,评估中(自 <a href="https://go.dev/doc/go1.22#compiler">Go 1.22</a> 起可用)</td></tr><tr><td><code>JSONv2</code></td><td>新的 <code>encoding/json/v2</code> 包,提供改进的 JSON 编码/解码函数</td><td>默认关闭,评估中(自 <a href="https://go.dev/doc/go1.25#json_v2">Go 1.25</a> 起可用)</td></tr><tr><td><code>RuntimeSecret</code></td><td>新的 <code>runtime/secret</code> 包,提供清零内存的函数;仅支持 Linux amd64/arm64</td><td>默认关闭,评估中(自 <a href="https://go.dev/doc/go1.26#new-experimental-runtimesecret-package">Go 1.26</a> 起可用)</td></tr><tr><td><code>GoroutineLeakProfile</code></td><td>新增 <code>goroutineleak</code> pprof 分析类型</td><td>默认关闭,评估中(自 <a href="https://go.dev/doc/go1.26#goroutineleak-profiles">Go 1.26</a> 起可用)</td></tr><tr><td><code>SIMD</code></td><td>新的 <code>simd/archsimd</code> 包,提供对架构特定 SIMD 操作的访问;仅支持 amd64</td><td>默认关闭,评估中(自 <a href="https://go.dev/doc/go1.26#simd">Go 1.26</a> 起可用)</td></tr><tr><td><code>RuntimeFreegc</code></td><td>在安全时允许立即重用内存而无需等待 GC 周期</td><td>默认关闭,评估中(自 Go 1.26 起可用,但状态信息见 <a href="https://github.com/golang/go/issues/74299">#74299</a>)</td></tr><tr><td><code>SizeSpecializedMalloc</code></td><td>启用按大小类别特化的 malloc 实现</td><td>默认关闭,评估中(自 Go 1.26 起可用,但状态信息见 <a href="https://github.com/golang/go/issues/74299">#74299</a>)</td></tr></tbody></table><p>以下是当前默认开启的实验特性:</p><table><thead><tr><th>实验名称</th><th>描述</th><th>状态</th></tr></thead><tbody><tr><td><code>LoopVar</code></td><td>每次迭代独立的<a href="https://go.dev/wiki/LoopvarExperiment">循环变量作用域</a></td><td>自 <a href="https://go.dev/doc/go1.22">Go 1.22</a> 起默认开启,但为边缘情况保留了退出选项</td></tr><tr><td><code>Dwarf5</code></td><td>DWARF 5 调试信息生成;减小二进制文件大小</td><td>默认开启,保留临时退出选项(退出选项<a href="https://go.dev/doc/go1.25#dwarf5-support">可能在未来的版本中被移除</a>)</td></tr><tr><td><code>RandomizedHeapBase64</code></td><td>在启动时随机化堆基址,作为安全措施</td><td>默认开启,保留临时退出选项(退出选项<a href="https://go.dev/doc/go1.26#heap-base-address-randomization">预计将在未来版本中移除</a>)</td></tr><tr><td><code>GreenTeaGC</code></td><td>新的垃圾回收器,具有更好的性能;在 darwin/ios/aix 上不可用</td><td>默认开启,保留临时退出选项(退出选项<a href="https://go.dev/doc/go1.26#new-garbage-collector">预计将在 Go 1.27 中移除</a>)</td></tr><tr><td><code>RegabiWrappers</code></td><td>用于在 ABI0 和 ABIInternal 函数之间调用的 ABI 包装器;仅支持 64 位架构</td><td>默认开启,保留临时退出选项,但退出选项仅对 s390x 有效,且<a href="https://github.com/golang/go/commit/6da07b9b44d2ae08921cb97900f076c96a7bf6fc">将在 Go 1.27 中移除</a></td></tr><tr><td><code>RegabiArgs</code></td><td>在所有编译的 Go 函数中启用寄存器参数/返回值;仅支持 64 位架构</td><td>默认开启,保留临时退出选项,但退出选项仅对 s390x 有效,且<a href="https://github.com/golang/go/commit/6da07b9b44d2ae08921cb97900f076c96a7bf6fc">将在 Go 1.27 中移除</a></td></tr></tbody></table><h2 id="如何启用和禁用实验特性?"><a href="#如何启用和禁用实验特性?" class="headerlink" title="如何启用和禁用实验特性?"></a>如何启用和禁用实验特性?</h2><p>实验特性通过 <code>GOEXPERIMENT</code> 环境设置来控制。</p><p>如果你想尝试某些默认关闭的实验特性,应将实验名称作为逗号分隔的<strong>小写</strong>值包含在 <code>GOEXPERIMENT</code> 中。例如,如果你想在构建应用程序时启用 <code>JSONv2</code> 和 <code>GoroutineLeakProfile</code> 实验,可以这样做:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=jsonv2,goroutineleakprofile go build ./...</span><br></pre></td></tr></table></figure><p>如果你想关闭某个默认开启的实验特性,可以在小写实验名称前加上 <code>no</code> 前缀。例如,如果你想在构建应用程序时关闭 <code>GreenTeaGC</code> 和 <code>RandomizedHeapBase64</code> 实验,可以这样做:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=nogreenteagc,norandomizedheapbase64 go build ./...</span><br></pre></td></tr></table></figure><p>混合启用和禁用的实验也完全没有问题:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=jsonv2,nogreenteagc go build ./...</span><br></pre></td></tr></table></figure><p>注意,如果你使用不同的 <code>GOEXPERIMENT</code> 值构建同一个包,Go 会将它们视为不同的构建,并在构建缓存中存储独立的条目。</p><p>我在上面的示例中使用了 <code>go build</code>,但你在使用 <code>go run</code> 或 <code>go test</code> 时也可以使用完全相同的模式。如果你想亲自尝试,试着创建以下使用实验性 <a href="https://pkg.go.dev/encoding/json/v2"><code>encoding/json/v2</code></a> 包的程序:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line"> "encoding/json/v2"</span><br><span class="line"> "fmt"</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">type Person struct {</span><br><span class="line"> Name string `json:"name"`</span><br><span class="line"> Age int `json:"age"`</span><br><span class="line"> City string `json:"city"`</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">func main() {</span><br><span class="line"> p := Person{Name: "Ada", Age: 36, City: "Vienna"}</span><br><span class="line"></span><br><span class="line"> data, _ := json.Marshal(p, json.StringifyNumbers(true))</span><br><span class="line"> fmt.Println(string(data))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果你正常运行此程序,它将无法编译,你会看到类似以下的错误消息:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ go run main.go</span><br><span class="line">package command-line-arguments</span><br><span class="line"> imports encoding/json/v2: build constraints exclude all Go files in /usr/local/go/src/encoding/json/v2</span><br></pre></td></tr></table></figure><p>但如果你启用了 <code>JSONv2</code> 实验,程序将按预期运行:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=jsonv2 go run main.go</span><br><span class="line">{"name":"Ada","age":"36","city":"Vienna"}</span><br></pre></td></tr></table></figure><h2 id="你应该关注哪些实验特性?"><a href="#你应该关注哪些实验特性?" class="headerlink" title="你应该关注哪些实验特性?"></a>你应该关注哪些实验特性?</h2><p>如果你像我一样是个普通的 Gopher,主要用 Go 编写程序而非开发 Go 本身,那么大多数可用的实验特性可能与你关系不大。</p><p>最有意思、最相关的几个可能是:</p><ul><li><a href="https://go.dev/blog/greenteagc"><code>GreenTeaGC</code></a> —— 如果你在使用 Go 1.26,你已经在默认使用它了。但如果你注意到任何性能或行为问题,你应该知道自己仍然可以禁用它(并且还应该提交一个 issue)。</li><li><a href="https://go.dev/doc/go1.25#dwarf5-support"><code>Dwarf5</code></a> —— 同样,如果你在使用 Go 1.25 或更高版本,你已经在默认使用它了。但如果你遇到任何问题,知道自己仍然可以禁用它是有用的。</li><li><a href="https://go.dev/doc/go1.25#json_v2"><code>JSONv2</code></a> —— 我不建议在它进入通用可用阶段之前切换,但如果你编写大量处理 JSON 的代码,值得尝试新的 <code>encoding/json/v2</code> 包,熟悉即将到来的内容,并在发现问题时给出反馈。</li><li><a href="https://go.dev/doc/go1.26#goroutineleak-profiles"><code>GoroutineLeakProfile</code></a> —— 这个特性可以立即派上用场,如果你怀疑有 goroutine 泄漏并需要调试,值得启用。</li><li><a href="https://go.dev/doc/go1.26#new-experimental-runtimesecret-package"><code>RuntimeSecret</code></a> —— 如果你编写加密代码或需要处理敏感数据,值得尝试并给出反馈。</li><li><a href="https://github.com/golang/go/issues/74299"><code>RuntimeFreegc</code></a> —— 如果你的应用程序严重依赖垃圾回收器,可能值得在启用此特性的情况下对你的代码进行基准测试,看看是否提高了性能,并在发现问题时给出反馈。</li></ul><p>最后,需要强调的是,实验特性不受 Go 兼容性承诺的覆盖。它们的 API、行为和性能特征都可能发生变化,因此通常最好避免过早采用,不要在实验特性最终确定之前依赖它们。</p><p>但实验特性通常是对 Go 中一些最重大变更的预览。如果你知道某个实验最终进入通用可用阶段并默认开启后可能会影响你或你的代码,那么尝试它、酌情运行基准测试、并在发现问题时给出反馈是个好主意。</p><p>如果你想追踪有哪些可用的实验特性及其状态,Go 发布说明最近在记录实验特性及其使用方式方面做得越来越好了。结合这篇博客文章和每次 Go 新版本发布时浏览发布说明,你应该能对整体情况有一个不错的了解。</p><hr><p><strong>原文来源</strong>:Alex Edwards, "Go Experiments Explained", June 1, 2026, <a href="https://www.alexedwards.net/blog/go-experiments-explained">https://www.alexedwards.net/blog/go-experiments-explained</a></p>
同分类推荐文章
- 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
- amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)
- Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 (2026-06-17 04:00:24)
建议继续学习
- Go Reflect 性能 (累计阅读 14,160)
- 面向“接口”编程和面向“实现”编程 (累计阅读 13,912)
- 一种基于长连接的社交游戏服务器程序构架 (累计阅读 7,509)
- 从Go看,语言设计(一) (累计阅读 6,170)
- Lua GC 的源码剖析 (2) (累计阅读 5,080)
- go-kit 入门(一) (累计阅读 4,775)
- 分布式存储Seaweedfs源码分析 (累计阅读 4,757)
- 为什么我们要使用Go语言以及如何使用它的 (累计阅读 4,588)
- Go 语言初步 (累计阅读 4,502)
- 程序员的“横向发展” (累计阅读 4,144)