<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>WithTimeout on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/withtimeout/</link><description>Recent content in WithTimeout on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 26 May 2026 15:30:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/withtimeout/index.xml" rel="self" type="application/rss+xml"/><item><title>WithTimeout 的 cancel，不是你想象的那个“超时按钮”</title><link>https://blog.cpdd.fyi/posts/go-context-cancel-is-release/</link><pubDate>Tue, 26 May 2026 15:30:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-context-cancel-is-release/</guid><description>&lt;p&gt;接口已经超时了，goroutine 曲线还在往上爬。&lt;/p&gt;
&lt;p&gt;上篇讲到这里时，我们先压住了一个误判：这通常不是 &lt;code&gt;context&lt;/code&gt; 没生效，而是你的 goroutine 没有响应取消信号。&lt;/p&gt;
&lt;p&gt;但还有一个更隐蔽的问题，很多 Go 代码每天都在写：&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;span class="lnt"&gt;2
&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-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&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;这句 &lt;code&gt;defer cancel()&lt;/code&gt; 到底是干什么的？&lt;/p&gt;
&lt;p&gt;很多人心里其实把它理解成“超时按钮”。好像 cancel 是用来触发 timeout 的；既然 timeout 到点会自动发生，那函数里忘了调也没什么大不了。&lt;/p&gt;
&lt;p&gt;这个理解只差一步，但差的正是事故里最贵的一步。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-context-cancel-is-release/cover.png" alt="WithTimeout cancel 不是超时按钮"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WithTimeout&lt;/code&gt; 的 cancel 不是超时按钮，是资源释放按钮。&lt;/p&gt;
&lt;p&gt;时间到了，标准库会取消这个 context；但你的函数提前返回时，标准库不知道你这条调用链已经没用了。你不主动 cancel，它就只能等 deadline 到，或者等父 context 被取消。&lt;/p&gt;
&lt;p&gt;这不是语法洁癖，是生命周期设计。&lt;/p&gt;
&lt;h2 id="timeout-会自动发生不等于你可以不收尾"&gt;timeout 会自动发生，不等于你可以不收尾&lt;/h2&gt;
&lt;p&gt;先把 &lt;code&gt;WithTimeout&lt;/code&gt; 看薄一点。&lt;/p&gt;
&lt;p&gt;Go 标准库里，&lt;code&gt;WithTimeout(parent, d)&lt;/code&gt; 本质上就是：&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-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&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;它创建的是一个带 timer 的子 context。deadline 到了，timer 触发，context 的 &lt;code&gt;Done&lt;/code&gt; 会关闭，&lt;code&gt;Err()&lt;/code&gt; 会变成 &lt;code&gt;context deadline exceeded&lt;/code&gt;。&lt;/p&gt;</description></item></channel></rss>