<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>并发编程 on 鹿鸣青野的blog</title><link>https://www.sylgalaxy.xyz/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/</link><description>Recent content in 并发编程 on 鹿鸣青野的blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Tue, 13 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.sylgalaxy.xyz/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/index.xml" rel="self" type="application/rss+xml"/><item><title>atomic 原子操作</title><link>https://www.sylgalaxy.xyz/posts/go-03-05-atomic/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-05-atomic/</guid><description>&lt;blockquote&gt;
&lt;p&gt;C++ 的 std::atomic&lt;T&gt; 在 Go 里是 sync/atomic 包：无锁计数器、无锁标志位、无锁状态机的最优解&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sync/atomic&lt;/code&gt; 提供对内存地址的&lt;strong&gt;原子操作&lt;/strong&gt;：&lt;code&gt;Add&lt;/code&gt;（原子加）、&lt;code&gt;Load&lt;/code&gt;（原子读）、&lt;code&gt;Store&lt;/code&gt;（原子写）、&lt;code&gt;CompareAndSwap&lt;/code&gt;（比较并交换）、&lt;code&gt;Swap&lt;/code&gt;（交换）。它们作用于 &lt;code&gt;int32&lt;/code&gt; / &lt;code&gt;int64&lt;/code&gt; / &lt;code&gt;uint32&lt;/code&gt; / &lt;code&gt;uint64&lt;/code&gt; / &lt;code&gt;uintptr&lt;/code&gt; / &lt;code&gt;unsafe.Pointer&lt;/code&gt; 以及 &lt;code&gt;atomic.Value&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;和 Mutex 相比，原子操作是&lt;strong&gt;免锁（lock-free）&lt;strong&gt;的：既不阻塞、也无上下文切换开销，在「高频计数器、状态标志、一次性初始化标志」这类场景下性能明显优于锁。代价是&lt;/strong&gt;只能保护单变量&lt;/strong&gt;，且不能声明「一段代码的整齐」。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：读——&lt;code&gt;Load&lt;/code&gt;、写——&lt;code&gt;Store&lt;/code&gt;、改——&lt;code&gt;Add&lt;/code&gt; / &lt;code&gt;CompareAndSwap&lt;/code&gt;。把「多步的读-改-写」用一个 CAS 原子完成，是理解 Go 原子操作的钥匙。C++ 里的 &lt;code&gt;std::atomic&amp;lt;int&amp;gt;&lt;/code&gt; 心智模型几乎原样平移，差异只在 API 命名和哪些类型支持。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二c-对照速查表"&gt;二、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;原子整数&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::atomic&amp;lt;int&amp;gt; n{0}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;var n atomic.Int64&lt;/code&gt;（或 &lt;code&gt;var n int64&lt;/code&gt; + &lt;code&gt;atomic.AddInt64&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;Go 1.19+ 有类型化 &lt;code&gt;atomic.Int64&lt;/code&gt;，更顺手&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原子加&lt;/td&gt;
&lt;td&gt;&lt;code&gt;n.fetch_add(1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.AddInt64(&amp;amp;n, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 返回新值；&lt;code&gt;AddInt32&lt;/code&gt; / &lt;code&gt;AddUint64&lt;/code&gt; 等按类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原子读&lt;/td&gt;
&lt;td&gt;&lt;code&gt;n.load()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.LoadInt64(&amp;amp;n)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 读不写也有可能被优化异常，必须用 Load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原子写&lt;/td&gt;
&lt;td&gt;&lt;code&gt;n.store(42)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.StoreInt64(&amp;amp;n, 42)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 生硬但显式；&lt;code&gt;Load&lt;/code&gt;/&lt;code&gt;Store&lt;/code&gt; 配对使用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;比较并交换&lt;/td&gt;
&lt;td&gt;&lt;code&gt;n.compare_exchange_strong(expected, desired)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.CompareAndSwapInt64(&amp;amp;n, old, new)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;CAS 失败返回 false，循环重试是惯用法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原子交换&lt;/td&gt;
&lt;td&gt;&lt;code&gt;n.exchange(42)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.SwapInt64(&amp;amp;n, 42)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;有返回：交换前旧值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原子布尔/指针&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::atomic&amp;lt;bool&amp;gt;&lt;/code&gt; / &lt;code&gt;std::atomic&amp;lt;T*&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.Bool&lt;/code&gt;（1.19+）/ &lt;code&gt;atomic.Pointer[T]&lt;/code&gt;（1.19+）&lt;/td&gt;
&lt;td&gt;Go 类型化版本避免手写 unsafe.Pointer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;任意值原子存取&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::mutex&lt;/code&gt; 包 struct&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.Value&lt;/code&gt;（存储任意类型）&lt;/td&gt;
&lt;td&gt;Value 的 Load 要求「上次存过的类型」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存顺序&lt;/td&gt;
&lt;td&gt;&lt;code&gt;memory_order_relaxed&lt;/code&gt; / &lt;code&gt;seq_cst&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 不暴露 memory_order，原子操作只有一种语义&lt;/td&gt;
&lt;td&gt;自 Go 1.19 起内存模型正式规定：原子访问之间顺序一致（sequentially consistent）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三逐主题详解"&gt;三、逐主题详解&lt;/h2&gt;
&lt;h3 id="31-计数器addint64--adduint64"&gt;3.1 计数器：&lt;code&gt;AddInt64&lt;/code&gt; / &lt;code&gt;AddUint64&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;经典场景——无锁计数器，多 goroutine 并发累加。&lt;/p&gt;</description></item><item><title>channel 详解</title><link>https://www.sylgalaxy.xyz/posts/go-03-02-channel/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-02-channel/</guid><description>&lt;blockquote&gt;
&lt;p&gt;C++ 里你得自己造「互斥锁 + 条件变量 + 队列」才能做到的线程通信，Go 用一个 channel 就把它变成了语言级原语&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;channel（通道）是 Go 并发模型的&lt;strong&gt;核心&lt;/strong&gt;，它的口号是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;「Do not communicate by sharing memory; instead, share memory by communicating.」&lt;/strong&gt; —— 不要通过共享内存来通信，而要通过通信来共享内存。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个 channel 就是一个&lt;strong&gt;带类型的 FIFO 队列&lt;/strong&gt;，goroutine 之间通过它发送 / 接收数据，同时天然完成了&lt;strong&gt;同步&lt;/strong&gt;（谁等谁、什么时候等）与&lt;strong&gt;数据传递&lt;/strong&gt;两件事。&lt;/p&gt;
&lt;p&gt;作为 C++ 开发者，你过去写线程同步大概是这样：&lt;code&gt;std::mutex&lt;/code&gt; 保护共享数据 + &lt;code&gt;std::condition_variable&lt;/code&gt; 通知 + &lt;code&gt;std::queue&lt;/code&gt; 存数据 + 各种 &lt;code&gt;unique_lock&lt;/code&gt;。而 Go 把这三件套打包成了一个 channel：&lt;strong&gt;发送阻塞、接收阻塞、容量限制、关闭通知&lt;/strong&gt;全部内建，而且它在&lt;strong&gt;语言层面&lt;/strong&gt;保证了 channel 操作本身的线程安全。&lt;/p&gt;
&lt;p&gt;本笔记覆盖：创建、无缓冲 / 有缓冲的阻塞语义、单向 channel、关闭与 &lt;code&gt;range&lt;/code&gt; 消费、nil channel、死锁场景，以及「channel 当锁」的奇技淫巧。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二c-对照速查表"&gt;二、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;消息队列&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::queue&amp;lt;T&amp;gt;&lt;/code&gt; + &lt;code&gt;std::mutex&lt;/code&gt; + &lt;code&gt;std::condition_variable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ch := make(chan T)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 一条语句搞定，且是语言级原语&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;无缓冲阻塞队列&lt;/td&gt;
&lt;td&gt;手写「空/满都等条件变量」的队列&lt;/td&gt;
&lt;td&gt;&lt;code&gt;make(chan T)&lt;/code&gt;（默认无缓冲）&lt;/td&gt;
&lt;td&gt;无缓冲 channel 天然同步：发=等收&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;有缓冲队列&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::queue&lt;/code&gt; + 信号量限制&lt;/td&gt;
&lt;td&gt;&lt;code&gt;make(chan T, 5)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;缓冲即容量，满则发送阻塞&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;发送操作&lt;/td&gt;
&lt;td&gt;&lt;code&gt;queue.push(x); cv.notify_one();&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ch &amp;lt;- x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;-&lt;/code&gt; 方向右：发送&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接收操作&lt;/td&gt;
&lt;td&gt;&lt;code&gt;queue.pop();&lt;/code&gt;（空时等 cv）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x := &amp;lt;-ch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;-&lt;/code&gt; 方向左：接收&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;队列空时接收&lt;/td&gt;
&lt;td&gt;条件变量等待 + 谓词循环&lt;/td&gt;
&lt;td&gt;阻塞直到有值&lt;/td&gt;
&lt;td&gt;Go 不需要手写谓词循环&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;队列满时发送&lt;/td&gt;
&lt;td&gt;信号量 / 容量检查&lt;/td&gt;
&lt;td&gt;阻塞直到有空位&lt;/td&gt;
&lt;td&gt;缓冲满才阻塞，缓冲空才阻塞&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生命周期管理&lt;/td&gt;
&lt;td&gt;队列对象手动析构、小心悬垂&lt;/td&gt;
&lt;td&gt;&lt;code&gt;close(ch)&lt;/code&gt;，只能发送方关闭&lt;/td&gt;
&lt;td&gt;关闭后 range 自动结束&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;多读多写&lt;/td&gt;
&lt;td&gt;一把锁 + 一个队列满危险&lt;/td&gt;
&lt;td&gt;内建线程安全，任意并发收发&lt;/td&gt;
&lt;td&gt;Go 收发都允许并发，无需额外锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;非阻塞操作&lt;/td&gt;
&lt;td&gt;try_lock / 检查队列非空&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select + default&lt;/code&gt;（下篇讲）&lt;/td&gt;
&lt;td&gt;select 是非阻塞收发的标准姿势&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三逐主题详解"&gt;三、逐主题详解&lt;/h2&gt;
&lt;h3 id="31-创建-channelmake"&gt;3.1 创建 channel：&lt;code&gt;make&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 无缓冲 channel：接收方和发送方必须同时准备好，相当于一次「握手」&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ch1&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 有缓冲 channel：容量为 5，缓冲不满时发送不阻塞&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ch2&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 只声明不 make 的 channel 是 nil channel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ch3&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;float64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ch3&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// true（nil channel 不能收发，见 3.7）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ch1&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ch2&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// false, false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;ch1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;ch2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;C++ 对照&lt;/strong&gt;：&lt;code&gt;make(chan int)&lt;/code&gt; 既有「分配」又有「初始化」两层含义，像 &lt;code&gt;std::make_shared&amp;lt;T&amp;gt;()&lt;/code&gt;。注意这里返回的是 &lt;strong&gt;channel 本身&lt;/strong&gt;，不是一个指针——但你如果 &lt;code&gt;ch2 := make(chan int)&lt;/code&gt; 再 &lt;code&gt;ch2 = ch2&lt;/code&gt;，传递的都是同一个底层通道的引用，多个 goroutine 共享它时不需要再加锁。&lt;/p&gt;</description></item><item><title>context 超时取消与传值</title><link>https://www.sylgalaxy.xyz/posts/go-03-06-context/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-06-context/</guid><description>&lt;blockquote&gt;
&lt;p&gt;C++ 没有直接对应的东西——它最接近 std::jthread 的 stop_token：一个穿过调用链的「取消令牌 + 截止时间 + 键值袋」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;context.Context&lt;/code&gt;（标准库 &lt;code&gt;context&lt;/code&gt; 包）是 Go 并发中&lt;strong&gt;传播取消信号、截止时间与请求级数据&lt;/strong&gt;的载体。它贯穿整个标准库（HTTP 请求、数据库、RPC），是每一个「可能被取消的耗时代码」的标配第一参数。&lt;/p&gt;
&lt;p&gt;它解决 C++ 里三个长期痛点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;传递取消信号&lt;/strong&gt;：深调用链中，任何一层想取消整条链，怎么办？C++ 用 &lt;code&gt;std::atomic&amp;lt;bool&amp;gt;&lt;/code&gt; 到处传 + 检查，或 &lt;code&gt;std::jthread&lt;/code&gt; 的 &lt;code&gt;stop_token&lt;/code&gt;。Go 里就是 &lt;code&gt;ctx.Done()&lt;/code&gt; + 广播。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递截止时间&lt;/strong&gt;：每个函数都检查「现在是不是超时了」很烦。Go 里 &lt;code&gt;context.WithDeadline&lt;/code&gt; 一路带过去，子任务自己查 &lt;code&gt;ctx.Done()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传递请求级数据&lt;/strong&gt;：分布式的 trace id、用户信息等「横切数据」，Go 用 &lt;code&gt;WithValue&lt;/code&gt; 携带，避免每个函数都加参。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：&lt;strong&gt;&lt;code&gt;context.Context&lt;/code&gt; 只做三件事：取消（Cancel）、截止（Deadline/Timeout）、传值（Value）。&lt;/strong&gt; 它不是「任意全局状态袋」，不该往里塞业务对象；也不是给每个函数都强加的仪式，而是给「可能被取消的耗时代码」用的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二c-对照速查表"&gt;二、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;取消令牌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::stop_token&lt;/code&gt; / 手写 &lt;code&gt;atomic&amp;lt;bool&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.Done()&lt;/code&gt; + 树形传播&lt;/td&gt;
&lt;td&gt;Go 一次 cancel 广播整棵子树&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;超时取消&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::stoptoken&lt;/code&gt; + 手算时间&lt;/td&gt;
&lt;td&gt;&lt;code&gt;context.WithTimeout(ctx, 3*time.Second)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;一行完成「到时自动 cancel」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;截止时间&lt;/td&gt;
&lt;td&gt;&lt;code&gt;steady_clock::now() + d&lt;/code&gt; 自己算&lt;/td&gt;
&lt;td&gt;&lt;code&gt;context.WithDeadline(ctx, t)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 语义是绝对时刻&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;手动取消&lt;/td&gt;
&lt;td&gt;手写 &lt;code&gt;atomic&amp;lt;bool&amp;gt;&lt;/code&gt; + 到处检查&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cancelFunc()&lt;/code&gt;（WithCancel 返回）&lt;/td&gt;
&lt;td&gt;cancel 是「广播」，不只本层&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;关联请求数据&lt;/td&gt;
&lt;td&gt;线程局部 / 全局 map（危险）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;context.WithValue(parent, key, val)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 类型安全 + 随 ctx 走&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;goroutine 收尾&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jthread.request_stop()&lt;/code&gt; + join&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select { case &amp;lt;-ctx.Done(): ... }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 惯例：非主路径用 select 监听取消&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;取消原因&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ctx.Err()&lt;/code&gt; → &lt;code&gt;context.Canceled&lt;/code&gt; / &lt;code&gt;DeadlineExceeded&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;区分「被取消」与「超时」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下文传播&lt;/td&gt;
&lt;td&gt;无（要么传 token，要么全局）&lt;/td&gt;
&lt;td&gt;函数第一参数约定&lt;/td&gt;
&lt;td&gt;Go 靠约定 + vet 检查不传 nil&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三逐主题详解"&gt;三、逐主题详解&lt;/h2&gt;
&lt;h3 id="31-两个根contextbackground-与-contexttodo"&gt;3.1 两个根：&lt;code&gt;context.Background()&lt;/code&gt; 与 &lt;code&gt;context.TODO()&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Background：程序的「根上下文」，所有 context 树的起点&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Background&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Background Err =&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Err&lt;/span&gt;()) &lt;span style="color:#75715e"&gt;// nil（永远不会被取消）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Background Done =&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Done&lt;/span&gt;()) &lt;span style="color:#75715e"&gt;// nil channel（永不关闭）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// TODO：还没想好用哪个 context 时的占位，语义同 Background&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;todo&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TODO&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;TODO 等价于 Background:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;todo&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Err&lt;/span&gt;() &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;ctx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;todo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;规则&lt;/strong&gt;：&lt;/p&gt;</description></item><item><title>goroutine</title><link>https://www.sylgalaxy.xyz/posts/go-03-01-goroutine/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-01-goroutine/</guid><description>&lt;blockquote&gt;
&lt;p&gt;Go 并发的基本执行单元：比线程轻量十倍的绿色协程，由 Go 运行时调度&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一c-对照速查表"&gt;一、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;并发执行单元&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::thread&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go func()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;goroutine 由运行时管理，不映射到 OS 线程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;创建成本&lt;/td&gt;
&lt;td&gt;~1MB 栈 + 系统调用&lt;/td&gt;
&lt;td&gt;~2KB 栈，动态增长&lt;/td&gt;
&lt;td&gt;goroutine 创建成本极低，可轻松创建上万&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;启动方式&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::thread t(func); t.join();&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go func()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 无需手动 join，用 channel/WaitGroup 同步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调度模型&lt;/td&gt;
&lt;td&gt;1:1 映射 OS 线程&lt;/td&gt;
&lt;td&gt;M:N 调度（GMP 模型）&lt;/td&gt;
&lt;td&gt;运行时把 N 个 goroutine 调度到 M 个线程上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;栈大小&lt;/td&gt;
&lt;td&gt;固定（通常 1~8MB）&lt;/td&gt;
&lt;td&gt;初始 2KB，按需增长/收缩&lt;/td&gt;
&lt;td&gt;同一地址空间下可容纳海量 goroutine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;终止方式&lt;/td&gt;
&lt;td&gt;thread 析构或 detach&lt;/td&gt;
&lt;td&gt;主 goroutine 结束则全部退出&lt;/td&gt;
&lt;td&gt;goroutine 是 &amp;ldquo;fire and forget&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：goroutine 是 Go 并发的基石。创建一个 goroutine 只用一个 &lt;code&gt;go&lt;/code&gt; 关键字，运行时自动处理调度、栈管理和线程复用。但 goroutine 之间需要通过 &lt;strong&gt;channel&lt;/strong&gt; 或 &lt;strong&gt;sync 包&lt;/strong&gt; 同步，否则会出现数据竞争。&lt;/p&gt;</description></item><item><title>select 多路复用</title><link>https://www.sylgalaxy.xyz/posts/go-03-03-select/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-03-select/</guid><description>&lt;blockquote&gt;
&lt;p&gt;一个 goroutine 同时监听多个 channel 的「事件循环」：C++/epoll 监听多个 fd 的那个心智模型，Go 把它内建成了 select 语句&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;select&lt;/code&gt; 是 Go 语法层面的&lt;strong&gt;多路复用（multiplexing）&lt;strong&gt;原语：它让一个 goroutine 同时&lt;/strong&gt;等待多个 channel 上的收发操作&lt;/strong&gt;，只要其中一个就绪，就走对应的分支。&lt;/p&gt;
&lt;p&gt;对于 C++ 开发者，最贴切的类比是 &lt;strong&gt;Linux epoll / poll&lt;/strong&gt;：你不想为每个 socket 各开一个线程去阻塞读，而是用一个线程同时监听一堆 fd，哪个有事件处理哪个。&lt;code&gt;select&lt;/code&gt; 就是「 goroutine 版本的 epoll」——但它更进一步：它还能同时做&lt;strong&gt;非阻塞尝试&lt;/strong&gt;（default）、&lt;strong&gt;定时超时&lt;/strong&gt;（&lt;code&gt;time.After&lt;/code&gt;）、&lt;strong&gt;动态禁用分支&lt;/strong&gt;（nil channel）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：&lt;code&gt;select&lt;/code&gt; 是「组合 channel 为复杂同步逻辑」的胶水。没有 select，一个 goroutine 只能死等一个 channel；有了 select，你才能写超时、写取消、写事件循环、写多路汇流。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二c-对照速查表"&gt;二、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;多路监听&lt;/td&gt;
&lt;td&gt;&lt;code&gt;epoll_wait(fds, ...)&lt;/code&gt; / &lt;code&gt;poll()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select { case &amp;lt;-ch1: ... }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 是对 channel 通道复用，C++ 是对 fd 复用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;同时等两个事件&lt;/td&gt;
&lt;td&gt;一个线程一个 fd 各睡一觉 / epoll&lt;/td&gt;
&lt;td&gt;一个 goroutine 同时等多个 case&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select&lt;/code&gt; 内建，代码直白得多&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;随机公平选择&lt;/td&gt;
&lt;td&gt;epoll 按就绪顺序返回&lt;/td&gt;
&lt;td&gt;多 case 就绪时&lt;strong&gt;随机&lt;/strong&gt;选一个&lt;/td&gt;
&lt;td&gt;Go 保证公平：随机而非先后&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;非阻塞尝试&lt;/td&gt;
&lt;td&gt;&lt;code&gt;poll(fd, timeout=0)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select { case ...: default: }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt; = 都不就绪立即走&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;超时等待&lt;/td&gt;
&lt;td&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 的 timeout 参数&lt;/td&gt;
&lt;td&gt;&lt;code&gt;select&lt;/code&gt; + &lt;code&gt;time.After(1s)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;超时是普通 channel，不是特殊机制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;收发都监听&lt;/td&gt;
&lt;td&gt;读 / 写事件分别注册&lt;/td&gt;
&lt;td&gt;一个 select 里既有发送又有接收 case&lt;/td&gt;
&lt;td&gt;语法上收发对称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;监听无限器&lt;/td&gt;
&lt;td&gt;服务端 accept 线程循环&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for { select { ... } }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;事件循环惯用法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;特定事件暂时屏蔽&lt;/td&gt;
&lt;td&gt;取消注册 fd&lt;/td&gt;
&lt;td&gt;case 的 channel 置 nil → 永久阻塞&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;nil channel 妙用&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三逐主题详解"&gt;三、逐主题详解&lt;/h2&gt;
&lt;h3 id="31-select-基本语法"&gt;3.1 select 基本语法&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ch1&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ch2&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make(&lt;span style="color:#66d9ef"&gt;chan&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 两个 goroutine 各自往不同 channel 发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() { &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Millisecond&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;ch1&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;来自 ch1&amp;#34;&lt;/span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() { &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;300&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Millisecond&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;ch2&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;来自 ch2&amp;#34;&lt;/span&gt; }()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// select 会阻塞，直到某个 case 就绪&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ch1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;收到:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// 大概率先到 ch1（100ms 更快）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;case&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ch2&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;收到:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;msg&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;要点&lt;/strong&gt;：&lt;/p&gt;</description></item><item><title>sync 包（WaitGroup / Mutex / Once）</title><link>https://www.sylgalaxy.xyz/posts/go-03-04-sync-waitgroup-mutex-once/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-04-sync-waitgroup-mutex-once/</guid><description>&lt;blockquote&gt;
&lt;p&gt;C++ 的 std::thread::join、std::mutex、std::once_flag，Go 用 sync 包给了一套轻量且更安全的等价物&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sync&lt;/code&gt; 包是 Go 标准库的&lt;strong&gt;同步原语仓库&lt;/strong&gt;，核心成员：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;th&gt;C++ 对应&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;等一批 goroutine 全部结束&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::thread::join&lt;/code&gt;（一次等多个）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync.Mutex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;互斥锁&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::mutex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync.RWMutex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读写锁&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::shared_mutex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync.Once&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;只执行一次（懒加载 / 单例）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::once_flag&lt;/code&gt; + &lt;code&gt;std::call_once&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync.Map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;并发安全的 map（特化场景）&lt;/td&gt;
&lt;td&gt;手写 lock + &lt;code&gt;std::map&lt;/code&gt; / 无直接对应&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sync.Cond&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;条件变量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::condition_variable&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：Go 的哲学是「尽量用 channel」，但&lt;strong&gt;同步原语&lt;/strong&gt;（锁 / 分组等待 / 只跑一次）仍然有明确位置。与 C++ 的锁相比，Go 的锁&lt;strong&gt;不可拷贝&lt;/strong&gt;、有 &lt;code&gt;defer&lt;/code&gt; 配合的极简用法、内置检测工具，心智负担小得多。本笔记重点讲 WaitGroup、Mutex / RWMutex、Once，并简述 errgroup。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二c-对照速查表"&gt;二、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;等待线程结束&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t.join()&lt;/code&gt;（单个）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wg := sync.WaitGroup{}; wg.Add(1); defer wg.Done(); wg.Wait()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;WaitGroup 一次等&lt;strong&gt;一批&lt;/strong&gt;，Add 要提前&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;互斥锁&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::mutex m; m.lock(); m.unlock();&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;var mu sync.Mutex; mu.Lock(); defer mu.Unlock()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 惯例用 &lt;code&gt;defer&lt;/code&gt; 解锁，防漏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;锁唯一性&lt;/td&gt;
&lt;td&gt;lock 传递没意义&lt;/td&gt;
&lt;td&gt;结构体字段还是值？&lt;strong&gt;拷贝锁 = 复制已锁状态 → 编译常见错&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go 锁内含 &lt;code&gt;noCopy&lt;/code&gt;，拷贝会被提示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;只初始化一次&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::once_flag&lt;/code&gt; + &lt;code&gt;std::call_once&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;var once sync.Once; once.Do(func(){...})&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go 写进一个 &lt;code&gt;Do&lt;/code&gt;，天然幂等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;读写锁&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::shared_mutex&lt;/code&gt;（C++17）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sync.RWMutex&lt;/code&gt; + &lt;code&gt;RLock()/RUnlock()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;读读并发、写写互斥、写独占&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可重入&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::recursive_mutex&lt;/code&gt; 才行&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mutex 不可重入&lt;/strong&gt;，同 goroutine 再锁 = 自锁死&lt;/td&gt;
&lt;td&gt;Go 明确拒绝重入，倒逼更好的设计&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;条件变量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::condition_variable&lt;/code&gt; + predicate 循环&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sync.Cond&lt;/code&gt;（少见）或直接 channel&lt;/td&gt;
&lt;td&gt;Go 社区倾向 channel 代替 cond&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分组错误传播&lt;/td&gt;
&lt;td&gt;手写 &lt;code&gt;std::future&lt;/code&gt; 收集异常&lt;/td&gt;
&lt;td&gt;&lt;code&gt;errgroup.Group&lt;/code&gt;（&lt;code&gt;golang.org/x/sync&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;Go 有现成库，等全部且带首个错误&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三逐主题详解"&gt;三、逐主题详解&lt;/h2&gt;
&lt;h3 id="31-syncwaitgroup等一批-goroutine"&gt;3.1 sync.WaitGroup：等一批 goroutine&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;sync&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;worker&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;wg&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WaitGroup&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;wg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Done&lt;/span&gt;() &lt;span style="color:#75715e"&gt;// 结束前把计数器减 1（defer 保证即使中途 panic 也会减，避免死锁）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;worker %d 开始\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Millisecond&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// 模拟干活&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;worker %d 结束\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;wg&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WaitGroup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;wg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// ⚠️ Add 必须在 go 之前！计数器先 +1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;go&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;worker&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;, &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;wg&lt;/span&gt;) &lt;span style="color:#75715e"&gt;// 注意传指针：WaitGroup 内部要改计数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;wg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Wait&lt;/span&gt;() &lt;span style="color:#75715e"&gt;// 阻塞直到所有 Done，相当于「等这一批全部 join」&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;所有 worker 都结束了&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;WaitGroup 规则&lt;/strong&gt;：&lt;/p&gt;</description></item><item><title>并发模式（worker pool / 扇入扇出 / pipeline）</title><link>https://www.sylgalaxy.xyz/posts/go-03-07-workerpool-pipeline/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-07-workerpool-pipeline/</guid><description>&lt;blockquote&gt;
&lt;p&gt;C++ 里要自己造轮子的线程池、任务队列、流水线，Go 用 channel 组合出几种固定范式，一次学会受用终生&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;「并发模式」不是语法，而是被反复验证有效的&lt;strong&gt;编排范式&lt;/strong&gt;。Go 里它们几乎全由 channel + goroutine 组合而成：worker pool（工人池）、fan-out（扇出）、fan-in（扇入 / 汇聚）、pipeline（流水线）、限速器（rate limit）、错误传播。&lt;/p&gt;
&lt;p&gt;对于 C++ 开发者，这些模式你多半见过、也手动实现过：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;C++ 里怎么做的&lt;/th&gt;
&lt;th&gt;痛点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;线程池&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::thread&lt;/code&gt; 数组 + &lt;code&gt;std::queue&lt;/code&gt; 任务 + mutex 守卫&lt;/td&gt;
&lt;td&gt;线程生命周期、任务派发、锁竞争都要自己管&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;扇出&lt;/td&gt;
&lt;td&gt;把一个大任务拆 N 份发给 N 线程&lt;/td&gt;
&lt;td&gt;怎么「等所有分片」+ 汇总&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;流水线&lt;/td&gt;
&lt;td&gt;每个 stage 一个线程 + 队列 + condvar&lt;/td&gt;
&lt;td&gt;关停时机难管&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;限速&lt;/td&gt;
&lt;td&gt;令牌桶 / 手写信号量&lt;/td&gt;
&lt;td&gt;又要造一个计数器 + 条件变量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Go 里这些平均不到 30 行，且&lt;strong&gt;语义全部显式可见&lt;/strong&gt;。本笔记不讲抽象，直接给 6 个能 &lt;code&gt;go run&lt;/code&gt; 的模板。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：所有模式都建立在「&lt;strong&gt;channel 即队列 + 同步&lt;/strong&gt;」之上。模式 = 固定形状的 channel 拓扑 + 一组约定（谁 close、谁 range、谁 Wait）。&lt;/p&gt;</description></item><item><title>数据竞争与 race 检测</title><link>https://www.sylgalaxy.xyz/posts/go-03-08-race/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.sylgalaxy.xyz/posts/go-03-08-race/</guid><description>&lt;blockquote&gt;
&lt;p&gt;C++ 的 TSan / helgrind 你在用，Go 内置了一个更好使的：&lt;code&gt;go run -race&lt;/code&gt; 一键找数据竞争&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="一简述"&gt;一、简述&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;数据竞争（data race）&lt;/strong&gt;：两个或多个 goroutine &lt;strong&gt;并发访问同一内存位置&lt;/strong&gt;，且&lt;strong&gt;至少一个是写操作&lt;/strong&gt;，且没有任何同步（锁 / channel / 原子操作）约束它们之间的顺序。数据竞争的结果是&lt;strong&gt;未定义行为&lt;/strong&gt;（UB）：读到撕裂值、编译器优化后行为诡异、crash、或者——最恼人的——&lt;strong&gt;偶尔才出错&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;C++ 里你熟知的工具：&lt;strong&gt;ThreadSanitizer（TSan）&lt;/strong&gt;、&lt;code&gt;-fsanitize=thread&lt;/code&gt;、helgrind。Go 的对应物是内置的 &lt;strong&gt;race detector&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go run -race main.go &lt;span style="color:#75715e"&gt;# 直接跑并检测&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go test -race ./... &lt;span style="color:#75715e"&gt;# 测试时检测（推荐）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go build -race main.go &lt;span style="color:#75715e"&gt;# 构建带检测的二进制（性能有损，仅调试）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心要点&lt;/strong&gt;：Go race detector 不是「预防工具」，而是「&lt;strong&gt;逮现行犯&lt;/strong&gt;」的探针——它监测每个内存访问，报告「两个 goroutine 同时访问同一地址」的现场。&lt;strong&gt;加了 -race，不等于程序没问题；没加 -race，问题照样存在。&lt;/strong&gt; 修复手段三件套：Mutex、atomic、channel 传值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="二c-对照速查表"&gt;二、C++ 对照速查表&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;C++&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;关键差异&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;数据竞争定义&lt;/td&gt;
&lt;td&gt;两个线程无同步访问同一地址&lt;/td&gt;
&lt;td&gt;同，goroutine 版&lt;/td&gt;
&lt;td&gt;语义一致：至少一写 + 无同步&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;检测工具&lt;/td&gt;
&lt;td&gt;TSan / &lt;code&gt;-fsanitize=thread&lt;/code&gt; / helgrind&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go run -race&lt;/code&gt; / &lt;code&gt;go test -race&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Go &lt;strong&gt;内置&lt;/strong&gt;，零配置，一行参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;检测原理&lt;/td&gt;
&lt;td&gt;影子内存 + happens-before 分析&lt;/td&gt;
&lt;td&gt;同样基于 happens-before 的 shadow 内存&lt;/td&gt;
&lt;td&gt;实现同源（Go 4 个字节内存配 8 字节影子）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;共享计数器&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::atomic&amp;lt;int&amp;gt;&lt;/code&gt; / mutex&lt;/td&gt;
&lt;td&gt;&lt;code&gt;atomic.AddInt64&lt;/code&gt;（首选）&lt;/td&gt;
&lt;td&gt;Go 原子 / 锁 / channel 三种解法都有&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;map 并发写&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::unordered_map&lt;/code&gt; + mutex&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;map 并发写 = 直接 panic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go 有额外保护：写中读/写检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修复手段&lt;/td&gt;
&lt;td&gt;锁 / 原子 / 无共享&lt;/td&gt;
&lt;td&gt;Mutex / atomic / channel 传值&lt;/td&gt;
&lt;td&gt;第三种（传值）是 Go 特色&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存模型文档&lt;/td&gt;
&lt;td&gt;cppreference memory_order&lt;/td&gt;
&lt;td&gt;&lt;a href="https://go.dev/ref/mem"&gt;The Go Memory Model&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Go 文档短得多，先读「happen-before」&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;检测开销&lt;/td&gt;
&lt;td&gt;慢 5~15 倍&lt;/td&gt;
&lt;td&gt;运行慢 5&lt;del&gt;10 倍，内存多 5&lt;/del&gt;10 倍&lt;/td&gt;
&lt;td&gt;都只该在测试/CI 用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="三逐主题详解"&gt;三、逐主题详解&lt;/h2&gt;
&lt;h3 id="31-什么是数据竞争"&gt;3.1 什么是数据竞争&lt;/h3&gt;
&lt;p&gt;判定为数据竞争需要三条同时成立：&lt;/p&gt;</description></item></channel></rss>