<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golang on Chen Shungen</title><link>https://chenshungen.cn/tags/golang/</link><description>Recent content in Golang on Chen Shungen</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Mon, 20 Apr 2026 14:27:00 +0800</lastBuildDate><atom:link href="https://chenshungen.cn/tags/golang/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 扩展并发原语与分布式并发</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-extended-concurrency/</link><pubDate>Mon, 20 Apr 2026 14:27:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-extended-concurrency/</guid><description>&lt;p>前面几篇我们讲了标准库的并发原语、atomic 和 Channel，掌握这些已经能解决 80% 的并发问题。但要进一步提升并发编程能力，还需要了解 &lt;strong>扩展并发原语&lt;/strong> 和 &lt;strong>分布式并发原语&lt;/strong>。这篇文章分两部分：上半部分讲 Go 官方和社区提供的进程内扩展原语（Semaphore、SingleFlight、ErrGroup、CyclicBarrier），下半部分讲基于 etcd 的分布式并发原语（Leader 选举、分布式锁、队列、栅栏、STM）。&lt;/p>
&lt;hr>
&lt;h1 id="上篇扩展并发原语">上篇：扩展并发原语&lt;/h1>
&lt;h2 id="一semaphore信号量">一、Semaphore（信号量）&lt;/h2>
&lt;h3 id="什么是信号量">什么是信号量？&lt;/h3>
&lt;p>信号量（Semaphore）是荷兰计算机科学家 Edsger Dijkstra 在 1963 年提出的并发原语，用来 &lt;strong>控制多个 goroutine 同时访问多个资源&lt;/strong>。&lt;/p>
&lt;p>它的核心是一个计数器（0 到 n），表示当前可用的资源数量：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 信号量模型 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├────────────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 初始资源数 = n │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Acquire (P 操作)：计数器 - 1 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 计数器 &amp;gt; 0 → 成功获取，继续执行 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 计数器 = 0 → 阻塞等待，直到有资源释放 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Release (V 操作)：计数器 + 1 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 唤醒一个等待中的 goroutine │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;blockquote>
&lt;p>&lt;strong>P/V 操作&lt;/strong> 的名称来自荷兰语：P（Proberen，尝试）减少信号量，V（Verhogen，增加）增加信号量。&lt;/p></description></item><item><title>Go 内存模型</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-memory-model/</link><pubDate>Mon, 20 Apr 2026 14:25:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-memory-model/</guid><description>&lt;p>前面的文章我们讲了 Mutex、Channel、atomic 等各种并发工具，但有一个更底层的问题我们还没回答——&lt;strong>一个 goroutine 写入的值，另一个 goroutine 什么时候能看到？&lt;/strong> 这就是 Go 内存模型（The Go Memory Model）要回答的问题。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>注意&lt;/strong>：这里的&amp;quot;内存模型&amp;quot;不是内存分配/回收，而是 &lt;strong>并发环境下变量的可见性规则&lt;/strong>。&lt;/p>&lt;/blockquote>
&lt;h2 id="一为什么需要内存模型">一、为什么需要内存模型？&lt;/h2>
&lt;p>直觉上，代码应该按你写的顺序执行。但现实中有两个因素会打破这种直觉：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 打破执行顺序的两个因素 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 1. 编译器重排 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 编译器为了优化性能，可能调整指令执行顺序 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 只要在单 goroutine 内语义不变，就允许重排 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 2. CPU 多级缓存 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 每个 CPU 核有自己的 L1/L2 缓存 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 一个核写入的值不一定立即对其他核可见 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>看一个例子：&lt;/p></description></item><item><title>Go 并发原语 - Channel</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-channel/</link><pubDate>Mon, 20 Apr 2026 14:00:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-channel/</guid><description>&lt;p>Channel 是 Go 语言内建的 &lt;strong>first-class 类型&lt;/strong>，也是 Go 与众不同的特性之一。它不是通过库提供的——而是直接内置在语言规范中，地位之高在编程语言中比较罕见。Channel 的设计源自 CSP（Communicating Sequential Process）模型：&lt;strong>不要通过共享内存来通信，要通过通信来共享内存。&lt;/strong>&lt;/p>
&lt;h2 id="一channel-基本用法">一、Channel 基本用法&lt;/h2>
&lt;h3 id="声明和初始化">声明和初始化&lt;/h3>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 声明&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">ch&lt;/span> &lt;span class="kd">chan&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 双向 channel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">sendCh&lt;/span> &lt;span class="kd">chan&lt;/span>&lt;span class="o">&amp;lt;-&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 只发送&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">recvCh&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="c1">// 只接收&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 初始化（必须用 make）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ch&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 无缓冲&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ch&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kt">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 有缓冲，容量 10&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h3 id="三种操作">三种操作&lt;/h3>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Channel 的三种操作 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ch &amp;lt;- value 发送：把 value 放入 channel │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ value := &amp;lt;-ch 接收：从 channel 取出值 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ close(ch) 关闭：关闭 channel │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h3 id="无缓冲-vs-有缓冲">无缓冲 vs 有缓冲&lt;/h3>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">无缓冲 channel (make(chan int))：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 发送者 channel 接收者
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │── send ────→ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (阻塞...) │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ ←── recv ────────│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (解除阻塞) ──→│──→ (收到值) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 发送和接收必须同时就绪，类似&amp;#34;面对面交接&amp;#34;。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">有缓冲 channel (make(chan int, 3))：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 发送者 buffer [_ _ _] 接收者
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │── send ────→ [v _ _] (不阻塞) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │── send ────→ [v v _] (不阻塞) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │── send ────→ [v v v] (不阻塞) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │── send ────→ [v v v] (阻塞! 满了) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ ←── recv ────────│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (解除阻塞) ──→│──→ (取出一个值) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 缓冲未满时发送不阻塞，缓冲为空时接收阻塞。&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="二channel-的行为表">二、Channel 的行为表&lt;/h2>
&lt;p>对 channel 执行不同操作，在不同状态下的行为：&lt;/p></description></item><item><title>Go 并发原语 - atomic</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-atomic/</link><pubDate>Mon, 20 Apr 2026 13:30:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-atomic/</guid><description>&lt;p>前面讲 Mutex、WaitGroup 等并发原语的实现时，你会发现它们的底层都依赖 &lt;code>sync/atomic&lt;/code> 包的原子操作。原子操作是并发编程的最底层基石——比锁更轻量、比 Channel 更快，适合特定场景下的高性能并发控制。这一篇我们专门来讲 atomic。&lt;/p>
&lt;h2 id="一什么是原子操作">一、什么是原子操作？&lt;/h2>
&lt;p>原子操作是指 &lt;strong>不会被中断的操作&lt;/strong>。在其他 goroutine 看来，原子操作要么已经完成，要么还没开始，不会看到&amp;quot;执行了一半&amp;quot;的中间状态。&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">普通操作（count++，三步）： 原子操作（atomic.AddInt64，一步）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> goroutine A goroutine B goroutine A goroutine B
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 读取 count=5 │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ 读取 count=5 │ AddInt64(+1) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ count=5+1=6 │ │ (不可分割) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 写回 count=6 │ count=5+1=6 │ │ AddInt64(+1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ 写回 count=6 │ │ (不可分割)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 结果：6（丢失一次+1）💀 结果：7（正确）✅&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>原子操作为什么能保证这一点？因为 CPU 硬件直接支持：&lt;/p></description></item><item><title>Go 并发原语 - Context</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-context/</link><pubDate>Mon, 20 Apr 2026 13:00:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-context/</guid><description>&lt;p>并发编程中，除了&amp;quot;互斥访问&amp;quot;和&amp;quot;等待通知&amp;quot;之外，还有一类核心需求——&lt;strong>取消和超时控制&lt;/strong>。比如：HTTP 请求超时了，下游所有 goroutine 都应该停止工作；用户取消了操作，正在进行的数据库查询应该被中断。Go 标准库的 &lt;code>context&lt;/code> 包就是为了解决这类问题而生的。&lt;/p>
&lt;h2 id="一context-解决什么问题">一、Context 解决什么问题？&lt;/h2>
&lt;p>假设一个 HTTP 请求触发了多个 goroutine 并行处理：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> HTTP 请求
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── goroutine: 查询数据库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── goroutine: 调用下游 API
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── goroutine: 读取缓存&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>如果请求超时了（比如客户端断开连接），这三个 goroutine 应该如何知道&amp;quot;不用干了&amp;quot;？&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">没有 Context： 有 Context：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 请求超时 请求超时
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 数据库查询还在跑... │── ctx.Done() 触发
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 下游 API 还在等... │── 所有 goroutine 收到信号
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 缓存读取还在等... │── 立即停止，释放资源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 资源浪费 💀 └── 干净退出 ✅&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>Context 提供了三个核心能力：&lt;/p></description></item><item><title>Go 并发原语 - Pool</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-pool/</link><pubDate>Mon, 20 Apr 2026 12:30:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-pool/</guid><description>&lt;p>Go 是自动垃圾回收的语言，创建对象没有回收的心理负担。但如果你要开发高性能应用，就必须关注 GC 的影响——大量创建堆上的对象，会增加 GC 标记的时间和 STW（stop-the-world）的开销。&lt;strong>对象池&lt;/strong> 是一种经典的优化手段：把不用的对象回收起来复用，减少堆分配和 GC 压力。Go 标准库提供了 &lt;code>sync.Pool&lt;/code> 来实现这个目的。&lt;/p>
&lt;p>这篇文章我们先讲 sync.Pool 的用法和原理，再扩展到连接池和 Worker Pool。&lt;/p>
&lt;h2 id="一syncpool-基本用法">一、sync.Pool 基本用法&lt;/h2>
&lt;p>sync.Pool 的 API 非常简洁：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ sync.Pool │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ New func() any 创建新对象的工厂函数（Pool 为空时调用）│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Get() any 从池中取出一个对象 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Put(x any) 把对象放回池中 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>一个典型的用法——复用 &lt;code>bytes.Buffer&lt;/code>：&lt;/p></description></item><item><title>Go 并发原语 - Map</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-map/</link><pubDate>Mon, 20 Apr 2026 12:00:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-map/</guid><description>&lt;p>前面几篇我们聊了各种同步原语，这一篇聊一个更贴近日常开发的话题——&lt;strong>map 的并发安全&lt;/strong>。Go 内建的 map 类型不是线程安全的，并发读写会直接 panic。那怎么办？这一篇我们从内建 map 的基本用法和陷阱开始，逐步讲到加锁方案和标准库的 &lt;code>sync.Map&lt;/code>。&lt;/p>
&lt;h2 id="一内建-map-基本用法">一、内建 map 基本用法&lt;/h2>
&lt;p>Go 的 map 是内建的哈希表类型：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">m&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="kt">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="kt">int&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">m&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;age&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">28&lt;/span> &lt;span class="c1">// 写入&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">v&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;age&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1">// 读取&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">ok&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">m&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1">// 读取 + 检查是否存在&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">m&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;age&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 删除&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>key 的类型必须是 &lt;strong>可比较的（comparable）&lt;/strong>——能用 &lt;code>==&lt;/code> 和 &lt;code>!=&lt;/code> 比较：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ✅ 可以做 key 的类型 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ bool、整数、浮点数、复数、字符串 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 指针、Channel、接口 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 所有字段都可比较的 struct │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 元素都可比较的数组 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 不能做 key 的类型 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├──────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ slice、map、函数 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h3 id="struct-做-key-的坑">struct 做 key 的坑&lt;/h3>
&lt;p>用 struct 做 key 时要特别小心——修改了 struct 字段后，就再也找不到原来的值了：&lt;/p></description></item><item><title>Go 并发原语 - Once</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-once/</link><pubDate>Mon, 20 Apr 2026 11:30:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-once/</guid><description>&lt;p>前面几篇我们聊了 Mutex、WaitGroup 和 Cond，它们各自解决不同维度的并发问题。这一篇聊一个相对简单但极其实用的原语——&lt;code>sync.Once&lt;/code>，它解决的是 &lt;strong>&amp;ldquo;确保某个操作只执行一次&amp;rdquo;&lt;/strong> 的问题，最经典的场景就是单例资源的延迟初始化。&lt;/p>
&lt;h2 id="一为什么需要-once">一、为什么需要 Once？&lt;/h2>
&lt;p>单例资源的初始化有好几种方式，按执行时机可以分成两类：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 程序启动时初始化 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 1. package 级别变量 var startTime = time.Now() │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 2. init 函数 func init() { startTime = ... } │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 3. main 函数中调用 func main() { initApp() } │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ✅ 天然线程安全（Go 保证 init 串行执行） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 无法延迟——程序启动就初始化，不管用不用得上 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 首次使用时初始化（延迟初始化） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────────────────────────────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 需要自己保证线程安全 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ✅ 按需初始化，不浪费资源 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 并发控制是个问题 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────┘&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>延迟初始化更灵活，但并发安全怎么办？最直接的想法是用 Mutex：&lt;/p></description></item><item><title>Go 并发原语 - Cond</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-cond/</link><pubDate>Mon, 20 Apr 2026 11:00:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-cond/</guid><description>&lt;p>前两篇我们聊了 Mutex 和 WaitGroup，它们分别解决&amp;quot;互斥访问&amp;quot;和&amp;quot;等待一组任务完成&amp;quot;的问题。但并发编程中还有一类需求——&lt;strong>等待某个条件满足后再继续执行&lt;/strong>。比如：队列满了，生产者要等；队列空了，消费者要等。这就是 &lt;code>sync.Cond&lt;/code>（条件变量）要解决的问题。&lt;/p>
&lt;h2 id="一为什么需要-cond">一、为什么需要 Cond？&lt;/h2>
&lt;p>假设你要实现一个限定容量的队列：队列满时生产者阻塞，队列空时消费者阻塞。没有 Cond 的话，你可能会这样写：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ❌ 轮询方案：浪费 CPU，响应慢&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">queue&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="nx">maxSize&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">10&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 空转等待&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">queue&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">queue&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">item&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>这种轮询方式有两个严重问题：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">问题 1：CPU 空转 问题 2：响应延迟
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> CPU 条件满足
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ──●──────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │?│ │?│ │?│ │?│ │?│ ← 反复空问 │ 等待下一次轮询
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─┘ └─┘ └─┘ └─┘ └─┘ ─────────────────●
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 全是无效检查 最多延迟 10ms&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>&lt;strong>Cond 的方案则是：条件不满足就阻塞休眠，条件满足后立即唤醒，零空转、零延迟。&lt;/strong>&lt;/p></description></item><item><title>Go 并发原语 - WaitGroup</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-waitgroup/</link><pubDate>Mon, 20 Apr 2026 10:30:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-waitgroup/</guid><description>&lt;p>上一篇我们聊了 Mutex，它解决的是&amp;quot;同一时刻只能有一个 goroutine 访问共享资源&amp;quot;的问题。但并发编程中还有另一类常见需求——&lt;strong>等待一组任务全部完成后再继续&lt;/strong>。这就是 &lt;code>sync.WaitGroup&lt;/code> 要解决的问题。&lt;/p>
&lt;h2 id="一为什么需要-waitgroup">一、为什么需要 WaitGroup？&lt;/h2>
&lt;p>假设你要并行执行三个子任务，全部完成后才能进入下一步。没有 WaitGroup 的话，你可能会这样写：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// ❌ 轮询方案：又慢又浪费 CPU&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">done1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">done2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">done3&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">false&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* 任务1 */&lt;/span> &lt;span class="nx">done1&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* 任务2 */&lt;/span> &lt;span class="nx">done2&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="cm">/* 任务3 */&lt;/span> &lt;span class="nx">done3&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="kc">true&lt;/span> &lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">done1&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">done2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="p">!&lt;/span>&lt;span class="nx">done3&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">100&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Millisecond&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 空转等待&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>这种轮询方式有两个问题：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">问题 1：响应慢 问题 2：浪费 CPU
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 任务完成 CPU
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ──●────────────┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 等待轮询 │?│ │?│ │?│ │?│ │?│ ← 反复空问
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ───────────────● └─┘ └─┘ └─┘ └─┘ └─┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 最多延迟 100ms 全是无效检查&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>WaitGroup 的方案则是：任务没完成就阻塞，全部完成后立即唤醒，零延迟、零空转。&lt;/p></description></item><item><title>Go 并发原语 - Mutex</title><link>https://chenshungen.cn/blog/golang-concurrency/golang-mutex/</link><pubDate>Mon, 20 Apr 2026 10:00:00 +0800</pubDate><guid>https://chenshungen.cn/blog/golang-concurrency/golang-mutex/</guid><description>&lt;p>并发编程是 Go 的灵魂，而 &lt;strong>Mutex（互斥锁）&lt;/strong> 是并发控制的第一道防线。这篇文章带你从&amp;quot;为什么需要锁&amp;quot;开始，一路走到 Mutex 的演进原理、常见坑点和读写锁 RWMutex，争取一文把 Mutex 讲透。&lt;/p>
&lt;h2 id="一没有锁的世界有多危险">一、没有锁的世界有多危险？&lt;/h2>
&lt;p>先看一个经典的并发计数器问题：&lt;/p>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s">&amp;#34;fmt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="s">&amp;#34;sync&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="kd">var&lt;/span> &lt;span class="nx">count&lt;/span> &lt;span class="kt">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="kd">var&lt;/span> &lt;span class="nx">wg&lt;/span> &lt;span class="nx">sync&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">WaitGroup&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="k">for&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span> &lt;span class="p">&amp;lt;&lt;/span> &lt;span class="mi">10000&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="nx">i&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="k">go&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="k">defer&lt;/span> &lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">			&lt;span class="nx">count&lt;/span>&lt;span class="o">++&lt;/span> &lt;span class="c1">// 非原子操作：读取 → 加1 → 写回&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">		&lt;span class="p">}()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">wg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Wait&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">	&lt;span class="nx">fmt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Println&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;count:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">count&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// 几乎不可能是 10000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>多次运行，结果可能是 9831、9917、9756……每次都不一样。这就是典型的 &lt;strong>数据竞争（Data Race）&lt;/strong>。&lt;/p></description></item><item><title>Go 语言后端开发实践总结</title><link>https://chenshungen.cn/blog/golang-backend/</link><pubDate>Wed, 15 Apr 2026 00:00:00 +0000</pubDate><guid>https://chenshungen.cn/blog/golang-backend/</guid><description>&lt;h2 id="为什么选择-go">为什么选择 Go？&lt;/h2>
&lt;p>Go 语言在后端开发中的优势不言而喻：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>编译速度快&lt;/strong>：秒级编译，开发体验流畅&lt;/li>
&lt;li>&lt;strong>并发原语优秀&lt;/strong>：goroutine + channel 模型简洁强大&lt;/li>
&lt;li>&lt;strong>部署简单&lt;/strong>：单二进制文件，无依赖&lt;/li>
&lt;li>&lt;strong>性能优异&lt;/strong>：接近 C 的性能，远超脚本语言&lt;/li>
&lt;/ul>
&lt;h2 id="项目结构推荐">项目结构推荐&lt;/h2>
&lt;div class="highlight-wrapper">
 &lt;button class="copy-code-btn" type="button" aria-label="Copy code to clipboard">
 &lt;svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
 &lt;rect x="9" y="9" width="13" height="13" rx="2" ry="2">&lt;/rect>
 &lt;path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1">&lt;/path>
 &lt;/svg>
 &lt;span class="copy-text">Copy&lt;/span>
 &lt;/button>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">project/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── cmd/ # 应用入口
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── server/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── main.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── internal/ # 内部包（不对外暴露）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── handler/ # HTTP 处理器
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── service/ # 业务逻辑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── repository/ # 数据访问
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── model/ # 数据模型
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── pkg/ # 可复用的公共包
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── api/ # API 定义（proto/openapi）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── config/ # 配置文件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── deploy/ # 部署相关&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;h2 id="错误处理模式">错误处理模式&lt;/h2>
&lt;p>Go 的错误处理虽然冗长，但有其设计哲学。推荐的做法：&lt;/p></description></item></channel></rss>