<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>第 16 章 工具与可观测性 on Go 语言原本</title>
    <link>/zh-cn/part5toolchain/ch16tools/</link>
    <description>Recent content in 第 16 章 工具与可观测性 on Go 语言原本</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <atom:link href="/zh-cn/part5toolchain/ch16tools/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>16.1 运行时死锁检查</title>
      <link>/zh-cn/part5toolchain/ch16tools/deadlock/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/deadlock/</guid>
      <description>&lt;h1 id=&#34;161-运行时死锁检查&#34;&gt;16.1 运行时死锁检查&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;fatal error: all goroutines are asleep - deadlock!&lt;/code&gt;,几乎每个 Go 程序员都见过这条错误。它来自&#xA;运行时内置的死锁检测器。这一节先讲清它怎么判定、何时被触发，再讲一件更要紧的事：它在理论上&#xA;能保证什么、又有什么是它&lt;strong&gt;结构性地检测不了&lt;/strong&gt;的。后者是许多线上「卡死」之谜的根源，理解了它，&#xA;比记住那条错误信息更有价值。&lt;/p&gt;&#xA;&lt;p&gt;我们先说结论，再看它如何从一段不到一百行的代码里得出。运行时的死锁检测器并不维护、也从不检查&#xA;一张「谁在等谁」的等待图（wait-for graph）。它只问两个极粗的问题：还有没有 M 在运行？将来有没有&#xA;计时器会触发？两个都答「没有」，它就宣告死锁。这种粗糙不是疏忽，它恰恰决定了检测器的能力边界,&#xA;本节后半会看到，正因为没有逐资源的等待边，它&lt;strong&gt;无法&lt;/strong&gt;在一部分 goroutine 之间找出一个死锁环。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1611-判据只数运行中的-m不看谁等谁&#34;&gt;16.1.1 判据：只数运行中的 M，不看谁等谁&lt;/h2&gt;&#xA;&lt;p&gt;死锁检测逻辑藏在 &lt;code&gt;runtime/proc.go&lt;/code&gt; 的 &lt;code&gt;checkdead&lt;/code&gt; 里&#xA;（&lt;a href=&#34;../../../part3concurrency/ch09sched/sysmon&#34;&gt;9.8&lt;/a&gt;）。它的判据可以一句话概括：&lt;strong&gt;没有任何线程&#xA;还在运行，且没有任何途径能让某个 goroutine 重新变得可运行，程序就死锁了。&lt;/strong&gt; 落到代码，「没有&#xA;线程在运行」是一次减法,用机器线程总数减去各类空闲与系统线程：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// checkdead：判定是否陷入全局死锁（裁剪后的速写）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 调用时必须持有 sched.lock。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;checkdead&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 作为 c-shared / c-archive 库被宿主程序嵌入时，没有运行中的 goroutine 是正常的,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 宿主仍在跑。panicking、cgo 额外 M 等情形也各自豁免（此处略）。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;islibrary&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isarchive&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;GOARCH&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;wasm&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// run = 机器线程数 − 空闲 M − 持锁空闲 M − 系统 M，即「正在运行的 M」数。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// run0 通常为 0（cgo 额外 M 存在时为 1）。只要还有 M 在运行，就不可能&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 是全局死锁，立即返回。&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;mcount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nmidle&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nmidlelocked&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sched&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;nmsys&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;run0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ...（见下）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;注意这里没有出现任何「锁」「channel」「WaitGroup」的字样。&lt;code&gt;checkdead&lt;/code&gt; 不知道、也不关心每个&#xA;goroutine 究竟阻塞在哪个原语上,它只数还有几个 M 在跑。这是理解它全部行为的关键。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.2 竞争检查</title>
      <link>/zh-cn/part5toolchain/ch16tools/race/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/race/</guid>
      <description>&lt;h1 id=&#34;162-竞争检查&#34;&gt;16.2 竞争检查&lt;/h1&gt;&#xA;&lt;p&gt;数据竞争（data race）是并发程序里最隐蔽、最难复现的一类 bug。它的定义很短：两个 goroutine&#xA;并发访问同一内存地址、其中至少一个是写、且二者之间没有任何同步把它们排序，那么程序的行为&#xA;就是未定义的（&lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;）。「未定义」不是「读到旧值」&#xA;这么温和，它意味着编译器与处理器都被允许做出令人意外的重排，结果可能时对时错，可能只在压力&#xA;负载下、只在某种 CPU 上、只在某个版本里偶发一次。靠人眼复查这种 bug 是没有指望的。Go 的&#xA;&lt;strong&gt;竞态检测器&lt;/strong&gt;（&lt;code&gt;-race&lt;/code&gt;）正是把这件事从「靠运气复现」变成「跑一遍就告诉你」的工具。这一节&#xA;讲清它的原理、它背后的算法代价、以及它那两条必须记住的能力边界。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1621-基于-happens-before-的动态检测&#34;&gt;16.2.1 基于 happens-before 的动态检测&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;go test -race&lt;/code&gt;、&lt;code&gt;go run -race&lt;/code&gt;、&lt;code&gt;go build -race&lt;/code&gt;、&lt;code&gt;go install -race&lt;/code&gt; 都能开启竞态检测。它的&#xA;内核是 Google 在 LLVM &lt;code&gt;compiler-rt&lt;/code&gt; 里维护的 &lt;strong&gt;ThreadSanitizer&lt;/strong&gt;（TSan）运行时，Go 以预编译的&#xA;&lt;code&gt;.syso&lt;/code&gt; 形式把它链进程序（见 &lt;code&gt;runtime/race/&lt;/code&gt;，因此 &lt;code&gt;-race&lt;/code&gt; 依赖 cgo）。它的工作原理是&#xA;&lt;strong&gt;动态的 happens-before 检测&lt;/strong&gt;：程序运行时，编译器在每一次内存访问前插入一个回调，运行时则&#xA;拦截每一个同步事件，二者合起来让 TSan 实时维护一张 happens-before 关系图（&lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt;&#xA;那条偏序）。当它发现两个 goroutine 访问了同一地址、至少一个是写、而二者之间&lt;strong&gt;不存在&lt;/strong&gt;任何&#xA;happens-before 边，就报告一个数据竞争。&lt;/p&gt;&#xA;&lt;p&gt;为什么「访问」与「同步」要分开拦截？因为这正是 happens-before 偏序的两个来源。访问告诉检测器&#xA;「谁在何时碰了哪块内存」，同步告诉它「哪两条时间线之间被建立了次序」。Go 在 &lt;code&gt;runtime/race.go&lt;/code&gt;&#xA;里暴露的这组钩子，恰好把这两类事件一一对应了出来：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;//go:build race&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 访问事件：编译器在每次读/写内存前自动插桩调用&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceRead&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceWrite&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceReadRange&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 切片、memmove 等成片访问&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceWriteRange&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 同步事件：在 addr 上建立跨 goroutine 的 happens-before 关系&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 注释原文：establish happens-before relations between goroutines&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceAcquire&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ≈ C11 atomic_load(acquire)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceRelease&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ≈ C11 atomic_store(release)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;RaceReleaseMerge&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;unsafe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Pointer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ≈ C11 atomic_exchange(release)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;&lt;code&gt;RaceRead&lt;/code&gt;/&lt;code&gt;RaceWrite&lt;/code&gt; 是访问点，运行时里 channel 收发、&lt;code&gt;sync.Mutex&lt;/code&gt; 的加解锁、&lt;code&gt;sync/atomic&lt;/code&gt;&#xA;的每个原子操作、goroutine 的创建与退出，则在内部调用 &lt;code&gt;raceacquire&lt;/code&gt;/&lt;code&gt;racerelease&lt;/code&gt; 这对原语&#xA;把 happens-before 边「焊」上去。换句话说，&lt;a href=&#34;../../../part3concurrency/ch11sync/mem&#34;&gt;11.9&lt;/a&gt; 里&#xA;规定的每一条建立次序的语义（channel 发送 happens-before 对应接收完成、解锁 happens-before&#xA;后续加锁、原子写以 release 顺序同步原子读的 acquire），在 TSan 这里都落成一条具体的图上的边。&#xA;内存模型那句抽象的「无 happens-before 即竞争」，于是变成了一个能实际抓现行的判定。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.3 性能追踪</title>
      <link>/zh-cn/part5toolchain/ch16tools/trace/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/trace/</guid>
      <description>&lt;h1 id=&#34;163-性能追踪&#34;&gt;16.3 性能追踪&lt;/h1&gt;&#xA;&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt;（&lt;a href=&#34;.././perf&#34;&gt;16.5&lt;/a&gt;）回答的是「时间花在哪些函数上」。它把一段时间里的 CPU 采样聚合&#xA;成一棵调用树，告诉你 &lt;code&gt;json.Marshal&lt;/code&gt; 占了 30% 的 CPU。但它答不了另一类问题：一个请求耗了&#xA;50ms，其中 45ms 这个 goroutine &lt;strong&gt;根本没在跑&lt;/strong&gt;，它在等。等什么？等锁？等网络？被 GC 打断？&#xA;还是想跑却没有 P 可用（&lt;a href=&#34;../../../part3concurrency/ch09sched/steal&#34;&gt;9.2&lt;/a&gt;）？CPU 画像对「等待」&#xA;一无所知，因为它只采样正在执行的栈，一个阻塞的 goroutine 不在任何 CPU 上，自然不会被采到。&lt;/p&gt;&#xA;&lt;p&gt;回答「为什么等」要靠另一件工具，&lt;strong&gt;执行追踪器&lt;/strong&gt;（execution tracer）。它不做统计抽样，而是&#xA;&lt;strong&gt;逐条记录运行时事件&lt;/strong&gt;，按纳秒级时间戳排成一条时间线：哪个 goroutine 在哪一刻被创建、开始&#xA;跑、阻塞、被唤醒、结束;每个 P 何时开始与停止调度;每次 GC 走到哪个阶段;每个系统调用何时&#xA;进出。把这条时间线交给 &lt;code&gt;go tool trace&lt;/code&gt; 渲染，你能逐毫秒地看清程序里发生了什么。这一节讲它&#xA;记录什么、怎么用、以及它近年从「重而全」走向「轻而按需」的演进。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1631-追踪器记录什么运行时事件的时间线&#34;&gt;16.3.1 追踪器记录什么：运行时事件的时间线&lt;/h2&gt;&#xA;&lt;p&gt;执行追踪器内建在运行时里（&lt;code&gt;runtime/trace.go&lt;/code&gt;），它捕获的不是用户代码的函数调用，而是&#xA;&lt;strong&gt;运行时与调度器层面的状态转移&lt;/strong&gt;。&lt;code&gt;runtime/trace.go&lt;/code&gt; 的设计注释把这份事件清单列得很清楚：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;goroutine 生命周期&lt;/strong&gt;：创建（go 语句）、开始运行、阻塞、被唤醒、退出;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;阻塞的原因&lt;/strong&gt;：在 channel 收发、在 &lt;code&gt;sync.Mutex&lt;/code&gt;、在网络 I/O（netpoller，&lt;a href=&#34;../../../part3concurrency/ch09sched/poller&#34;&gt;9.9 网络轮询器&lt;/a&gt;）、在 &lt;code&gt;select&lt;/code&gt; 上各是不同的事件类型;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;每个 P 的调度活动&lt;/strong&gt;：P 何时启动、停止、被抢占;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;GC 相关事件&lt;/strong&gt;：标记开始、标记结束、各 STW（stop-the-world）停顿、堆大小的变化（&lt;a href=&#34;../../../part4memory/ch13gc/pacing&#34;&gt;13.3&lt;/a&gt;）;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;系统调用&lt;/strong&gt;：进入、返回、以及因系统调用阻塞而交还 P 的时刻。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;大多数事件都附带一个&lt;strong&gt;纳秒精度的时间戳&lt;/strong&gt;和一份&lt;strong&gt;栈回溯&lt;/strong&gt;。这意味着 trace 不仅告诉你「这里&#xA;发生了一次阻塞」，还告诉你「是哪一行代码、在哪个调用栈深处阻塞的」。把这些事件按 P、按&#xA;goroutine 在时间轴上铺开，&lt;code&gt;go tool trace&lt;/code&gt; 画出一张可交互的时间线，你能直接读出：这一刻有&#xA;几个 P 在干活、哪个 goroutine 跑在哪个 P 上、它为什么停下、GC 是不是正在和业务抢 CPU。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.4 代码测试</title>
      <link>/zh-cn/part5toolchain/ch16tools/testing/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/testing/</guid>
      <description>&lt;h1 id=&#34;164-代码测试&#34;&gt;16.4 代码测试&lt;/h1&gt;&#xA;&lt;p&gt;测试在 Go 里不是外挂，而是语言工具链的一等公民。&lt;code&gt;go test&lt;/code&gt; 内建，&lt;code&gt;testing&lt;/code&gt; 包标准，约定&#xA;取代配置。这套设计上的选择，深刻塑造了 Go 的工程文化：在别的语言里「要不要写测试、用哪个&#xA;框架」是一道需要权衡的决策，在 Go 里它是默认动作。这一节讲清这套机制如何运转、为何如此设计，&#xA;以及它从单元测试一路长到 Go 1.18 模糊测试的谱系。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1641-一等公民约定取代配置&#34;&gt;16.4.1 一等公民：约定取代配置&lt;/h2&gt;&#xA;&lt;p&gt;Go 的测试靠约定运转，几乎零配置。三条规则就是全部：测试文件以 &lt;code&gt;_test.go&lt;/code&gt; 结尾，测试函数&#xA;形如 &lt;code&gt;func TestXxx(t *testing.T)&lt;/code&gt;，与被测代码放在同一个包里。&lt;code&gt;go test&lt;/code&gt; 会自动发现并运行它们，&#xA;不需要 XML 配置、不需要外部 runner、不需要注解。一个项目无论多大，&lt;code&gt;go test ./...&lt;/code&gt; 一行就跑遍&#xA;全部测试：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// strings_test.go，与被测包 strings 同目录、同包&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;strings&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;testing&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;TestIndex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;testing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;T&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;chicken&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ken&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Errorf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Index = %d, want 4&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;got&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 报告失败，但不中断&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;「约定优于配置」带来两层收益。其一是零门槛：写测试不需要先学一套框架，函数签名对了就能跑。&#xA;其二，也是更深远的一层，是整个生态的统一。所有 Go 项目的测试方式完全一致，于是 CI、覆盖率&#xA;工具、IDE、&lt;code&gt;go vet&lt;/code&gt; 都只需面对一种约定，无需为各色框架各写一套适配。这与别家形成鲜明对照：&#xA;Java 世界有 JUnit 4 / JUnit 5 / TestNG 之分，注解与 runner 各不相同；Python 有 &lt;code&gt;unittest&lt;/code&gt;、&#xA;&lt;code&gt;pytest&lt;/code&gt;、&lt;code&gt;nose&lt;/code&gt; 并存，发现规则与 fixture 机制互不兼容。框架的多样性把「跑某项目的测试」变成&#xA;一件需要先读文档的事。Go 用一套内建约定把这件事抹平到了零。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.5 基准测试与性能画像</title>
      <link>/zh-cn/part5toolchain/ch16tools/perf/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/perf/</guid>
      <description>&lt;h1 id=&#34;165-基准测试与性能画像&#34;&gt;16.5 基准测试与性能画像&lt;/h1&gt;&#xA;&lt;p&gt;优化的第一原则是先测量，再动手。凭直觉猜瓶颈往往猜错，真正的热点常常不在你以为的地方。Go 把&#xA;这套纪律所需的工具全部做进了工具链：基准测试（benchmark）回答「多快」，性能画像（profiling）&#xA;回答「慢在哪、内存花在哪」，而 &lt;code&gt;benchstat&lt;/code&gt; 用统计方法回答「这次改动到底是真的快了，还是只是&#xA;噪声」。这一节把这三件工具讲透，并把它们串成一条「画像找热点 → 改 → 基准验证」的测量驱动闭环,&#xA;这条闭环再往上，便与 PGO（&lt;a href=&#34;../../ch15compile/optimize&#34;&gt;15.3&lt;/a&gt;）和执行追踪&#xA;（&lt;a href=&#34;.././trace&#34;&gt;16.3&lt;/a&gt;）合流，构成 Go 完整的性能工程方法论。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1651-基准测试让多快可复现&#34;&gt;16.5.1 基准测试：让「多快」可复现&lt;/h2&gt;&#xA;&lt;p&gt;基准测试用 &lt;code&gt;func BenchmarkXxx(b *testing.B)&lt;/code&gt; 写，&lt;code&gt;go test -bench&lt;/code&gt; 运行。它要解决的核心难题是&#xA;&lt;strong&gt;测量精度&lt;/strong&gt;：单次调用一个纳秒级函数，时钟分辨率与调用开销会把信号淹没。Go 的办法是把待测代码&#xA;放进循环跑很多次，再除以次数得到「每次操作的耗时」（ns/op）。但循环要跑多少次？跑少了不稳定，&#xA;跑多了浪费时间。早期写法是手写一个从 &lt;code&gt;0&lt;/code&gt; 到 &lt;code&gt;b.N&lt;/code&gt; 的循环，框架&lt;strong&gt;自动反复调整 &lt;code&gt;b.N&lt;/code&gt;、多跑几轮&lt;/strong&gt;，&#xA;直到测量窗口足够长、结果足够稳定为止：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;BenchmarkFib&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;testing&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;B&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;N&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 框架反复加大 b.N 直到耗时稳定&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;fib&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这套 &lt;code&gt;b.N&lt;/code&gt; 机制朴素可靠，却有两处长期为人诟病的坑。其一是&lt;strong&gt;计时边界&lt;/strong&gt;：若基准函数里有昂贵的&#xA;准备工作，它会被算进每次测量，得手动 &lt;code&gt;b.ResetTimer()&lt;/code&gt;/&lt;code&gt;b.StopTimer()&lt;/code&gt; 把它摘出去（见&#xA;&lt;a href=&#34;/zh-cn/part5toolchain/ch16tools/perf/#1653-%e5%9f%ba%e5%87%86%e6%b5%8b%e8%af%95%e7%9a%84%e4%b8%a4%e4%b8%aa%e7%bb%8f%e5%85%b8%e9%99%b7%e9%98%b1&#34;&gt;16.5.3&lt;/a&gt;）。其二是&lt;strong&gt;死代码消除&lt;/strong&gt;：编译器看到 &lt;code&gt;fib(30)&lt;/code&gt; 的返回值&#xA;没人用，可能直接把整个调用优化掉，于是你测了个寂寞。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.6 运行时统计量</title>
      <link>/zh-cn/part5toolchain/ch16tools/metric/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/metric/</guid>
      <description>&lt;h1 id=&#34;166-运行时统计量&#34;&gt;16.6 运行时统计量&lt;/h1&gt;&#xA;&lt;p&gt;画像（&lt;a href=&#34;.././perf&#34;&gt;16.5&lt;/a&gt;）与追踪（&lt;a href=&#34;.././trace&#34;&gt;16.3&lt;/a&gt;）属于「出事时拉来诊断」的工具：它们成本&#xA;偏高、产出庞大，适合在你已经怀疑某处有问题时，针对一段时间或一次请求采下来细看。但生产环境&#xA;里更常见的问题不是「现在帮我剖析一下」，而是「这个服务过去一周健康吗、什么时候开始劣化的、&#xA;该不该半夜把人叫起来」。回答这类问题靠的不是一次性的深剖，而是&lt;strong&gt;持续的、低成本的、可长期&#xA;保留的数值&lt;/strong&gt;：堆有多大、GC 多频繁、goroutine 数量是否在涨、调度延迟的尾部有没有抬头。这类&#xA;数值就是&lt;strong&gt;运行时指标&lt;/strong&gt;（metrics）。&lt;/p&gt;&#xA;&lt;p&gt;指标和画像的区别，本质是「聚合 vs 明细」与「常驻 vs 按需」。画像把每一次分配、每一段 CPU&#xA;归因到具体调用栈，信息量大、采集贵；指标只保留聚合后的标量或分布，单次读取近乎免费，因而&#xA;可以每隔几秒采一次、连续采上几个月。这一节讲清 Go 暴露指标的两套接口、它们的演进，以及&#xA;指标如何接入完整的可观测性体系。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1661-从-memstats-到-runtimemetrics&#34;&gt;16.6.1 从 MemStats 到 runtime/metrics&lt;/h2&gt;&#xA;&lt;p&gt;历史上，程序读运行时内存指标的唯一入口是 &lt;code&gt;runtime.ReadMemStats&lt;/code&gt;，它填充一个 &lt;code&gt;MemStats&lt;/code&gt;&#xA;结构（&lt;a href=&#34;../../../part4memory/ch12alloc/mstats&#34;&gt;12.8&lt;/a&gt;）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// runtime.MemStats：字段写死在结构体里（节选）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemStats&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Alloc&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 当前存活对象占用的堆字节&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HeapInuse&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 含已用 span 的堆字节&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;NumGC&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint32&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 已完成的 GC 轮数&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PauseTotalNs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;       &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 累计 STW 停顿（纳秒）&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;PauseNs&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;256&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;uint64&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 最近 256 次停顿的环形缓冲&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... 还有约三十个字段&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;MemStats&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;runtime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;ReadMemStats&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 读取时需短暂 stop-the-world&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;这套接口有三处硬伤。其一，字段&lt;strong&gt;写死&lt;/strong&gt;在结构体里：运行时想暴露一个新指标，就得往&#xA;&lt;code&gt;MemStats&lt;/code&gt; 里加字段、改公开 API，受 Go 兼容性承诺约束，加得很慎重，于是许多内部状态根本&#xA;没有出口。其二，&lt;code&gt;ReadMemStats&lt;/code&gt; 为了取得自洽快照，读取时要短暂 &lt;strong&gt;stop-the-world&lt;/strong&gt;，在高频&#xA;采集下成本不可忽略。其三，停顿信息只有一个 &lt;code&gt;PauseNs&lt;/code&gt; 环形数组和累计值，想知道「停顿的 P99&#xA;是多少」得自己从原始数组里算，而那个数组只存最近 256 次，早就漏掉了长期分布。&lt;/p&gt;</description>
    </item>
    <item>
      <title>16.7 语言服务协议</title>
      <link>/zh-cn/part5toolchain/ch16tools/gopls/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/zh-cn/part5toolchain/ch16tools/gopls/</guid>
      <description>&lt;h1 id=&#34;167-语言服务协议&#34;&gt;16.7 语言服务协议&lt;/h1&gt;&#xA;&lt;p&gt;自动补全、跳转定义、实时报错、重构,现代编辑器里这些功能，在 Go 这边由 &lt;strong&gt;&lt;code&gt;gopls&lt;/code&gt;&lt;/strong&gt;（Go language&#xA;server，读作「go please」）提供。它背后是&lt;strong&gt;语言服务器协议&lt;/strong&gt;（Language Server Protocol，LSP）。&#xA;本节讲清三件事：LSP 解开的那个组合爆炸结、gopls 如何建立在编译器前端的可复用库之上、以及它&#xA;为何称得上 Go 工具链思想的集大成者。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1671-lsp解开-mn-的结&#34;&gt;16.7.1 LSP：解开 M×N 的结&lt;/h2&gt;&#xA;&lt;p&gt;设想没有 LSP 的世界。要让 &lt;strong&gt;M 个编辑器&lt;/strong&gt;都支持 &lt;strong&gt;N 种语言&lt;/strong&gt;的智能功能，朴素的做法是为每一对&#xA;「编辑器 × 语言」单独写一套集成：VS Code 要懂 Go、懂 Rust、懂 Python，Vim 也要各懂一遍,补全、&#xA;跳转、报错的逻辑在每一对里都重写。代价是 &lt;strong&gt;M×N&lt;/strong&gt; 套实现，且任何一门语言的语义更新，都要在&#xA;M 个编辑器里各自跟进。这条增长曲线注定走不远。&lt;/p&gt;&#xA;&lt;p&gt;LSP 把它解成 &lt;strong&gt;M+N&lt;/strong&gt;。微软在 2016 年 6 月为 Visual Studio Code 提出这套协议(后与 Red Hat、&#xA;Codenvy 合作开放，现已成行业标准)。它的关键是定义&lt;strong&gt;一套与语言、与编辑器都无关的标准协议&lt;/strong&gt;：&#xA;每门语言只需写&lt;strong&gt;一个&lt;/strong&gt;语言服务器（实现协议），每个编辑器只需写&lt;strong&gt;一个&lt;/strong&gt; LSP 客户端,此后任意&#xA;编辑器配任意语言，自动打通。语言服务器是一个独立进程，编辑器通过标准输入输出或套接字与它通信。&lt;/p&gt;&#xA;&lt;p&gt;协议本身建立在 &lt;strong&gt;JSON-RPC&lt;/strong&gt; 之上,只有三类消息：客户端发&lt;strong&gt;请求&lt;/strong&gt;（request）等服务器回&lt;strong&gt;响应&lt;/strong&gt;&#xA;（response），以及单向的&lt;strong&gt;通知&lt;/strong&gt;（notification）。位置用「文档 URI + 行列号」表示，刻意避开任何&#xA;语言特有的抽象（如某语言的 AST 节点），这正是它能跨语言通用的前提。一次「跳转到定义」的往返&#xA;是这样：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
