<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>源码 on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/%E6%BA%90%E7%A0%81/</link><description>Recent content in 源码 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Wed, 20 May 2026 17:50:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/%E6%BA%90%E7%A0%81/index.xml" rel="self" type="application/rss+xml"/><item><title>Go context 最容易被误用的地方：以为 cancel 会替你收拾现场</title><link>https://blog.cpdd.fyi/posts/go-context-timeout-source/</link><pubDate>Wed, 20 May 2026 17:50:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-context-timeout-source/</guid><description>&lt;p&gt;线上接口开始超时，goroutine 数一路往上涨。&lt;/p&gt;
&lt;p&gt;你第一反应可能是：不是已经传了 &lt;code&gt;context.WithTimeout&lt;/code&gt; 吗？超时到了，goroutine 不就该停了吗？&lt;/p&gt;
&lt;p&gt;这就是 Go &lt;code&gt;context&lt;/code&gt; 最容易被误用的地方。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 不负责替你停掉 goroutine。它只负责把“该停了”这句话传到门口。门里的人听不听、什么时候退场，要看你自己的代码。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-context-timeout-source/cover.png" alt="Go context 调用链生命周期封面"&gt;&lt;/p&gt;
&lt;p&gt;这篇是 Go &lt;code&gt;context&lt;/code&gt; 系列的第一篇。先不急着讲 HTTP、gRPC、database/sql 这些框架怎么接入，也不急着把所有 API 列一遍。&lt;/p&gt;
&lt;p&gt;我们先把最底下这层讲清楚：&lt;code&gt;context&lt;/code&gt; 到底是什么，&lt;code&gt;cancel&lt;/code&gt; 到底做了什么，为什么 &lt;code&gt;WithTimeout&lt;/code&gt; 后面那句 &lt;code&gt;defer cancel()&lt;/code&gt; 不是仪式感，而是资源释放动作。&lt;/p&gt;
&lt;h2 id="事故通常不是从源码开始的"&gt;事故通常不是从源码开始的&lt;/h2&gt;
&lt;p&gt;真实排查里，很少有人一上来就打开 &lt;code&gt;context.go&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;更常见的是这样的场景：&lt;/p&gt;
&lt;p&gt;一个接口偶发超时。压测一上来，P99 开始抖，错误日志里出现 &lt;code&gt;context deadline exceeded&lt;/code&gt;。你看监控，CPU 没打满，内存也没爆，但 goroutine 数不太对，一路往上爬。&lt;/p&gt;
&lt;p&gt;然后你打开 pprof：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s &lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:6060/debug/pprof/goroutine?debug=1&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;采样里有一批 goroutine 卡在业务 worker 上，等某个 channel，或者等 &lt;code&gt;&amp;lt;-ctx.Done()&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这时候最危险的判断是：&lt;/p&gt;
&lt;p&gt;“是不是 Go 的 context 泄漏了？”&lt;/p&gt;
&lt;p&gt;大多数时候，不是。&lt;/p&gt;
&lt;p&gt;更可能是你的调用链里少了三件事之一：创建了 timeout context 但没及时 &lt;code&gt;cancel&lt;/code&gt;；启动了 goroutine 但没监听 &lt;code&gt;ctx.Done()&lt;/code&gt;；或者监听了 &lt;code&gt;Done&lt;/code&gt;，却没有确保取消路径真的跑到。&lt;/p&gt;</description></item></channel></rss>