<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>WithValue on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/withvalue/</link><description>Recent content in WithValue on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 26 May 2026 17:20:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/withvalue/index.xml" rel="self" type="application/rss+xml"/><item><title>context.WithValue 不是 map，是一条链</title><link>https://blog.cpdd.fyi/posts/go-context-value-is-chain/</link><pubDate>Tue, 26 May 2026 17:20:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-context-value-is-chain/</guid><description>&lt;p&gt;函数签名很干净，代码却越来越难查。&lt;/p&gt;
&lt;p&gt;你打开一个 Go 函数，只看到一个 &lt;code&gt;ctx context.Context&lt;/code&gt;。再往里翻，发现 user id 从 &lt;code&gt;ctx.Value&lt;/code&gt; 里取，tenant 从 &lt;code&gt;ctx.Value&lt;/code&gt; 里取，trace id 从 &lt;code&gt;ctx.Value&lt;/code&gt; 里取，logger、配置开关、灰度标记，也都从 &lt;code&gt;ctx.Value&lt;/code&gt; 里取。&lt;/p&gt;
&lt;p&gt;签名短了，依赖没少。&lt;/p&gt;
&lt;p&gt;它们只是被藏起来了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-context-value-is-chain/cover.png" alt="context.WithValue 链式查找"&gt;&lt;/p&gt;
&lt;p&gt;上篇我们拆了 &lt;code&gt;WithTimeout&lt;/code&gt;：&lt;code&gt;cancel&lt;/code&gt; 不是超时按钮，而是资源释放按钮。再往下看，就会撞上 &lt;code&gt;context&lt;/code&gt; 里另一个最容易被滥用的口子：&lt;code&gt;WithValue&lt;/code&gt;。&lt;/p&gt;
&lt;p&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;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&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;userIDKey&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userID&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;/p&gt;
&lt;p&gt;这正是危险的地方。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WithValue&lt;/code&gt; 不是让你逃开函数签名的后门。它在源码里也不是一个 map。每调用一次，它只是给原来的 context 外面再包一层。&lt;/p&gt;
&lt;p&gt;这篇把 context 系列收住：先看 &lt;code&gt;propagateCancel&lt;/code&gt; 为什么不是每层都起 goroutine，再看 &lt;code&gt;valueCtx&lt;/code&gt; 为什么是一条链，最后把 &lt;code&gt;Background&lt;/code&gt;、&lt;code&gt;TODO&lt;/code&gt; 和一份检查清单讲清楚。&lt;/p&gt;
&lt;h2 id="不是每个-withcancel-都起-goroutine"&gt;不是每个 WithCancel 都起 goroutine&lt;/h2&gt;
&lt;p&gt;先补一个容易被误传的细节。&lt;/p&gt;
&lt;p&gt;有人一听 &lt;code&gt;context&lt;/code&gt; 会向下传播取消，就下意识以为：每调用一次 &lt;code&gt;WithCancel&lt;/code&gt;，标准库是不是都要起一个 goroutine，专门监听父 context？&lt;/p&gt;
&lt;p&gt;不是。&lt;/p&gt;
&lt;p&gt;Go 1.25.4 的 &lt;code&gt;propagateCancel&lt;/code&gt; 里，路径很克制。&lt;/p&gt;</description></item></channel></rss>