<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Channel on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/channel/</link><description>Recent content in Channel on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Thu, 21 May 2026 18:30:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/channel/index.xml" rel="self" type="application/rss+xml"/><item><title>加 buffer 不是修 bug：Go channel 阻塞排查实战</title><link>https://blog.cpdd.fyi/posts/go-channel-practice-debugging/</link><pubDate>Thu, 21 May 2026 18:30:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-channel-practice-debugging/</guid><description>&lt;p&gt;goroutine 数量从 200 慢慢涨到 2 万。&lt;/p&gt;
&lt;p&gt;监控图上那条线，不陡，但一直在爬。就像水龙头没拧紧——不喷，但也不会停。&lt;/p&gt;
&lt;p&gt;你的第一反应是什么？&lt;/p&gt;
&lt;p&gt;加大 buffer。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make(chan int, 100)&lt;/code&gt; 改成 &lt;code&gt;make(chan int, 1000)&lt;/code&gt;。重启。观察。好像好了。&lt;/p&gt;
&lt;p&gt;三天后，又涨回来了。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-practice-debugging/cover.svg" alt="Go channel 阻塞排查封面"&gt;&lt;/p&gt;
&lt;p&gt;这不是运气差。这是 Go 服务最常见的线上问题之一：channel 阻塞导致 goroutine 泄漏。而&amp;quot;加 buffer&amp;quot;是社区里最流行、也最没用的解法。&lt;/p&gt;
&lt;p&gt;buffer 只是把崩溃推迟了。问题的根因——谁在等谁、谁该退出、谁该 close——一个都没解决。&lt;/p&gt;
&lt;p&gt;这篇文章做一件事：从&amp;quot;goroutine 涨了&amp;quot;这个信号出发，走一遍完整的排查路径，把 channel 的六个最常见陷阱逐个拆开。&lt;/p&gt;
&lt;p&gt;不是背 API。&lt;/p&gt;
&lt;p&gt;是下次线上出问题的时候，你知道先看什么。&lt;/p&gt;
&lt;h2 id="发现怎么知道-channel-卡住了"&gt;发现：怎么知道 channel 卡住了&lt;/h2&gt;
&lt;p&gt;排查 channel 阻塞的第一步不是看代码，是看数字。&lt;/p&gt;
&lt;h3 id="runtimenumgoroutine最简单的基线"&gt;runtime.NumGoroutine()：最简单的基线&lt;/h3&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;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&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="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;runtime&amp;#34;&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ticker&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewTicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;goroutines: %d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NumGoroutine&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="w"&gt; &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="w"&gt; &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="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;这段代码放到任何 Go 服务的 &lt;code&gt;init()&lt;/code&gt; 里，每 30 秒打一行日志。goroutine 数量只升不降，大概率有泄漏。&lt;/p&gt;</description></item><item><title>Go channel 不是高级锁，它是一种组织并发的语言</title><link>https://blog.cpdd.fyi/posts/go-channel-csp-design-philosophy/</link><pubDate>Thu, 21 May 2026 16:02:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-channel-csp-design-philosophy/</guid><description>&lt;p&gt;有人为了写得“更 Go”，把一个简单 cache 包成了 channel 协议。&lt;/p&gt;
&lt;p&gt;每次 &lt;code&gt;Get&lt;/code&gt;，先构造 request，发给 owner goroutine，再等 response channel 返回。还要处理 context、close、退出顺序、goroutine 泄漏。&lt;/p&gt;
&lt;p&gt;最后代码确实没有显式 &lt;code&gt;sync.RWMutex&lt;/code&gt; 了。&lt;/p&gt;
&lt;p&gt;但问题也来了：本来一把锁能讲清楚的事，被写成了一套小型 RPC。&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-channel-csp-design-philosophy/cover.png" alt="Go channel 设计哲学封面"&gt;&lt;/p&gt;
&lt;p&gt;Go 社区有一句被反复引用的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do not communicate by sharing memory; instead, share memory by communicating.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;很多人把它理解成：Go 不喜欢锁，channel 更高级，能用 channel 就别用 mutex。&lt;/p&gt;
&lt;p&gt;这个理解太粗了。&lt;/p&gt;
&lt;p&gt;channel 的价值不是把锁藏起来，而是把通信显式写出来。mutex 也不是“不 Go”，它就是保护共享状态的直接工具。&lt;/p&gt;
&lt;p&gt;真正要问的不是：channel 和 mutex 谁更高级？&lt;/p&gt;
&lt;p&gt;真正要问的是：你现在到底是在传东西，还是在护东西？&lt;/p&gt;
&lt;h2 id="channel-首先讲的是结构不是性能"&gt;channel 首先讲的是结构，不是性能&lt;/h2&gt;
&lt;p&gt;Rob Pike 在那场很有名的演讲《Concurrency is not Parallelism》里，一直在压一个区分：concurrency 是 structure，parallelism 是 execution。&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-channel-csp-design-philosophy/rob-pike.jpg" alt="Rob Pike"&gt;&lt;/p&gt;</description></item><item><title>Go channel 源码不是在讲队列，而是在讲 goroutine 怎么排队</title><link>https://blog.cpdd.fyi/posts/go-channel-hchan-runtime-source/</link><pubDate>Thu, 21 May 2026 15:17:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-channel-hchan-runtime-source/</guid><description>&lt;p&gt;goroutine dump 里满屏 &lt;code&gt;chan send&lt;/code&gt;，你知道它卡住了。&lt;/p&gt;
&lt;p&gt;但它到底卡在哪里？&lt;/p&gt;
&lt;p&gt;是缓冲区满了？没有 receiver？被 &lt;code&gt;select&lt;/code&gt; 挂进了等待队列？还是 channel 被 close 之后才醒过来，然后 panic？&lt;/p&gt;
&lt;p&gt;如果只停留在“channel 是 goroutine 之间通信的管道”，这些问题永远说不清。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ch &amp;lt;- v&lt;/code&gt; 这一行代码，在 Go runtime 里不是一句“把数据塞进队列”。它会走过 &lt;code&gt;hchan&lt;/code&gt;、环形缓冲区、&lt;code&gt;sendq&lt;/code&gt; / &lt;code&gt;recvq&lt;/code&gt;、&lt;code&gt;sudog&lt;/code&gt;、&lt;code&gt;gopark&lt;/code&gt;、&lt;code&gt;goready&lt;/code&gt;，最后决定一个 goroutine 是继续跑，还是挂起来等别人。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-hchan-runtime-source/cover.png" alt="Go channel hchan 运行时封面"&gt;&lt;/p&gt;
&lt;p&gt;Go 的 channel 设计长期受 CSP 思想影响。Rob Pike 在 Go 早期设计和并发模型传播里反复强调：不要通过共享内存来通信，而要通过通信来共享内存。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-channel-hchan-runtime-source/rob-pike.jpg" alt="Rob Pike"&gt;&lt;/p&gt;
&lt;p&gt;这句话听起来像哲学，但到了 runtime 里，它会变成很具体的结构：谁在等谁，数据放在哪里，哪个 goroutine 应该被唤醒。&lt;/p&gt;
&lt;p&gt;这篇只做一件事：沿着 Go 1.25.4 的 &lt;code&gt;runtime/chan.go&lt;/code&gt; 和 &lt;code&gt;runtime/select.go&lt;/code&gt;，把 channel 的几条关键路径走一遍。&lt;/p&gt;
&lt;p&gt;不是为了背源码。&lt;/p&gt;
&lt;p&gt;是为了让你下次看到 &lt;code&gt;chan send&lt;/code&gt;、&lt;code&gt;chan receive&lt;/code&gt;、&lt;code&gt;close&lt;/code&gt;、&lt;code&gt;select&lt;/code&gt; 的时候，脑子里有一张清楚的运行时地图。&lt;/p&gt;
&lt;p&gt;channel 不是无锁队列，它是带调度语义的协作原语。&lt;/p&gt;
&lt;h2 id="hchanchannel-在运行时到底长什么样"&gt;hchan：channel 在运行时到底长什么样&lt;/h2&gt;
&lt;p&gt;Go 代码里你写的是：&lt;/p&gt;</description></item></channel></rss>