<?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>山高月小</title><link>https://blog.dart.xin/</link><description>Recent content on 山高月小</description><generator>Hugo</generator><language>zh-cn</language><copyright>zwdart</copyright><lastBuildDate>Fri, 23 Oct 2026 09:39:12 +0800</lastBuildDate><atom:link href="https://blog.dart.xin/index.xml" rel="self" type="application/rss+xml"/><item><title>UML 建模</title><link>https://blog.dart.xin/softexam/s109-uml/</link><pubDate>Fri, 23 Oct 2026 09:39:12 +0800</pubDate><guid>https://blog.dart.xin/softexam/s109-uml/</guid><description>&lt;p&gt;下面把 UML 建模（下午试题三）相关的知识点系统整理成一份完整文档，结构参照 DFD 和 ER，从 UML 整体定位、六大关系、类图、用例图、序列图，到考试题型、答题方法和检查清单，最后给出背诵口诀。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="uml-建模--系统整理"&gt;UML 建模 · 系统整理&lt;/h1&gt;
&lt;h2 id="一uml-在软考中的定位"&gt;一、UML 在软考中的定位&lt;/h2&gt;
&lt;p&gt;UML 建模是下午试题三（15 分必答），考法以&lt;strong&gt;补充类名、补充多重度、补充用例关系、补充消息名&lt;/strong&gt;为主，极少要求从零画整张图。&lt;/p&gt;
&lt;p&gt;软件设计师涉及的 UML 图按用途分为三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;静态建模&lt;/strong&gt;：类图、对象图、用例图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态建模&lt;/strong&gt;：序列图（时序图/顺序图）、通信图、状态图、活动图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物理建模&lt;/strong&gt;：构件图、部署图。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下午题&lt;strong&gt;高频图种是类图和用例图，序列图次之&lt;/strong&gt;；状态图和活动图偶尔出现，通常只要求补状态名或转换条件。上午选择题则可能涉及图的分类（静态/动态/物理）和接口、部署图的使用阶段等边角知识。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二uml-类图的六大关系核心考点"&gt;二、UML 类图的六大关系（核心考点）&lt;/h2&gt;
&lt;p&gt;类图是 UML 的核心，描述系统的&lt;strong&gt;静态结构&lt;/strong&gt;——类的属性、方法以及类与类之间的关系。六大关系必须从&lt;strong&gt;符号、语义、代码表现、强度排序&lt;/strong&gt;四个维度同时掌握。&lt;/p&gt;
&lt;h3 id="1-六种关系总表"&gt;1. 六种关系总表&lt;/h3&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;箭头/菱形方向&lt;/th&gt;
&lt;th&gt;语义&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;strong&gt;依赖&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;虚线 + 开口箭头&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;指向&lt;strong&gt;被依赖&lt;/strong&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;strong&gt;关联&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;实线&lt;/strong&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;strong&gt;聚合&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;实线 + 空心菱形&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;菱形在&lt;strong&gt;整体&lt;/strong&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;strong&gt;组合&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;实线 + 实心菱形&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;菱形在&lt;strong&gt;整体&lt;/strong&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;strong&gt;泛化（继承）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;实线 + 空心三角&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;三角尖端指向&lt;strong&gt;父类&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;“is-a”，子类继承父类&lt;/td&gt;
&lt;td&gt;&lt;code&gt;extends&lt;/code&gt;（Java）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;实现&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;虚线 + 空心三角&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;三角尖端指向&lt;strong&gt;接口&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;类实现接口契约&lt;/td&gt;
&lt;td&gt;&lt;code&gt;implements&lt;/code&gt;（Java）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;符号速记&lt;/strong&gt;：&lt;strong&gt;依赖虚线开口，关联实线直连；聚合空心菱，组合实心菱；泛化实线空心三角，实现虚线空心三角。菱形永远在整体端。&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>下面把 UML 建模（下午试题三）相关的知识点系统整理成一份完整文档，结构参照 DFD 和 ER，从 UML 整体定位、六大关系、类图、用例图、序列图，到考试题型、答题方法和检查清单，最后给出背诵口诀。</p>
<hr>
<h1 id="uml-建模--系统整理">UML 建模 · 系统整理</h1>
<h2 id="一uml-在软考中的定位">一、UML 在软考中的定位</h2>
<p>UML 建模是下午试题三（15 分必答），考法以<strong>补充类名、补充多重度、补充用例关系、补充消息名</strong>为主，极少要求从零画整张图。</p>
<p>软件设计师涉及的 UML 图按用途分为三类：</p>
<ul>
<li><strong>静态建模</strong>：类图、对象图、用例图。</li>
<li><strong>动态建模</strong>：序列图（时序图/顺序图）、通信图、状态图、活动图。</li>
<li><strong>物理建模</strong>：构件图、部署图。</li>
</ul>
<p>下午题<strong>高频图种是类图和用例图，序列图次之</strong>；状态图和活动图偶尔出现，通常只要求补状态名或转换条件。上午选择题则可能涉及图的分类（静态/动态/物理）和接口、部署图的使用阶段等边角知识。</p>
<hr>
<h2 id="二uml-类图的六大关系核心考点">二、UML 类图的六大关系（核心考点）</h2>
<p>类图是 UML 的核心，描述系统的<strong>静态结构</strong>——类的属性、方法以及类与类之间的关系。六大关系必须从<strong>符号、语义、代码表现、强度排序</strong>四个维度同时掌握。</p>
<h3 id="1-六种关系总表">1. 六种关系总表</h3>
<table>
	<thead>
			<tr>
					<th>关系</th>
					<th>线条与端点符号</th>
					<th>箭头/菱形方向</th>
					<th>语义</th>
					<th>代码表现</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>依赖</strong></td>
					<td><strong>虚线 + 开口箭头</strong></td>
					<td>指向<strong>被依赖</strong>的类</td>
					<td>临时使用，一方变化“波及”另一方</td>
					<td>方法参数、局部变量、返回值</td>
			</tr>
			<tr>
					<td><strong>关联</strong></td>
					<td><strong>实线</strong>（可带开口箭头）</td>
					<td>有箭头时指向被关联类</td>
					<td>长期结构关系，“知道并持有”</td>
					<td>成员变量</td>
			</tr>
			<tr>
					<td><strong>聚合</strong></td>
					<td><strong>实线 + 空心菱形</strong></td>
					<td>菱形在<strong>整体</strong>端</td>
					<td>弱拥有，部分可脱离整体独立存在</td>
					<td>成员变量，外部注入</td>
			</tr>
			<tr>
					<td><strong>组合</strong></td>
					<td><strong>实线 + 实心菱形</strong></td>
					<td>菱形在<strong>整体</strong>端</td>
					<td>强拥有，部分生命周期由整体控制</td>
					<td>成员变量，整体内部创建</td>
			</tr>
			<tr>
					<td><strong>泛化（继承）</strong></td>
					<td><strong>实线 + 空心三角</strong></td>
					<td>三角尖端指向<strong>父类</strong></td>
					<td>“is-a”，子类继承父类</td>
					<td><code>extends</code>（Java）</td>
			</tr>
			<tr>
					<td><strong>实现</strong></td>
					<td><strong>虚线 + 空心三角</strong></td>
					<td>三角尖端指向<strong>接口</strong></td>
					<td>类实现接口契约</td>
					<td><code>implements</code>（Java）</td>
			</tr>
	</tbody>
</table>
<p><strong>符号速记</strong>：<strong>依赖虚线开口，关联实线直连；聚合空心菱，组合实心菱；泛化实线空心三角，实现虚线空心三角。菱形永远在整体端。</strong></p>
<h3 id="2-强度排序易错但考得隐蔽">2. 强度排序（易错，但考得隐蔽）</h3>
<p>六种关系的耦合强度从强到弱为：<strong>泛化 &gt; 组合 &gt; 聚合 &gt; 关联 &gt; 实现 ≈ 依赖</strong>。绑定越紧，一方的修改对另一方的影响越大。这个排序在选择题中可能以“哪种关系耦合最强/最弱”的形式出现。</p>
<h3 id="3-聚合-vs-组合最高频辨析">3. 聚合 vs 组合（最高频辨析）</h3>
<p>两者的区分只看<strong>生命周期是否同步</strong>：</p>
<ul>
<li><strong>聚合</strong>：整体消失，部分<strong>仍然存在</strong>。如“部门”与“员工”，部门撤销，员工调到其他部门继续存在。</li>
<li><strong>组合</strong>：整体消失，部分<strong>随之销毁</strong>。如“订单”与“订单项”，订单删除，订单项也不复存在。</li>
</ul>
<p>考试中如果题干强调“<strong>同生共死</strong>”“<strong>不可分割</strong>”“<strong>随整体销毁</strong>”，选<strong>组合</strong>；如果强调“<strong>可独立存在</strong>”“<strong>可脱离整体</strong>”，选<strong>聚合</strong>。</p>
<h3 id="4-依赖的判定容易漏判">4. 依赖的判定（容易漏判）</h3>
<p>依赖是最弱的关系，表现为<strong>临时使用</strong>：一个类的方法把另一个类的对象作为<strong>参数、局部变量或返回值</strong>，就构成依赖。如果两个类之间只是“方法里用了一下”，没有成员变量长期持有，就画<strong>依赖虚线</strong>，而不是关联实线。</p>
<h3 id="5-类框与可见性">5. 类框与可见性</h3>
<p>类框通常分三层：<strong>类名、属性、方法</strong>。可见性符号：<code>+</code> 公开（public），<code>-</code> 私有（private），<code>#</code> 保护（protected）。</p>
<p><strong>下午题注意</strong>：业务分析层面的类图<strong>不应出现 getter/setter 等纯技术方法</strong>，应列出与业务规则相关的核心操作。</p>
<hr>
<h2 id="三多重度类图必考填空">三、多重度（类图必考填空）</h2>
<p>多重度写在<strong>关联线两端</strong>，表示“一个类的实例可以与另一个类的多少个实例相关联”。考试中常以填空形式出现，要求补齐 (1)~(4) 处的多重度。</p>
<table>
	<thead>
			<tr>
					<th>写法</th>
					<th>含义</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>1</code></td>
					<td>恰好一个</td>
			</tr>
			<tr>
					<td><code>0..1</code></td>
					<td>零个或一个</td>
			</tr>
			<tr>
					<td><code>*</code> 或 <code>0..*</code></td>
					<td>零个或多个</td>
			</tr>
			<tr>
					<td><code>1..*</code></td>
					<td>至少一个</td>
			</tr>
	</tbody>
</table>
<p><strong>判定方法（最关键）</strong> ：<strong>站在每一端，看对方</strong>。</p>
<ul>
<li>站在 A 端看 B：一个 A 对象对应几个 B 对象？把答案写在 <strong>B 端</strong>。</li>
<li>站在 B 端看 A：一个 B 对象对应几个 A 对象？把答案写在 <strong>A 端</strong>。</li>
</ul>
<p><strong>易错点</strong>：只标一边、把“0”漏掉（新系统刚上线可以没有订单）、把 <code>1..*</code> 和 <code>0..*</code> 搞混。题干中出现“<strong>一个……可以对应多个……</strong>”或“<strong>至少一个</strong>”的描述，直接对应到两端的多重度。</p>
<hr>
<h2 id="四用例图">四、用例图</h2>
<p>用例图描述<strong>参与者、用例以及它们之间的关系</strong>，用于获取需求和指导测试。下午题常考“补充参与者名、补充用例名、判断用例之间的关系”。</p>
<h3 id="1-基本图形">1. 基本图形</h3>
<ul>
<li><strong>参与者（Actor）</strong> ：<strong>火柴人</strong>，画在系统边界<strong>之外</strong>。可以是人、硬件或其他系统扮演的角色。<strong>“时间”不能作为参与者</strong>。</li>
<li><strong>用例</strong>：<strong>椭圆</strong>，内写“动词 + 名词”的用例名（如“借书”“还书”）。用例必须对参与者有<strong>可观测的完整结果</strong>，<strong>不能把内部步骤拆成独立用例</strong>（“验证密码”不是用例，“登录”才是）。</li>
<li><strong>系统边界</strong>：可选<strong>矩形框</strong>包围所有用例，参与者画在框外。</li>
</ul>
<h3 id="2-四种关系与箭头方向最大陷阱">2. 四种关系与箭头方向（最大陷阱）</h3>
<table>
	<thead>
			<tr>
					<th>关系</th>
					<th>线条</th>
					<th>箭头方向</th>
					<th>语义</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>关联</strong></td>
					<td><strong>实线</strong>（可带箭头）</td>
					<td>参与者指向用例</td>
					<td>参与者使用用例</td>
			</tr>
			<tr>
					<td><strong>包含（include）</strong></td>
					<td><strong>虚线 + 箭头 + <code>《include》</code></strong></td>
					<td><strong>指向被包含的用例</strong></td>
					<td>基本用例<strong>一定</strong>会执行被包含用例</td>
			</tr>
			<tr>
					<td><strong>扩展（extend）</strong></td>
					<td><strong>虚线 + 箭头 + <code>《extend》</code></strong></td>
					<td><strong>指向基础（被扩展）用例</strong></td>
					<td>扩展用例<strong>有条件才</strong>执行</td>
			</tr>
			<tr>
					<td><strong>泛化</strong></td>
					<td><strong>实线 + 空心三角</strong></td>
					<td>三角指向<strong>父</strong>用例/父参与者</td>
					<td>继承关系</td>
			</tr>
	</tbody>
</table>
<p><strong>箭头口诀</strong>：<strong>“包箭被，扩箭基”</strong>——include 箭头指向<strong>被包含</strong>的用例；extend 箭头指向<strong>基础（被扩展）</strong> 的用例。</p>
<h3 id="3-include-与-extend-的语义区分">3. include 与 extend 的语义区分</h3>
<ul>
<li><strong>include</strong>：把多个用例的<strong>公共行为</strong>抽出来，<strong>必然执行</strong>。如“借书”和“还书”都需要“验证借书证”，把“验证借书证”抽成被包含用例。</li>
<li><strong>extend</strong>：扩展用例是<strong>可选的、条件性的</strong>，基础用例本身是完整的，只有扩展点被激活时才执行扩展用例。如“还书”时<strong>如果超期才</strong>触发“计算罚款”。</li>
</ul>
<p><strong>答题判断</strong>：题干说“<strong>必须</strong>”“<strong>一定</strong>”“<strong>公共步骤</strong>”→ include；说“<strong>可选</strong>”“<strong>如果</strong>”“<strong>特殊情况下</strong>”→ extend。</p>
<hr>
<h2 id="五序列图时序图">五、序列图（时序图）</h2>
<p>序列图是交互图，强调<strong>对象之间消息传递的时间顺序</strong>。下午题常考“补充对象名、补充消息名、补充消息顺序”，上午题常考消息类型和生命线。</p>
<h3 id="1-三大核心元素">1. 三大核心元素</h3>
<ul>
<li><strong>对象</strong>：矩形框，格式为“<strong>对象名 : 类名</strong>”，两者都可省略。发起交互的对象通常放在最左边。</li>
<li><strong>生命线</strong>：从对象框底部垂直向下的<strong>虚线</strong>，代表对象在交互中的存在时间。</li>
<li><strong>激活期（控制焦点）</strong> ：生命线上的<strong>细长矩形</strong>，表示对象执行一个操作的时间段。<strong>生命线是虚线，激活期是长条，不能混淆</strong>。</li>
</ul>
<h3 id="2-消息类型高频辨析">2. 消息类型（高频辨析）</h3>
<ul>
<li><strong>同步消息</strong>：<strong>实心三角箭头</strong>，发送者等待接收者处理完成（如客户端上传文件后等服务器确认）。</li>
<li><strong>异步消息</strong>：<strong>空心三角箭头</strong>，发送者不等待返回，继续执行。</li>
<li><strong>返回消息</strong>：<strong>虚线 + 开口箭头</strong>，表示方法调用返回结果。</li>
</ul>
<p>时间方向<strong>自上而下</strong>，消息按发生的先后顺序从上到下排列。</p>
<hr>
<h2 id="六其他图种低频认识即可">六、其他图种（低频，认识即可）</h2>
<ul>
<li><strong>状态图</strong>：描述一个对象在生命周期内的状态变迁，常考补状态名和转换条件。</li>
<li><strong>活动图</strong>：描述流程和并行行为，常考补活动结点名称。</li>
<li><strong>部署图</strong>：通常在<strong>实施阶段</strong>使用，考图分类时可能涉及。</li>
</ul>
<hr>
<h2 id="七下午题题型与答题方法">七、下午题题型与答题方法</h2>
<h3 id="题型一补充类名">题型一：补充类名</h3>
<p>题干给出类名清单（表 3-1）和类图框架，要求把类名填入空缺的类框。方法：读题干中每个类的职责描述，与清单中的类名逐一匹配；<strong>先填职责最明确的类</strong>，用排除法处理剩余空缺。</p>
<h3 id="题型二补充多重度">题型二：补充多重度</h3>
<p>按“站在每一端看对方”的方法逐一判定。题干中的数量词（“一个”“多个”“至少一个”“可以没有”）是直接线索。<strong>两端都要标，不能只标一边</strong>。</p>
<h3 id="题型三判断或补充类图关系">题型三：判断或补充类图关系</h3>
<p>看两个类之间是“临时使用”（依赖）、“长期持有”（关联）、“整体—部分”（聚合/组合，看生命周期）还是“父子继承”（泛化）。符号要严格对应：菱形在整体端、三角指父类/接口。</p>
<h3 id="题型四补充用例名参与者名判断用例关系">题型四：补充用例名/参与者名，判断用例关系</h3>
<p>参与者从题干中的“人/部门/外部系统”提取，用题干原词。用例名用“动词+名词”，不要拆内部步骤。关系判断用“必须/公共”→ include，“可选/如果”→ extend，箭头方向对照“包箭被，扩箭基”。</p>
<h3 id="题型五补充序列图的消息名或对象名">题型五：补充序列图的消息名或对象名</h3>
<p>消息名通常对应<strong>接收对象类中的方法名</strong>；对象名对应类名。结合题干描述的操作步骤，按时间顺序从上到下补全。</p>
<h3 id="答题规范">答题规范</h3>
<ul>
<li>类名、用例名、消息名<strong>用题干原词</strong>，不要自己发明。</li>
<li>多重度写标准形式（<code>1</code>、<code>0..*</code>、<code>1..*</code>），不要写“多个”这种口语。</li>
<li>关系符号画标准：菱形在整体端、泛化三角指父、include/extend 箭头方向不能反。</li>
</ul>
<hr>
<h2 id="八高频易错点汇总">八、高频易错点汇总</h2>
<ol>
<li><strong>聚合/组合菱形方向反</strong>：菱形必须在<strong>整体</strong>端，不是部分端。</li>
<li><strong>把组合和聚合的生命周期语义搞反</strong>：组合是同生共死，聚合是可分离。</li>
<li><strong>include 与 extend 箭头方向搞反</strong>：记住“<strong>扩箭基</strong>”——extend 指向基础用例。</li>
<li><strong>把“可选/条件”画成 include</strong>：可选行为必须用 extend。</li>
<li><strong>多重度只标一边或漏标“0”</strong>：两端都要标，<code>0..*</code> 的“0”不能省。</li>
<li><strong>依赖与关联混淆</strong>：临时使用（参数/局部变量）是依赖，长期持有（成员变量）是关联。</li>
<li><strong>泛化和实现混用</strong>：继承父类用<strong>实线</strong>空心三角，实现接口用<strong>虚线</strong>空心三角。</li>
<li><strong>把内部步骤拆成用例</strong>：用例必须对参与者有完整可观测结果，“验证密码”不是用例。</li>
<li><strong>参与者画进系统边界内</strong>：参与者一定在<strong>外部</strong>。</li>
<li><strong>序列图生命线与激活期混淆</strong>：虚线是生命线（对象存在），细长矩形是激活期（对象忙碌）。</li>
</ol>
<hr>
<h2 id="九uml-答题检查清单">九、UML 答题检查清单</h2>
<p><strong>第一步：符号与方向</strong></p>
<ul>
<li>类图：菱形在整体端？泛化三角指父类/接口？泛化实线、实现虚线？</li>
<li>用例图：include 箭头指向被包含用例？extend 箭头指向基础用例？</li>
<li>序列图：同步消息实心三角、异步消息空心三角、返回消息虚线？</li>
</ul>
<p><strong>第二步：标注</strong></p>
<ul>
<li>多重度两端都标了吗？“0”有没有漏？</li>
<li>用例关系的构造型 <code>《include》</code> / <code>《extend》</code> 写了吗？</li>
<li>类框的类名、属性、方法层级是否合理？</li>
</ul>
<p><strong>第三步：语义</strong></p>
<ul>
<li>聚合 vs 组合看生命周期：同生共死是组合，可分离是聚合。</li>
<li>include vs extend 看必然性：必须执行是 include，有条件才执行是 extend。</li>
<li>依赖 vs 关联看持续性：临时使用是依赖，成员变量持有是关联。</li>
</ul>
<p><strong>第四步：用词</strong></p>
<ul>
<li>类名、用例名、消息名是否用了题干原词？</li>
<li>有没有把内部步骤拆成用例？</li>
<li>参与者是否在系统边界之外？</li>
</ul>
<hr>
<h2 id="十uml-背诵清单直接背">十、UML 背诵清单（直接背）</h2>
<ol>
<li><strong>六大关系符号</strong>：依赖虚线开口，关联实线直连；聚合空心菱，组合实心菱；泛化实线空心三角，实现虚线空心三角。</li>
<li><strong>菱形在整体端</strong>，泛化三角指父类/接口。</li>
<li><strong>强度排序</strong>：泛化 &gt; 组合 &gt; 聚合 &gt; 关联 &gt; 实现 ≈ 依赖。</li>
<li><strong>聚合 vs 组合</strong>：生命周期同步是组合，可分离是聚合。</li>
<li><strong>依赖</strong>：方法参数/局部变量/返回值中使用，临时关系。</li>
<li><strong>多重度</strong>：<code>1</code>、<code>0..1</code>、<code>0..*</code>、<code>1..*</code>；<strong>站在每一端看对方</strong>，两端都标。</li>
<li><strong>用例关系箭头</strong>：<strong>包箭被，扩箭基</strong>——include 指被包含，extend 指基础。</li>
<li><strong>include = 必须/公共行为</strong>；<strong>extend = 可选/条件触发</strong>。</li>
<li><strong>用例名“动词+名词”</strong>，不拆内部步骤；参与者必须在系统边界外。</li>
<li><strong>序列图</strong>：对象名:类名；生命线是虚线，激活期是长条；同步实心三角，异步空心三角，返回虚线开口。</li>
<li><strong>答题用题干原词</strong>，不自己发明名称。</li>
<li><strong>业务类图不塞 getter/setter</strong>，只列业务核心操作。</li>
</ol>
<hr>
<h2 id="十一uml-速记口诀">十一、UML 速记口诀</h2>
<blockquote>
<p><strong>类图六关系：依赖虚线箭，关联实线连；聚合空心菱，组合实心菱；泛化实线三角指父，实现虚线三角指接口；菱形永远在整体端，生命周期分聚合组合。</strong></p>
<p><strong>用例四关系：关联实线指用例，包含虚线箭头指被包，扩展虚线箭头指基础，泛化空心三角指父类。包箭被，扩箭基；必须 include，可选 extend。</strong></p>
<p><strong>多重度：站一端看另一端，两端都标不漏零；同生共死是组合，可脱整体是聚合。</strong></p>
<p><strong>序列图：对象在上线向下，同步实心异步空，返回虚线开口箭，激活长条命是虚。</strong></p>
</blockquote>
<hr>
<h2 id="十二uml-与-dfder-的区分防止混淆">十二、UML 与 DFD、ER 的区分（防止混淆）</h2>
<table>
	<thead>
			<tr>
					<th>对比项</th>
					<th>UML</th>
					<th>DFD</th>
					<th>ER</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>用途</strong></td>
					<td>面向对象建模，静态结构 + 动态交互</td>
					<td>结构化分析，数据流动与加工</td>
					<td>概念设计，业务对象与联系</td>
			</tr>
			<tr>
					<td><strong>矩形</strong></td>
					<td><strong>类框</strong>（类名/属性/方法）</td>
					<td>外部实体</td>
					<td>实体</td>
			</tr>
			<tr>
					<td><strong>菱形</strong></td>
					<td>聚合/组合的端点符号（在整体端）</td>
					<td>无</td>
					<td>联系</td>
			</tr>
			<tr>
					<td><strong>椭圆/圆</strong></td>
					<td><strong>用例</strong>（椭圆）</td>
					<td>加工（圆角矩形/圆）</td>
					<td>属性</td>
			</tr>
			<tr>
					<td><strong>带箭头线</strong></td>
					<td><strong>部分关系带箭头</strong>（依赖、泛化、实现、用例关系）</td>
					<td><strong>全部数据流带箭头</strong></td>
					<td><strong>无箭头</strong>（无向边）</td>
			</tr>
			<tr>
					<td><strong>核心标注</strong></td>
					<td>多重度、构造型 <code>《include》</code> / <code>《extend》</code></td>
					<td>数据流名词、加工动宾</td>
					<td>联系类型、主键下划线</td>
			</tr>
	</tbody>
</table>
<p><strong>关键区分</strong>：UML 的矩形是<strong>类</strong>，ER 的矩形是<strong>业务实体</strong>，DFD 的矩形是<strong>系统外部实体</strong>；UML 中菱形只作为<strong>聚合/组合的端点符号</strong>，ER 中菱形是<strong>联系本身</strong>；UML 的箭头有方向语义，ER 的连线<strong>不带箭头</strong>。</p>
<hr>
<p>把以上内容按“六大关系→多重度→用例图→序列图→题型→易错点”的顺序过一遍，再拿历年真题的 UML 题练手，重点练 <strong>“多重度两端判定”</strong> 和 <strong>“include/extend 箭头方向”</strong> ，这两条是 UML 下午题最高频的采分点和丢分点。</p>
]]></content:encoded></item><item><title>数据库ER 实体-联系图</title><link>https://blog.dart.xin/softexam/s108-er/</link><pubDate>Fri, 23 Oct 2026 09:39:03 +0800</pubDate><guid>https://blog.dart.xin/softexam/s108-er/</guid><description>&lt;p&gt;下面把 ER（实体-联系图）相关的所有知识点系统整理成一份完整文档，从概念、符号、联系类型、转换规则、范式到考试题型和答题方法，层层递进，最后给出检查清单和背诵口诀。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="er-实体-联系图--系统整理"&gt;ER 实体-联系图 · 系统整理&lt;/h1&gt;
&lt;h2 id="一er-图是什么"&gt;一、ER 图是什么&lt;/h2&gt;
&lt;p&gt;ER 图（Entity-Relationship Diagram，实体-联系图）是&lt;strong&gt;概念设计阶段&lt;/strong&gt;的工具，用图形方式描述业务中的&lt;strong&gt;实体、属性和联系&lt;/strong&gt;。它回答三个问题：业务中有哪些对象、每个对象有哪些特征、对象之间是什么关系。&lt;/p&gt;
&lt;p&gt;ER 图是下午试题二（15分必答）的核心，通常与“E-R 图转关系模式、判断主键外键、判断范式”结合考查。考试中&lt;strong&gt;很少要求从零画完整 ER 图&lt;/strong&gt;，更多是&lt;strong&gt;补充缺失的联系或属性&lt;/strong&gt;，然后把图转换成关系模式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二er-图的三种基本元素"&gt;二、ER 图的三种基本元素&lt;/h2&gt;
&lt;p&gt;ER 图只有三种基本符号，形状必须标准，命名和标注规则必须遵守。&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;内部写什么&lt;/th&gt;
&lt;th&gt;命名规则&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;strong&gt;实体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;封闭矩形&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;实体名称&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;名词&lt;/strong&gt;（客户、订单、学生、课程）&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;椭圆&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;属性名称&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;名词&lt;/strong&gt;（学号、姓名、成绩）&lt;/td&gt;
&lt;td&gt;用&lt;strong&gt;无向边&lt;/strong&gt;（不带箭头）连到所属实体或联系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;联系&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;菱形&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;联系名称&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;动词&lt;/strong&gt;（选修、预订、属于）&lt;/td&gt;
&lt;td&gt;用&lt;strong&gt;无向边&lt;/strong&gt;连到相关实体，&lt;strong&gt;连线旁标联系类型&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="各元素详解"&gt;各元素详解&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;实体&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务中需要长期管理的对象，用封闭矩形表示。&lt;/li&gt;
&lt;li&gt;命名用&lt;strong&gt;名词&lt;/strong&gt;，如“客户”“订单”“商品”“学生”“课程”。&lt;/li&gt;
&lt;li&gt;矩形四边全封，与 DFD 的外部实体形状相同，但语义不同：ER 的实体是&lt;strong&gt;业务对象&lt;/strong&gt;，DFD 的外部实体是&lt;strong&gt;系统边界之外的数据源点&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;属性&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;描述实体或联系的特征，用椭圆表示。&lt;/li&gt;
&lt;li&gt;用&lt;strong&gt;无向边&lt;/strong&gt;（不带箭头的实线）连接到所属实体或联系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主键标注&lt;/strong&gt;：能唯一标识实体的属性（主键），在椭圆内属性名&lt;strong&gt;下方画一条实线下划线&lt;/strong&gt;。例如“学号”加下划线表示它是学生实体的主键。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外键标注&lt;/strong&gt;：通常用&lt;strong&gt;虚下划线&lt;/strong&gt;标注（下午题关系模式填空时常用实下划线标主键）。&lt;/li&gt;
&lt;li&gt;属性连线&lt;strong&gt;不能带箭头&lt;/strong&gt;，带箭头是高频错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;联系&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表示实体之间的业务关联，用菱形表示。&lt;/li&gt;
&lt;li&gt;命名用&lt;strong&gt;动词&lt;/strong&gt;或&lt;strong&gt;动词短语&lt;/strong&gt;，如“选修”“预订”“属于”“供应”。&lt;/li&gt;
&lt;li&gt;用&lt;strong&gt;无向边&lt;/strong&gt;分别连接到相关实体。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;联系类型必须标在连线旁&lt;/strong&gt;（不是写在菱形里），用 &lt;code&gt;1:1&lt;/code&gt;、&lt;code&gt;1:n&lt;/code&gt;、&lt;code&gt;m:n&lt;/code&gt; 表示。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三三种联系类型及画法"&gt;三、三种联系类型及画法&lt;/h2&gt;
&lt;h3 id="11一对一"&gt;1:1（一对一）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标注方式&lt;/strong&gt;：菱形两侧连线旁都标 &lt;code&gt;1&lt;/code&gt;（或 &lt;code&gt;1:1&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：班级与班长——一个班只有一个班长，一个班长只在一个班。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;画法&lt;/strong&gt;：班级 —1— ◇属于◇ —1— 班长&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="1n一对多"&gt;1:n（一对多）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标注方式&lt;/strong&gt;：连接到“1端”实体的线旁标 &lt;code&gt;1&lt;/code&gt;，连接到“多端”实体的线旁标 &lt;code&gt;n&lt;/code&gt; 或 &lt;code&gt;m&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：部门与员工——一个部门有多名员工，一名员工只属于一个部门。部门旁标 &lt;code&gt;1&lt;/code&gt;，员工旁标 &lt;code&gt;n&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;画法&lt;/strong&gt;：部门 —1— ◇拥有◇ —n— 员工&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意&lt;/strong&gt;：“1”端和“n”端不能反，“1”在少的一端，“n”在多的一端。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mn多对多"&gt;m:n（多对多）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;标注方式&lt;/strong&gt;：菱形两侧连线旁分别标 &lt;code&gt;m&lt;/code&gt; 和 &lt;code&gt;n&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：学生与课程——一个学生选多门课，一门课被多个学生选。学生旁标 &lt;code&gt;m&lt;/code&gt;，课程旁标 &lt;code&gt;n&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;画法&lt;/strong&gt;：学生 —m— ◇选修◇ —n— 课程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="联系类型标注的三个关键"&gt;联系类型标注的三个关键&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;标在连线旁&lt;/strong&gt;，不是写在菱形里面。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不用箭头&lt;/strong&gt;，ER 图用无向边加数字标注，不用箭头表示方向。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两端都要标&lt;/strong&gt;，不能只标一端。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="四联系自身的属性高频丢分点"&gt;四、联系自身的属性（高频丢分点）&lt;/h2&gt;
&lt;p&gt;属性不只能属于实体，&lt;strong&gt;联系本身也可以有属性&lt;/strong&gt;。&lt;/p&gt;</description><content:encoded><![CDATA[<p>下面把 ER（实体-联系图）相关的所有知识点系统整理成一份完整文档，从概念、符号、联系类型、转换规则、范式到考试题型和答题方法，层层递进，最后给出检查清单和背诵口诀。</p>
<hr>
<h1 id="er-实体-联系图--系统整理">ER 实体-联系图 · 系统整理</h1>
<h2 id="一er-图是什么">一、ER 图是什么</h2>
<p>ER 图（Entity-Relationship Diagram，实体-联系图）是<strong>概念设计阶段</strong>的工具，用图形方式描述业务中的<strong>实体、属性和联系</strong>。它回答三个问题：业务中有哪些对象、每个对象有哪些特征、对象之间是什么关系。</p>
<p>ER 图是下午试题二（15分必答）的核心，通常与“E-R 图转关系模式、判断主键外键、判断范式”结合考查。考试中<strong>很少要求从零画完整 ER 图</strong>，更多是<strong>补充缺失的联系或属性</strong>，然后把图转换成关系模式。</p>
<hr>
<h2 id="二er-图的三种基本元素">二、ER 图的三种基本元素</h2>
<p>ER 图只有三种基本符号，形状必须标准，命名和标注规则必须遵守。</p>
<table>
	<thead>
			<tr>
					<th>元素</th>
					<th>标准图形</th>
					<th>内部写什么</th>
					<th>命名规则</th>
					<th>连线方式</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>实体</strong></td>
					<td><strong>封闭矩形</strong></td>
					<td>实体名称</td>
					<td><strong>名词</strong>（客户、订单、学生、课程）</td>
					<td>—</td>
			</tr>
			<tr>
					<td><strong>属性</strong></td>
					<td><strong>椭圆</strong></td>
					<td>属性名称</td>
					<td><strong>名词</strong>（学号、姓名、成绩）</td>
					<td>用<strong>无向边</strong>（不带箭头）连到所属实体或联系</td>
			</tr>
			<tr>
					<td><strong>联系</strong></td>
					<td><strong>菱形</strong></td>
					<td>联系名称</td>
					<td><strong>动词</strong>（选修、预订、属于）</td>
					<td>用<strong>无向边</strong>连到相关实体，<strong>连线旁标联系类型</strong></td>
			</tr>
	</tbody>
</table>
<h3 id="各元素详解">各元素详解</h3>
<p><strong>实体</strong></p>
<ul>
<li>业务中需要长期管理的对象，用封闭矩形表示。</li>
<li>命名用<strong>名词</strong>，如“客户”“订单”“商品”“学生”“课程”。</li>
<li>矩形四边全封，与 DFD 的外部实体形状相同，但语义不同：ER 的实体是<strong>业务对象</strong>，DFD 的外部实体是<strong>系统边界之外的数据源点</strong>。</li>
</ul>
<p><strong>属性</strong></p>
<ul>
<li>描述实体或联系的特征，用椭圆表示。</li>
<li>用<strong>无向边</strong>（不带箭头的实线）连接到所属实体或联系。</li>
<li><strong>主键标注</strong>：能唯一标识实体的属性（主键），在椭圆内属性名<strong>下方画一条实线下划线</strong>。例如“学号”加下划线表示它是学生实体的主键。</li>
<li><strong>外键标注</strong>：通常用<strong>虚下划线</strong>标注（下午题关系模式填空时常用实下划线标主键）。</li>
<li>属性连线<strong>不能带箭头</strong>，带箭头是高频错误。</li>
</ul>
<p><strong>联系</strong></p>
<ul>
<li>表示实体之间的业务关联，用菱形表示。</li>
<li>命名用<strong>动词</strong>或<strong>动词短语</strong>，如“选修”“预订”“属于”“供应”。</li>
<li>用<strong>无向边</strong>分别连接到相关实体。</li>
<li><strong>联系类型必须标在连线旁</strong>（不是写在菱形里），用 <code>1:1</code>、<code>1:n</code>、<code>m:n</code> 表示。</li>
</ul>
<hr>
<h2 id="三三种联系类型及画法">三、三种联系类型及画法</h2>
<h3 id="11一对一">1:1（一对一）</h3>
<ul>
<li><strong>标注方式</strong>：菱形两侧连线旁都标 <code>1</code>（或 <code>1:1</code>）。</li>
<li><strong>示例</strong>：班级与班长——一个班只有一个班长，一个班长只在一个班。</li>
<li><strong>画法</strong>：班级 —1— ◇属于◇ —1— 班长</li>
</ul>
<h3 id="1n一对多">1:n（一对多）</h3>
<ul>
<li><strong>标注方式</strong>：连接到“1端”实体的线旁标 <code>1</code>，连接到“多端”实体的线旁标 <code>n</code> 或 <code>m</code>。</li>
<li><strong>示例</strong>：部门与员工——一个部门有多名员工，一名员工只属于一个部门。部门旁标 <code>1</code>，员工旁标 <code>n</code>。</li>
<li><strong>画法</strong>：部门 —1— ◇拥有◇ —n— 员工</li>
<li><strong>注意</strong>：“1”端和“n”端不能反，“1”在少的一端，“n”在多的一端。</li>
</ul>
<h3 id="mn多对多">m:n（多对多）</h3>
<ul>
<li><strong>标注方式</strong>：菱形两侧连线旁分别标 <code>m</code> 和 <code>n</code>。</li>
<li><strong>示例</strong>：学生与课程——一个学生选多门课，一门课被多个学生选。学生旁标 <code>m</code>，课程旁标 <code>n</code>。</li>
<li><strong>画法</strong>：学生 —m— ◇选修◇ —n— 课程</li>
</ul>
<h3 id="联系类型标注的三个关键">联系类型标注的三个关键</h3>
<ol>
<li><strong>标在连线旁</strong>，不是写在菱形里面。</li>
<li><strong>不用箭头</strong>，ER 图用无向边加数字标注，不用箭头表示方向。</li>
<li><strong>两端都要标</strong>，不能只标一端。</li>
</ol>
<hr>
<h2 id="四联系自身的属性高频丢分点">四、联系自身的属性（高频丢分点）</h2>
<p>属性不只能属于实体，<strong>联系本身也可以有属性</strong>。</p>
<ul>
<li><strong>示例</strong>：“学生”与“课程”的“选修”联系，属性“成绩”描述的是“某个学生选修某门课程”这件事，应画一个椭圆连接到<strong>菱形</strong>上，而不是连到学生或课程实体上。</li>
<li><strong>判断方法</strong>：题干中出现“数量、日期、成绩、时间、金额”这类描述<strong>关系状态</strong>的词，基本就是联系属性。</li>
<li><strong>经典错误</strong>：把“成绩”画成学生或课程的属性，而不是选修联系的属性。</li>
</ul>
<hr>
<h2 id="五e-r-图转关系模式必背规则">五、E-R 图转关系模式（必背规则）</h2>
<p>这是下午题第二题的核心得分点，三种联系类型的转换规则必须条件反射。</p>
<table>
	<thead>
			<tr>
					<th>联系类型</th>
					<th>转换规则</th>
					<th>示例</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>1:1</strong></td>
					<td>任一方主键放入另一方作外键（或两表合并）</td>
					<td>班级（班级号，班级名，班长学号）；班长（学号，姓名）</td>
			</tr>
			<tr>
					<td><strong>1:n</strong></td>
					<td><strong>1端</strong>主键放入<strong>n端</strong>作外键</td>
					<td>部门（部门号，部门名）；员工（员工号，姓名，部门号）</td>
			</tr>
			<tr>
					<td><strong>m:n</strong></td>
					<td><strong>必须单独建一张联系表</strong>，字段 = 双方主键 + 联系自身属性；主键 = 双方主键的<strong>组合</strong>，两主键各自也是外键</td>
					<td>学生（学号，姓名）；课程（课程号，课程名）；选修（学号，课程号，成绩）</td>
			</tr>
	</tbody>
</table>
<h3 id="转换规则详解">转换规则详解</h3>
<p><strong>1:1 转换</strong></p>
<ul>
<li>两种做法：把一方主键放进另一方作外键，或两表合并。</li>
<li>考试中通常选择“把一方主键放入另一方”，具体放哪一方看题干语义（通常把可选性低的一方主键放入另一方）。</li>
</ul>
<p><strong>1:n 转换</strong></p>
<ul>
<li><strong>1端主键放入n端作外键</strong>，这是唯一正确方向。</li>
<li>例如部门与员工：员工表里加“部门号”作外键，而不是部门表里加“员工号”。</li>
<li><strong>方向搞反是高频错误</strong>。</li>
</ul>
<p><strong>m:n 转换</strong></p>
<ul>
<li><strong>必须单独建表</strong>，不能把一方主键塞进另一方。</li>
<li>联系表的字段 = 双方主键 + 联系自身属性。</li>
<li>联系表的主键 = 双方主键的<strong>组合</strong>。</li>
<li>双方主键在联系表中<strong>各自也是外键</strong>，分别参照两个实体表。</li>
<li><strong>示例</strong>：学生选课，单独建“选修（学号，课程号，成绩）”，主键是（学号，课程号），学号和课程号各自是外键。</li>
</ul>
<h3 id="主键与外键判定">主键与外键判定</h3>
<p><strong>主键判定</strong>：</p>
<ul>
<li>题干出现“<strong>唯一标识</strong>”“<strong>编号</strong>”“<strong>身份证号</strong>”的，基本是主键候选。</li>
<li>订单表的主键通常是“订单编号”，而不是“客户编号 + 商品编号”的组合（除非题意明确订单号不唯一）。</li>
<li>m:n 联系表的主键是<strong>双方主键的组合</strong>。</li>
</ul>
<p><strong>外键判定</strong>：</p>
<ul>
<li>如果 A 表里出现了 B 表的主键，它就是外键。</li>
<li>m:n 联系表中，双方主键的组合是主键，同时<strong>各自也是外键</strong>，分别参照两个实体表。</li>
<li>1:n 转换中，n 端表中的 1 端主键是外键。</li>
</ul>
<p><strong>答题格式</strong>：关系模式写成 <code>表名（主键加下划线，外键用波浪线或文字说明）</code>，并在答案中明确写出“主键是XX，外键是XX，参照XX表”。</p>
<hr>
<h2 id="六范式判定送分题">六、范式判定（送分题）</h2>
<p>范式判定通常与关系模式转换结合考查，判断转换后的关系模式属于第几范式。</p>
<table>
	<thead>
			<tr>
					<th>范式</th>
					<th>判定条件</th>
					<th>通俗理解</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>1NF</strong></td>
					<td>属性不可再分</td>
					<td>每个格子只放一个值，不能放“多个电话”这种复合值</td>
			</tr>
			<tr>
					<td><strong>2NF</strong></td>
					<td>消除<strong>非主属性对码的部分函数依赖</strong></td>
					<td>联合主键中，某个非主属性不能只依赖主键的一部分</td>
			</tr>
			<tr>
					<td><strong>3NF</strong></td>
					<td>消除<strong>非主属性对码的传递函数依赖</strong></td>
					<td>非主属性不能经由另一个非主属性间接依赖主键</td>
			</tr>
	</tbody>
</table>
<h3 id="判定方法">判定方法</h3>
<ul>
<li><strong>1NF</strong>：看有没有“多值属性”或“复合属性”，有就不满足1NF。</li>
<li><strong>2NF</strong>：先找主键。如果主键是联合主键（多个属性组合），看有没有非主属性只依赖其中一部分。有则不是2NF。</li>
<li><strong>3NF</strong>：看有没有“A→B，B→C”且B不是候选键的情况。有则是传递依赖，不是3NF。</li>
<li><strong>考试常见答案</strong>：转换后的关系模式通常能达到<strong>3NF</strong>；如果题干强调“存在传递依赖”，则答案是2NF。</li>
</ul>
<h3 id="范式一句话">范式一句话</h3>
<blockquote>
<p><strong>1NF 列不可分；2NF 消除部分依赖；3NF 消除传递依赖。</strong></p>
</blockquote>
<hr>
<h2 id="七考试题型与答题方法">七、考试题型与答题方法</h2>
<h3 id="题型一补充-er-图中的联系或属性">题型一：补充 ER 图中的联系或属性</h3>
<p><strong>问法</strong>：“根据题干说明，补充ER图中缺失的联系”“指出某联系的类型”“补充某实体的属性”。</p>
<p><strong>方法</strong>：</p>
<ul>
<li>读题干，找“A和B之间是什么关系”的描述，判断联系类型（1:1、1:n、m:n）。</li>
<li>联系类型标在连线旁，两端都要标。</li>
<li>如果题干说“某关系有某属性”（如“订单有订单日期”），把属性画成椭圆连到菱形上。</li>
</ul>
<h3 id="题型二将-er-图转换为关系模式">题型二：将 ER 图转换为关系模式</h3>
<p><strong>问法</strong>：“将ER图转换为关系模式”“补充关系模式中的空缺”“指出每个关系模式的主键和外键”。</p>
<p><strong>方法</strong>：</p>
<ul>
<li>按“1:1、1:n、m:n”三种规则逐一转换。</li>
<li>1:n 把1端主键放n端；m:n 单独建表，主键为组合。</li>
<li>主键加下划线，外键明确写出参照关系。</li>
<li>答案格式：<code>表名（主键，属性1，属性2，外键）</code>，并注明主键和外键。</li>
</ul>
<h3 id="题型三判断范式">题型三：判断范式</h3>
<p><strong>问法</strong>：“该关系模式属于第几范式”“是否存在部分依赖/传递依赖”。</p>
<p><strong>方法</strong>：</p>
<ul>
<li>先找主键，再看非主属性对主键的依赖关系。</li>
<li>有部分依赖→1NF；有传递依赖→2NF；都没有→3NF。</li>
<li>答题时写出判断依据，如“因为存在非主属性对联合主键的部分依赖，所以不是2NF”。</li>
</ul>
<h3 id="题型四补充关系模式中的空缺">题型四：补充关系模式中的空缺</h3>
<p><strong>问法</strong>：“补充(a)(b)处的属性”“指出空缺处应填什么”。</p>
<p><strong>方法</strong>：</p>
<ul>
<li>看关系模式的已有字段，缺的通常是<strong>外键</strong>或<strong>联系自身属性</strong>。</li>
<li>如果缺的是外键，从相关实体表的主键中找。</li>
<li>如果缺的是联系属性（如“成绩”“数量”），从题干描述中找。</li>
</ul>
<h3 id="答题规范">答题规范</h3>
<ul>
<li>关系模式写成 <code>表名（属性1，属性2，……）</code>，主键加下划线。</li>
<li>明确写出“主键是XX，外键是XX，参照XX表”。</li>
<li>范式判定要写出依据（部分依赖/传递依赖）。</li>
<li>用题干原词命名实体、属性和联系，不要自己发明名称。</li>
</ul>
<hr>
<h2 id="八高频易错点汇总">八、高频易错点汇总</h2>
<ol>
<li><strong>形状画错</strong>：实体画成椭圆、属性画成矩形、联系画成矩形。</li>
<li><strong>联系类型漏标或标错位置</strong>：<code>1:n</code> 必须标在<strong>连线旁</strong>，且“1”端和“n”端不能反。</li>
<li><strong>联系属性错挂实体</strong>：成绩、数量、日期这类描述“关系”的属性，必须挂到菱形上。</li>
<li><strong>主键漏下划线</strong>：主键属性必须加实线下划线。</li>
<li><strong>属性连线带箭头</strong>：属性与实体/联系之间用<strong>无向边</strong>，不要画箭头。</li>
<li><strong>1:n 转换方向搞反</strong>：必须是<strong>1端主键放入n端</strong>，不能反过来。</li>
<li><strong>m:n 忘记单独建表</strong>：m:n 联系必须单独建一张表，不能把一方主键塞进另一方。</li>
<li><strong>m:n 建表后主键漏组合</strong>：联系表的主键必须是双方主键的<strong>组合</strong>，不能只用其中一方。</li>
<li><strong>外键漏标或标错参照</strong>：外键必须明确参照哪个实体表。</li>
<li><strong>范式判定只写结论不写依据</strong>：要写出“存在部分依赖/传递依赖”作为判断理由。</li>
<li><strong>实体命名用动词</strong>：实体必须是名词（“订单”不是“下订单”）。</li>
<li><strong>联系命名用名词</strong>：联系通常是动词（“选修”不是“选课关系”）。</li>
</ol>
<hr>
<h2 id="九er-答题检查清单">九、ER 答题检查清单</h2>
<p>答完 ER 题，按以下顺序自查：</p>
<p><strong>第一步：形状</strong></p>
<ul>
<li>实体是封闭矩形？</li>
<li>属性是椭圆？</li>
<li>联系是菱形？</li>
<li>属性连线是无向边（不带箭头）？</li>
</ul>
<p><strong>第二步：标注</strong></p>
<ul>
<li>联系类型（1:1、1:n、m:n）标在连线旁了吗？</li>
<li>两端都标了吗？“1”端和“n”端没反吧？</li>
<li>主键属性加下划线了吗？</li>
<li>联系自身的属性挂到菱形上了吗？</li>
</ul>
<p><strong>第三步：转换</strong></p>
<ul>
<li>1:n 是否把1端主键放进了n端？</li>
<li>m:n 是否单独建表了？</li>
<li>联系表的主键是否是双方主键的组合？</li>
<li>双方主键在联系表中是否都标了外键？</li>
<li>每个关系模式的主键和外键都明确写出了吗？</li>
</ul>
<p><strong>第四步：范式</strong></p>
<ul>
<li>先找主键，再看非主属性对主键的依赖。</li>
<li>有部分依赖→1NF；有传递依赖→2NF；都没有→3NF。</li>
<li>判断依据写出来了吗？</li>
</ul>
<hr>
<h2 id="十er-背诵清单直接背">十、ER 背诵清单（直接背）</h2>
<ol>
<li><strong>三种元素</strong>：实体（矩形）、属性（椭圆）、联系（菱形）。</li>
<li><strong>命名规则</strong>：实体名词、属性名词、联系动词。</li>
<li><strong>属性连线</strong>：无向边，不带箭头。</li>
<li><strong>主键标注</strong>：属性名下方实线下划线。</li>
<li><strong>联系类型</strong>：1:1、1:n、m:n，标在<strong>连线旁</strong>，两端都标，不用箭头。</li>
<li><strong>联系属性</strong>：描述关系状态的属性（成绩、数量、日期）挂到<strong>菱形</strong>上。</li>
<li><strong>1:1 转换</strong>：任一方主键放入另一方作外键（或合并）。</li>
<li><strong>1:n 转换</strong>：<strong>1端主键放入n端</strong>作外键，方向不能反。</li>
<li><strong>m:n 转换</strong>：<strong>必须单独建表</strong>，字段=双方主键+联系属性，主键=双方主键组合，双方主键各自也是外键。</li>
<li><strong>主键判定</strong>：题干“唯一标识”“编号”“身份证号”是主键候选。</li>
<li><strong>外键判定</strong>：A表出现B表主键，它就是外键。</li>
<li><strong>范式口诀</strong>：1NF列不可分；2NF消除部分依赖；3NF消除传递依赖。</li>
<li><strong>答题格式</strong>：表名（主键加下划线，属性，外键），明确写出主键和外键参照关系。</li>
<li><strong>范式答题</strong>：写出判断依据（部分依赖/传递依赖），不能只写结论。</li>
</ol>
<hr>
<h2 id="十一er-速记口诀">十一、ER 速记口诀</h2>
<blockquote>
<p><strong>实体矩形属性椭圆，联系菱形在中间；<br>
联系类型标线旁，主键属性下划线。<br>
联系属性挂菱形，m:n 单独建表记心间；<br>
1:n 把1端主键放n端，主外键判定看编号。<br>
1NF列不可分，2NF消部分，3NF消传递；<br>
转换先找主键，再判依赖，最后写清参照关系。</strong></p>
</blockquote>
<hr>
<h2 id="十二er-与-dfd-的区分防止混淆">十二、ER 与 DFD 的区分（防止混淆）</h2>
<table>
	<thead>
			<tr>
					<th>对比项</th>
					<th>ER 图</th>
					<th>DFD</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>用途</strong></td>
					<td>概念设计，描述业务对象和关系</td>
					<td>结构化分析，描述数据流动和加工</td>
			</tr>
			<tr>
					<td><strong>矩形</strong></td>
					<td><strong>实体</strong>（业务对象）</td>
					<td><strong>外部实体</strong>（系统边界之外的数据源点）</td>
			</tr>
			<tr>
					<td><strong>椭圆/圆</strong></td>
					<td><strong>属性</strong></td>
					<td><strong>加工</strong>（圆角矩形或圆）</td>
			</tr>
			<tr>
					<td><strong>菱形</strong></td>
					<td><strong>联系</strong></td>
					<td>无</td>
			</tr>
			<tr>
					<td><strong>开口矩形/平行线</strong></td>
					<td>无</td>
					<td><strong>数据存储</strong></td>
			</tr>
			<tr>
					<td><strong>连线</strong></td>
					<td><strong>无向边</strong>（不带箭头）</td>
					<td><strong>带箭头的直线</strong>（数据流）</td>
			</tr>
			<tr>
					<td><strong>标注</strong></td>
					<td>联系类型标线旁、主键下划线</td>
					<td>数据流用名词、加工用动宾</td>
			</tr>
	</tbody>
</table>
<p><strong>核心区分</strong>：ER 图的矩形是<strong>业务实体</strong>，DFD 的矩形是<strong>系统外部实体</strong>；ER 图连线<strong>不带箭头</strong>，DFD 数据流<strong>带箭头</strong>；ER 图有<strong>菱形联系</strong>，DFD 没有菱形。</p>
<hr>
<p>把以上内容按“符号→联系类型→转换规则→范式→题型→易错点”的顺序过一遍，再拿历年真题的数据库设计题练手，重点练“m:n 单独建表”和“1:n 方向不能反”，这两条是 ER 题最高频的采分点和丢分点。</p>
]]></content:encoded></item><item><title>数据流图 DFD</title><link>https://blog.dart.xin/softexam/s107-dfd/</link><pubDate>Fri, 23 Oct 2026 09:38:41 +0800</pubDate><guid>https://blog.dart.xin/softexam/s107-dfd/</guid><description>&lt;p&gt;下面把 DFD（数据流图）相关的所有知识点系统整理成一份完整文档，从概念、符号、分层、平衡、命名、连线到考试题型和答题方法，层层递进，最后给出背诵清单和速记口诀。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="dfd-数据流图--系统整理"&gt;DFD 数据流图 · 系统整理&lt;/h1&gt;
&lt;h2 id="一dfd-是什么"&gt;一、DFD 是什么&lt;/h2&gt;
&lt;p&gt;DFD（Data Flow Diagram，数据流图）是一种&lt;strong&gt;结构化分析&lt;/strong&gt;工具，用图形方式描述系统的&lt;strong&gt;数据流动&lt;/strong&gt;和&lt;strong&gt;加工过程&lt;/strong&gt;。它回答四个问题：数据从哪里来、经过什么加工、存到哪里、最后输出给谁。&lt;/p&gt;
&lt;p&gt;DFD 只关心&lt;strong&gt;数据流&lt;/strong&gt;，不关心控制流、时序和判断条件。它是下午试题一（15分必答）的固定题型。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二dfd-的四个基本元素"&gt;二、DFD 的四个基本元素&lt;/h2&gt;
&lt;p&gt;DFD 只有四种符号，必须条件反射地识别和画对。&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;内部写什么&lt;/th&gt;
&lt;th&gt;命名规则&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;strong&gt;外部实体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;封闭矩形&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;实体名称&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;名词&lt;/strong&gt;（客户、财务部门、银行系统）&lt;/td&gt;
&lt;td&gt;系统边界之外，是数据源点或终点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;加工&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;圆角矩形&lt;/strong&gt;（推荐）或&lt;strong&gt;圆形&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;编号 + 加工名（P1 验证订单）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;动词 + 名词&lt;/strong&gt;，不能只写动词&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;必须有输入，必须有输出&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数据存储&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;开口矩形&lt;/strong&gt;（右边开口）或&lt;strong&gt;两条平行线&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;编号 + 名称（D1 订单表）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;名词&lt;/strong&gt;（表/文件/库/清单/档案）&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;只能与加工相连&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数据流&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;带箭头的直线&lt;/strong&gt;（单向）&lt;/td&gt;
&lt;td&gt;流名称写在线上方&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;名词短语&lt;/strong&gt;（订单数据、付款通知）&lt;/td&gt;
&lt;td&gt;至少一端是加工，&lt;strong&gt;不能用动词命名&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="各元素详解"&gt;各元素详解&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;外部实体&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统边界之外的人、组织或外部系统。&lt;/li&gt;
&lt;li&gt;同一实体可在图中不同位置多次出现，角落加斜线标记。&lt;/li&gt;
&lt;li&gt;外部实体&lt;strong&gt;之间不能直连&lt;/strong&gt;，必须经过加工中转。&lt;/li&gt;
&lt;li&gt;顶层图中外部实体与系统加工之间的数据流，必须原样出现在0层图中（父子平衡）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;加工&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对数据执行变换或处理的功能单元。&lt;/li&gt;
&lt;li&gt;编号从 P1 开始（顶层图的整体系统加工为 P0）。&lt;/li&gt;
&lt;li&gt;每个加工&lt;strong&gt;至少一条输入流和至少一条输出流&lt;/strong&gt;。只有入没有出是“数据黑洞”，只有出没有入是“数据奇迹”，都是硬性错误。&lt;/li&gt;
&lt;li&gt;更深层子图用父编号加点号：P1.1、P1.2，体现逐层分解。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;数据存储&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据的静态存放处，本身&lt;strong&gt;不加工数据&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;开口矩形三条边、右边不封口；左侧小格写 D 编号，右侧写名称。两条平行线是简化画法，左右都不封口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不能画成封闭矩形&lt;/strong&gt;（会和外部实体混淆），&lt;strong&gt;不能画成圆角矩形&lt;/strong&gt;（会和加工混淆）。&lt;/li&gt;
&lt;li&gt;数据存储&lt;strong&gt;只能与加工相连&lt;/strong&gt;：箭头指向存储 = 写入；箭头从存储引出 = 读取。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据存储之间不能直连&lt;/strong&gt;，&lt;strong&gt;外部实体与数据存储之间也不能直连&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;数据流&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>下面把 DFD（数据流图）相关的所有知识点系统整理成一份完整文档，从概念、符号、分层、平衡、命名、连线到考试题型和答题方法，层层递进，最后给出背诵清单和速记口诀。</p>
<hr>
<h1 id="dfd-数据流图--系统整理">DFD 数据流图 · 系统整理</h1>
<h2 id="一dfd-是什么">一、DFD 是什么</h2>
<p>DFD（Data Flow Diagram，数据流图）是一种<strong>结构化分析</strong>工具，用图形方式描述系统的<strong>数据流动</strong>和<strong>加工过程</strong>。它回答四个问题：数据从哪里来、经过什么加工、存到哪里、最后输出给谁。</p>
<p>DFD 只关心<strong>数据流</strong>，不关心控制流、时序和判断条件。它是下午试题一（15分必答）的固定题型。</p>
<hr>
<h2 id="二dfd-的四个基本元素">二、DFD 的四个基本元素</h2>
<p>DFD 只有四种符号，必须条件反射地识别和画对。</p>
<table>
	<thead>
			<tr>
					<th>元素</th>
					<th>标准图形</th>
					<th>内部写什么</th>
					<th>命名规则</th>
					<th>核心约束</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>外部实体</strong></td>
					<td><strong>封闭矩形</strong></td>
					<td>实体名称</td>
					<td><strong>名词</strong>（客户、财务部门、银行系统）</td>
					<td>系统边界之外，是数据源点或终点</td>
			</tr>
			<tr>
					<td><strong>加工</strong></td>
					<td><strong>圆角矩形</strong>（推荐）或<strong>圆形</strong></td>
					<td>编号 + 加工名（P1 验证订单）</td>
					<td><strong>动词 + 名词</strong>，不能只写动词</td>
					<td><strong>必须有输入，必须有输出</strong></td>
			</tr>
			<tr>
					<td><strong>数据存储</strong></td>
					<td><strong>开口矩形</strong>（右边开口）或<strong>两条平行线</strong></td>
					<td>编号 + 名称（D1 订单表）</td>
					<td><strong>名词</strong>（表/文件/库/清单/档案）</td>
					<td><strong>只能与加工相连</strong></td>
			</tr>
			<tr>
					<td><strong>数据流</strong></td>
					<td><strong>带箭头的直线</strong>（单向）</td>
					<td>流名称写在线上方</td>
					<td><strong>名词短语</strong>（订单数据、付款通知）</td>
					<td>至少一端是加工，<strong>不能用动词命名</strong></td>
			</tr>
	</tbody>
</table>
<h3 id="各元素详解">各元素详解</h3>
<p><strong>外部实体</strong></p>
<ul>
<li>系统边界之外的人、组织或外部系统。</li>
<li>同一实体可在图中不同位置多次出现，角落加斜线标记。</li>
<li>外部实体<strong>之间不能直连</strong>，必须经过加工中转。</li>
<li>顶层图中外部实体与系统加工之间的数据流，必须原样出现在0层图中（父子平衡）。</li>
</ul>
<p><strong>加工</strong></p>
<ul>
<li>对数据执行变换或处理的功能单元。</li>
<li>编号从 P1 开始（顶层图的整体系统加工为 P0）。</li>
<li>每个加工<strong>至少一条输入流和至少一条输出流</strong>。只有入没有出是“数据黑洞”，只有出没有入是“数据奇迹”，都是硬性错误。</li>
<li>更深层子图用父编号加点号：P1.1、P1.2，体现逐层分解。</li>
</ul>
<p><strong>数据存储</strong></p>
<ul>
<li>数据的静态存放处，本身<strong>不加工数据</strong>。</li>
<li>开口矩形三条边、右边不封口；左侧小格写 D 编号，右侧写名称。两条平行线是简化画法，左右都不封口。</li>
<li><strong>不能画成封闭矩形</strong>（会和外部实体混淆），<strong>不能画成圆角矩形</strong>（会和加工混淆）。</li>
<li>数据存储<strong>只能与加工相连</strong>：箭头指向存储 = 写入；箭头从存储引出 = 读取。</li>
<li><strong>数据存储之间不能直连</strong>，<strong>外部实体与数据存储之间也不能直连</strong>。</li>
</ul>
<p><strong>数据流</strong></p>
<ul>
<li>单向箭头，表示数据在元素之间的流动方向。</li>
<li>命名必须是<strong>名词或名词短语</strong>，描述“流动的是什么”。</li>
<li><strong>绝对不能用动词或加工名来命名</strong>（“查询”“计算工资”“处理完成”都是错误命名）。</li>
<li>每条流必须有起点和终点，且<strong>至少一端是加工</strong>。合法连接只有：实体↔加工、加工↔加工、加工↔存储、存储↔加工。</li>
</ul>
<hr>
<h2 id="三dfd-的分层体系">三、DFD 的分层体系</h2>
<p>DFD 采用<strong>自顶向下、逐层分解</strong>的画法，每一层都是一张完整的图。</p>
<h3 id="顶层图上下文图">顶层图（上下文图）</h3>
<ul>
<li>整个系统画成<strong>一个加工</strong>（P0，命名为“XX系统”）。</li>
<li>只画<strong>外部实体</strong>和<strong>对外数据流</strong>。</li>
<li><strong>不出现数据存储</strong>，不展开内部加工。</li>
<li>作用：界定系统边界，明确系统与外界的所有交互。</li>
</ul>
<h3 id="0层图">0层图</h3>
<ul>
<li>顶层图中那个系统加工展开后的<strong>第一层细图</strong>。</li>
<li>包含<strong>多个主加工</strong>（P1、P2、P3……）、<strong>数据存储</strong>（D1、D2……）、<strong>外部实体</strong>（与顶层图同一批）和<strong>内部数据流</strong>。</li>
<li>加工编号从1开始，<strong>不出现0号加工</strong>（0号留给顶层图的系统整体）。</li>
</ul>
<h3 id="更深层子图">更深层子图</h3>
<ul>
<li>0层图中某个加工继续展开，得到1层图、2层图……</li>
<li>子图编号用父编号加点号：P1.1、P1.2。</li>
<li>考试中通常只考顶层图和0层图，更深层偶尔以“判断某加工能否继续分解”的形式出现。</li>
</ul>
<h3 id="分层关系一句话">分层关系一句话</h3>
<p><strong>顶层图只有1个加工，0层图把系统拆成几大块，更深层把某一块再拆细。每层都是一张完整的图，不是把上一层的图改一改。</strong></p>
<hr>
<h2 id="四数据平衡三原则核心考点">四、数据平衡三原则（核心考点）</h2>
<h3 id="原则一顶层图与0层图平衡">原则一：顶层图与0层图平衡</h3>
<ul>
<li>顶层图中系统与外部实体之间的<strong>每一条输入流和输出流</strong>，在0层图中都必须<strong>原样出现</strong>（名称一致、方向一致、连接的外部实体一致）。</li>
<li>0层图<strong>不能多出</strong>顶层图没有的对外数据流，也<strong>不能缺少</strong>顶层图已有的对外数据流。</li>
<li>0层图内部的加工间数据流、加工与存储间的数据流，不需要在顶层图中出现，<strong>不影响平衡</strong>。</li>
</ul>
<h3 id="原则二父图与子图平衡">原则二：父图与子图平衡</h3>
<ul>
<li>父图中某个加工的输入流和输出流，必须与它展开后的子图的输入流和输出流<strong>完全一致</strong>。</li>
<li>子图不能多出父图中没有的跨边界数据流，也不能缺少父图中已有的跨边界数据流。</li>
<li>平衡只看<strong>跨边界</strong>的数据流，子图内部加工之间的流不需要与父图对应。</li>
</ul>
<h3 id="原则三加工自身平衡">原则三：加工自身平衡</h3>
<ul>
<li>每个加工必须<strong>既有输入数据流，又有输出数据流</strong>。</li>
<li>只有入没有出 = <strong>数据黑洞</strong>；只有出没有入 = <strong>数据奇迹</strong>。两者都是硬性错误。</li>
</ul>
<h3 id="平衡口诀">平衡口诀</h3>
<blockquote>
<p><strong>顶层对外几条流，0层必须条条有；名称方向不能变，内部怎么拆随便。父图加工的入出，子图边界不能丢；每个加工有进有出，黑洞奇迹都没有。</strong></p>
</blockquote>
<h3 id="父图与子图通俗理解">父图与子图通俗理解</h3>
<ul>
<li><strong>父图</strong>是较粗的那层图，某个加工还没有展开。</li>
<li><strong>子图</strong>是把父图中<strong>某一个加工</strong>单独展开后得到的细图。</li>
<li>这个被展开的加工，就是父图和子图之间的“接口”。</li>
<li>举例：0层图中有加工“2.统计成绩”，把它展开得到1层图（2.1读取成绩、2.2计算平均分、2.3生成统计表）。0层图是父图，1层图是子图，平衡要求“2.统计成绩”的对外输入输出与1层图整体的对外输入输出一致。</li>
</ul>
<hr>
<h2 id="五命名规则高频扣分点">五、命名规则（高频扣分点）</h2>
<table>
	<thead>
			<tr>
					<th>元素</th>
					<th>正确命名</th>
					<th>错误命名</th>
					<th>原因</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>外部实体</strong></td>
					<td>客户、财务部门</td>
					<td>提交订单、审核申请</td>
					<td>实体是名词，不是动作</td>
			</tr>
			<tr>
					<td><strong>加工</strong></td>
					<td>验证订单、计算总成本</td>
					<td>校验、处理</td>
					<td>必须“动词+宾语”，不能只有动词</td>
			</tr>
			<tr>
					<td><strong>数据存储</strong></td>
					<td>订单表、库存文件</td>
					<td>存储订单、记录维修</td>
					<td>存储是名词，不是动作</td>
			</tr>
			<tr>
					<td><strong>数据流</strong></td>
					<td>订单数据、付款通知</td>
					<td>计算工资、查询完成</td>
					<td>数据流是名词短语，不能用加工名</td>
			</tr>
	</tbody>
</table>
<p><strong>数据流命名是最高频扣分点</strong>。看到数据流名称是动词或加工名，基本可以判定为错误。</p>
<hr>
<h2 id="六连线规则与三大禁令">六、连线规则与三大禁令</h2>
<h3 id="合法连接只有这五种">合法连接（只有这五种）</h3>
<ul>
<li>外部实体 → 加工</li>
<li>加工 → 外部实体</li>
<li>加工 → 加工</li>
<li>加工 → 数据存储（写入）</li>
<li>数据存储 → 加工（读取）</li>
</ul>
<h3 id="三大禁令">三大禁令</h3>
<ol>
<li><strong>数据存储之间不能直连</strong>。</li>
<li><strong>外部实体之间不能直连</strong>。</li>
<li><strong>外部实体与数据存储之间不能直连</strong>。</li>
</ol>
<p>所有连接都必须经过<strong>加工</strong>中转。违反禁令是下午题改错的高频考点。</p>
<hr>
<h2 id="七考试题型与答题方法">七、考试题型与答题方法</h2>
<h3 id="题型一补充外部实体或数据存储名称">题型一：补充外部实体或数据存储名称</h3>
<p><strong>问法</strong>：“指出顶层图中E1、E2分别是什么”“0层图中D1～D4分别是什么”。</p>
<p><strong>方法</strong>：</p>
<ul>
<li>外部实体：读题干，找系统边界之外的“人/部门/外部系统”，用题干原词。</li>
<li>数据存储：找题干中带“文件、表、库、清单、档案”的名词。</li>
</ul>
<h3 id="题型二补充缺失的数据流">题型二：补充缺失的数据流</h3>
<p><strong>问法</strong>：“0层图中某加工缺少一条输入数据流，请指出名称、起点和终点”。</p>
<p><strong>方法</strong>：</p>
<ol>
<li>先对比顶层图与0层图，找不平衡的对外流。</li>
<li>逐句读题干，找“提到要读某存储但图上没有入线”或“提到要输出给某实体但图上没有出线”的加工。</li>
<li>检查每个加工是否只有入没有出、或只有出没有入。</li>
<li>答案格式：<strong>在加工Y与存储Z之间添加数据流“XX信息”</strong>，方向要明确。</li>
</ol>
<p><strong>关键</strong>：数据流的起点或终点<strong>必须有一个是加工</strong>。</p>
<h3 id="题型三改错">题型三：改错</h3>
<p><strong>问法</strong>：“0层图中哪条数据流方向画错了”“找出图中的错误”。</p>
<p><strong>方法</strong>：按“形状→命名→连线→平衡”四步检查：</p>
<ul>
<li>形状对不对（实体矩形、加工圆角、存储开口）。</li>
<li>命名对不对（数据流是否用动词、加工是否只有动词）。</li>
<li>连线是否违反三大禁令。</li>
<li>加工是否有进有出，父子是否平衡。</li>
</ul>
<h3 id="题型四判断某加工能否继续分解">题型四：判断某加工能否继续分解</h3>
<p><strong>方法</strong>：看该加工是否足够简单、是否只有一个明确功能。如果加工内部还有多个子功能或判断逻辑，就可以继续分解；如果已经是原子操作，就不必再拆。</p>
<h3 id="答题规范">答题规范</h3>
<ul>
<li>补充数据流时用<strong>题干原词</strong>，不要自己发明口语化名称。</li>
<li>答案写成“<strong>在XX与XX之间添加数据流‘XX’</strong>”，起点终点方向明确。</li>
<li>数据流名称写<strong>名词短语</strong>，不写动词。</li>
<li>加工名称写<strong>动词+名词</strong>。</li>
</ul>
<hr>
<h2 id="八高频易错点汇总">八、高频易错点汇总</h2>
<ol>
<li><strong>数据流用动词或加工名命名</strong>（如“计算工资”“处理完成”）→ 必须用名词短语。</li>
<li><strong>数据存储画成封闭矩形</strong> → 与外部实体混淆。</li>
<li><strong>数据存储画成圆角矩形</strong> → 与加工混淆。</li>
<li><strong>加工只有入没有出，或只有出没有入</strong> → 数据黑洞/数据奇迹。</li>
<li><strong>父图子图不平衡</strong>：子图多出或缺少数据流，或名称方向不一致。</li>
<li><strong>外部实体与数据存储直连</strong> → 必须经过加工。</li>
<li><strong>数据存储之间直连</strong> → 必须经过加工。</li>
<li><strong>外部实体之间直连</strong> → 必须经过加工。</li>
<li><strong>顶层图出现数据存储</strong> → 顶层图只有加工、实体和数据流。</li>
<li><strong>加工名只写一个动词</strong>（如“校验”）→ 必须写清校验什么。</li>
<li><strong>数据流没有起点或终点</strong>，或两端都不是加工。</li>
<li><strong>补充数据流时自己发明名称</strong>，不用题干原词。</li>
</ol>
<hr>
<h2 id="九dfd-答题检查清单">九、DFD 答题检查清单</h2>
<p>答完DFD题，按以下顺序自查：</p>
<p><strong>第一步：形状</strong></p>
<ul>
<li>外部实体是封闭矩形？</li>
<li>加工是圆角矩形或圆形？</li>
<li>数据存储是开口矩形或两条平行线（不是封闭矩形、不是圆角矩形）？</li>
<li>数据流是带箭头的单向直线？</li>
</ul>
<p><strong>第二步：命名</strong></p>
<ul>
<li>数据流是名词短语（不是动词、不是加工名）？</li>
<li>加工是“动词+名词”（不是只有动词）？</li>
<li>数据存储是名词（不是动作）？</li>
</ul>
<p><strong>第三步：连线</strong></p>
<ul>
<li>存储没有直连存储？</li>
<li>实体没有直连实体？</li>
<li>实体没有直连存储？</li>
<li>每条数据流至少一端是加工？</li>
<li>每个加工有进有出？</li>
</ul>
<p><strong>第四步：平衡</strong></p>
<ul>
<li>顶层图与0层图的对外流名称、方向、数量一致？</li>
<li>父图加工的入出流与子图边界流一致？</li>
<li>没有数据黑洞或数据奇迹？</li>
</ul>
<hr>
<h2 id="十dfd-背诵清单直接背">十、DFD 背诵清单（直接背）</h2>
<ol>
<li><strong>四个元素</strong>：外部实体（矩形）、加工（圆角矩形/圆）、数据存储（开口矩形/平行线）、数据流（带箭头直线）。</li>
<li><strong>命名规则</strong>：实体名词、加工动宾、存储名词、数据流名词短语（不能用动词）。</li>
<li><strong>加工约束</strong>：必须有进有出，不能是黑洞或奇迹。</li>
<li><strong>存储约束</strong>：只能连加工，不能连存储或实体。</li>
<li><strong>实体约束</strong>：不能直连实体，不能直连存储，必须经过加工。</li>
<li><strong>顶层图</strong>：1个加工（P0），只有实体和数据流，无数据存储。</li>
<li><strong>0层图</strong>：多个加工（P1起），可出现数据存储，外部实体与顶层图相同。</li>
<li><strong>平衡三原则</strong>：顶层与0层平衡、父图与子图平衡、加工自身平衡。</li>
<li><strong>父图子图</strong>：父图是粗图，子图是父图中某一个加工展开后的细图；平衡要求该加工的对外接口展开前后不变。</li>
<li><strong>平衡口诀</strong>：顶层对外几条流，0层必须条条有；名称方向不能变，内部怎么拆随便。</li>
<li><strong>答题格式</strong>：在XX与XX之间添加数据流“XX”，起点终点明确，用题干原词。</li>
<li><strong>三大禁令</strong>：存储不直连存储、实体不直连实体、实体不直连存储。</li>
</ol>
<hr>
<h2 id="十一dfd-速记口诀">十一、DFD 速记口诀</h2>
<blockquote>
<p><strong>实体矩形在边界，加工圆角带编号；<br>
存储开口只连工，数据箭头名词报。<br>
加工有进必有出，实体存储不直交；<br>
顶层一个对外流，0层条条不能少。<br>
父图子图看边界，名称方向要对牢；<br>
数据流名用名词，动词加工全不要。</strong></p>
</blockquote>
<p>把以上内容按“符号→分层→平衡→命名→连线→题型→易错点”的顺序过一遍，再拿历年真题的DFD题练手，重点练“找缺失数据流”和“改错”，这两类问法占分最重。</p>
]]></content:encoded></item><item><title>结合真题的考点</title><link>https://blog.dart.xin/softexam/s106-paper/</link><pubDate>Thu, 22 Oct 2026 22:53:37 +0800</pubDate><guid>https://blog.dart.xin/softexam/s106-paper/</guid><description>&lt;p&gt;以下是结合2022—2026年真题考点提炼的&lt;strong&gt;上午选择题背诵条目&lt;/strong&gt;，共 &lt;strong&gt;510 条&lt;/strong&gt;。每条给出结论/原理 + 真题考查形式或典型陷阱，按模块编号。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一计算机组成与体系结构165"&gt;一、计算机组成与体系结构（1—65）&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; CPU由&lt;strong&gt;运算器、控制器、寄存器组和内部总线&lt;/strong&gt;组成。真题常问“CPU核心部件不包括什么”，&lt;strong&gt;存储器不属于CPU核心部件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;strong&gt;运算器&lt;/strong&gt;核心部件：&lt;strong&gt;ALU（算术逻辑单元）、AC（累加器）、DR（数据缓冲寄存器）、PSW（状态条件寄存器）&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; &lt;strong&gt;控制器&lt;/strong&gt;核心部件：&lt;strong&gt;PC（程序计数器）、IR（指令寄存器）、指令译码器、时序部件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; &lt;strong&gt;PC存下一条指令地址，IR存当前指令&lt;/strong&gt;。2024—2025反复考“PC存当前指令”为错误选项。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5.&lt;/strong&gt; &lt;strong&gt;IR对用户完全透明&lt;/strong&gt;，用户程序不能直接访问；&lt;strong&gt;PC用户可见&lt;/strong&gt;（可编程访问）。透明性辨析是常考点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6.&lt;/strong&gt; &lt;strong&gt;补码的作用&lt;/strong&gt;：将减法转化为加法，简化运算器设计。2024下半年考过“求补码真值”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7.&lt;/strong&gt; &lt;strong&gt;已知X的补码，求−X的补码&lt;/strong&gt;：&lt;strong&gt;连同符号位一起按位取反，末位加1&lt;/strong&gt;。2025下半年直接考查。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8.&lt;/strong&gt; &lt;strong&gt;移码&lt;/strong&gt;：只把补码的&lt;strong&gt;符号位取反&lt;/strong&gt;，其余位不变。常用于浮点数的阶码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9.&lt;/strong&gt; 正数的原码、反码、补码&lt;strong&gt;三者相同&lt;/strong&gt;；负数的反码是原码数值位按位取反（符号位不变），补码是反码末位加1。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10.&lt;/strong&gt; 机器字长为n时，补码表示范围：&lt;strong&gt;−2ⁿ⁻¹ 到 2ⁿ⁻¹−1&lt;/strong&gt;；原码和反码范围：&lt;strong&gt;−(2ⁿ⁻¹−1) 到 2ⁿ⁻¹−1&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;11.&lt;/strong&gt; &lt;strong&gt;浮点数&lt;/strong&gt;：&lt;strong&gt;阶码决定范围，尾数决定精度&lt;/strong&gt;。阶码越长范围越大，尾数越长精度越高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;12.&lt;/strong&gt; 浮点数运算步骤：&lt;strong&gt;对阶→尾数运算→规格化→舍入→溢出判断&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;13.&lt;/strong&gt; &lt;strong&gt;海明校验码&lt;/strong&gt;：&lt;strong&gt;可检错也可纠错&lt;/strong&gt;，校验位公式 &lt;strong&gt;2^r ≥ r + m + 1&lt;/strong&gt;（m数据位，r校验位）。连续多年考查。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;14.&lt;/strong&gt; &lt;strong&gt;奇偶校验&lt;/strong&gt;：&lt;strong&gt;只能检错，不能纠错&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15.&lt;/strong&gt; &lt;strong&gt;CRC循环冗余校验&lt;/strong&gt;：&lt;strong&gt;可检错，不可纠错&lt;/strong&gt;，用模2除法计算。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;16.&lt;/strong&gt; &lt;strong&gt;流水线周期&lt;/strong&gt;：等于各段中&lt;strong&gt;最慢的那一段&lt;/strong&gt;时间。2025下半年给出取指2ns、分析2ns、执行1ns，周期为2ns。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;17.&lt;/strong&gt; &lt;strong&gt;流水线执行时间&lt;/strong&gt;：&lt;strong&gt;单条指令完整时间 +（指令数n−1）× 流水线周期&lt;/strong&gt;。注意第1条走完全部工段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;18.&lt;/strong&gt; &lt;strong&gt;流水线吞吐率&lt;/strong&gt; = &lt;strong&gt;1 / 流水线周期&lt;/strong&gt;（指令不重叠时），或按“指令数 / 总时间”计算。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;19.&lt;/strong&gt; &lt;strong&gt;流水线加速比&lt;/strong&gt; = &lt;strong&gt;不使用流水线的时间 / 使用流水线的时间&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;20.&lt;/strong&gt; &lt;strong&gt;寻址方式快慢排序&lt;/strong&gt;：&lt;strong&gt;立即寻址 &amp;gt; 寄存器寻址 &amp;gt; 直接寻址 &amp;gt; 间接寻址&lt;/strong&gt;。立即最快（操作数在指令中），间接最慢（多次访存）。&lt;/p&gt;</description><content:encoded><![CDATA[<p>以下是结合2022—2026年真题考点提炼的<strong>上午选择题背诵条目</strong>，共 <strong>510 条</strong>。每条给出结论/原理 + 真题考查形式或典型陷阱，按模块编号。</p>
<hr>
<h2 id="一计算机组成与体系结构165">一、计算机组成与体系结构（1—65）</h2>
<p><strong>1.</strong> CPU由<strong>运算器、控制器、寄存器组和内部总线</strong>组成。真题常问“CPU核心部件不包括什么”，<strong>存储器不属于CPU核心部件</strong>。</p>
<p><strong>2.</strong> <strong>运算器</strong>核心部件：<strong>ALU（算术逻辑单元）、AC（累加器）、DR（数据缓冲寄存器）、PSW（状态条件寄存器）</strong> 。</p>
<p><strong>3.</strong> <strong>控制器</strong>核心部件：<strong>PC（程序计数器）、IR（指令寄存器）、指令译码器、时序部件</strong>。</p>
<p><strong>4.</strong> <strong>PC存下一条指令地址，IR存当前指令</strong>。2024—2025反复考“PC存当前指令”为错误选项。</p>
<p><strong>5.</strong> <strong>IR对用户完全透明</strong>，用户程序不能直接访问；<strong>PC用户可见</strong>（可编程访问）。透明性辨析是常考点。</p>
<p><strong>6.</strong> <strong>补码的作用</strong>：将减法转化为加法，简化运算器设计。2024下半年考过“求补码真值”。</p>
<p><strong>7.</strong> <strong>已知X的补码，求−X的补码</strong>：<strong>连同符号位一起按位取反，末位加1</strong>。2025下半年直接考查。</p>
<p><strong>8.</strong> <strong>移码</strong>：只把补码的<strong>符号位取反</strong>，其余位不变。常用于浮点数的阶码。</p>
<p><strong>9.</strong> 正数的原码、反码、补码<strong>三者相同</strong>；负数的反码是原码数值位按位取反（符号位不变），补码是反码末位加1。</p>
<p><strong>10.</strong> 机器字长为n时，补码表示范围：<strong>−2ⁿ⁻¹ 到 2ⁿ⁻¹−1</strong>；原码和反码范围：<strong>−(2ⁿ⁻¹−1) 到 2ⁿ⁻¹−1</strong>。</p>
<p><strong>11.</strong> <strong>浮点数</strong>：<strong>阶码决定范围，尾数决定精度</strong>。阶码越长范围越大，尾数越长精度越高。</p>
<p><strong>12.</strong> 浮点数运算步骤：<strong>对阶→尾数运算→规格化→舍入→溢出判断</strong>。</p>
<p><strong>13.</strong> <strong>海明校验码</strong>：<strong>可检错也可纠错</strong>，校验位公式 <strong>2^r ≥ r + m + 1</strong>（m数据位，r校验位）。连续多年考查。</p>
<p><strong>14.</strong> <strong>奇偶校验</strong>：<strong>只能检错，不能纠错</strong>。</p>
<p><strong>15.</strong> <strong>CRC循环冗余校验</strong>：<strong>可检错，不可纠错</strong>，用模2除法计算。</p>
<p><strong>16.</strong> <strong>流水线周期</strong>：等于各段中<strong>最慢的那一段</strong>时间。2025下半年给出取指2ns、分析2ns、执行1ns，周期为2ns。</p>
<p><strong>17.</strong> <strong>流水线执行时间</strong>：<strong>单条指令完整时间 +（指令数n−1）× 流水线周期</strong>。注意第1条走完全部工段。</p>
<p><strong>18.</strong> <strong>流水线吞吐率</strong> = <strong>1 / 流水线周期</strong>（指令不重叠时），或按“指令数 / 总时间”计算。</p>
<p><strong>19.</strong> <strong>流水线加速比</strong> = <strong>不使用流水线的时间 / 使用流水线的时间</strong>。</p>
<p><strong>20.</strong> <strong>寻址方式快慢排序</strong>：<strong>立即寻址 &gt; 寄存器寻址 &gt; 直接寻址 &gt; 间接寻址</strong>。立即最快（操作数在指令中），间接最慢（多次访存）。</p>
<p><strong>21.</strong> <strong>立即寻址</strong>：操作数直接包含在指令中，无需额外访存。</p>
<p><strong>22.</strong> <strong>直接寻址</strong>：指令中存放操作数的<strong>内存地址</strong>，需要访问内存获取数据。</p>
<p><strong>23.</strong> <strong>间接寻址</strong>：指令中存放的是<strong>操作数地址的地址</strong>，需要多次访存。</p>
<p><strong>24.</strong> <strong>寄存器寻址</strong>：操作数存放在CPU寄存器中，寻址开销小。</p>
<p><strong>25.</strong> <strong>Cache的作用</strong>：缓解CPU与主存之间的<strong>速度</strong>矛盾，依据<strong>局部性原理</strong>。Cache是除寄存器外最快的存储层次。</p>
<p><strong>26.</strong> <strong>Cache命中率</strong> = 命中次数 / 总访问次数。命中率计算题在2022—2025年均出现。</p>
<p><strong>27.</strong> <strong>局部性原理</strong>：包括<strong>时间局部性</strong>（刚用过的数据可能再用）和<strong>空间局部性</strong>（相邻数据可能被用）。Cache设计的理论基础。</p>
<p><strong>28.</strong> <strong>存储访问速度排序</strong>：<strong>通用寄存器 &gt; Cache &gt; 内存RAM &gt; 硬盘</strong>。越靠近CPU，速度越快、容量越小、成本越高。</p>
<p><strong>29.</strong> <strong>主存容量计算</strong>：容量 = 存储单元数 × 每单元位数。地址线n根，寻址范围 = <strong>2ⁿ</strong> 个存储单元。</p>
<p><strong>30.</strong> 内存地址范围计算技巧：<strong>大减小加1</strong>。例如B3000H到DABFFH的容量按此方法计算。</p>
<p><strong>31.</strong> <strong>串联系统可靠度</strong> = <strong>各部件可靠度相乘</strong>。</p>
<p><strong>32.</strong> <strong>并联系统可靠度</strong> = <strong>1 − 各部件失效率相乘</strong>（即 1 − (1−R)ⁿ）。并联易错点：不是直接乘可靠度。</p>
<p><strong>33.</strong> <strong>总线分类</strong>：<strong>数据总线双向</strong>，<strong>地址总线单向</strong>（CPU发出地址），<strong>控制总线双向</strong>。</p>
<p><strong>34.</strong> <strong>数据总线宽度</strong>决定CPU与内存之间一次能传输的数据位数；<strong>地址总线宽度</strong>决定寻址范围。</p>
<p><strong>35.</strong> <strong>RISC特点</strong>：指令少、<strong>定长</strong>、寄存器多、<strong>流水线友好</strong>、多数指令在一个周期内完成。2022下半年考过RISC特点。</p>
<p><strong>36.</strong> <strong>CISC特点</strong>：指令多、<strong>变长</strong>、寻址方式多、复杂指令需多个周期。</p>
<p><strong>37.</strong> <strong>I/O方式效率排序</strong>：<strong>DMA &gt; 中断 &gt; 程序查询</strong>。程序查询CPU全程等待，利用率最低；DMA硬件直接搬数据，CPU几乎不参与。</p>
<p><strong>38.</strong> <strong>程序查询方式</strong>：CPU需全程等待，无法与CPU并行工作，适用于低速外设。</p>
<p><strong>39.</strong> <strong>中断方式</strong>：外设就绪后主动发中断信号，CPU无需轮询等待，可与CPU并行工作。</p>
<p><strong>40.</strong> <strong>DMA方式</strong>：无需CPU干预，直接完成主存与外设数据交换，<strong>传输效率最高</strong>，适用于硬盘、显卡等高速设备。</p>
<p><strong>41.</strong> <strong>中断响应时</strong>，CPU保存<strong>程序计数器（PC）</strong> 和<strong>程序状态字（PSW）</strong>，然后转中断服务程序。</p>
<p><strong>42.</strong> <strong>中断向量</strong>：存放中断服务程序入口地址。中断向量表在内存固定区域。</p>
<p><strong>43.</strong> <strong>DMA与中断的区别</strong>：DMA不需要CPU逐字节介入，中断需要CPU执行中断服务程序。</p>
<p><strong>44.</strong> <strong>虚拟存储</strong>：解决主存容量不足的问题，由<strong>页式/段式/段页式</strong>管理。逻辑地址 = 页号 + 页内偏移。</p>
<p><strong>45.</strong> <strong>地址总线宽度与寻址范围</strong>：地址线n根，可寻址 <strong>2ⁿ</strong> 个单元。</p>
<p><strong>46.</strong> <strong>字节 = 8 bit</strong>；<strong>字 = 机器字长</strong>。主存编址分“按字节编址”和“按字编址”。</p>
<p><strong>47.</strong> <strong>最小数据单位是bit，最小存储单位是byte</strong>。</p>
<p><strong>48.</strong> 进制表示：<strong>D十进制、B二进制、O八进制、H十六进制</strong>。</p>
<p><strong>49.</strong> <strong>定点整数补码</strong>可表示 <strong>2ⁿ</strong> 个数（比原码多一个负数）。</p>
<p><strong>50.</strong> <strong>冯·诺依曼体系结构</strong>的核心思想：<strong>存储程序</strong>，指令和数据以同等地位存放在存储器中。</p>
<p><strong>51.</strong> <strong>指令的组成</strong>：<strong>操作码 + 地址码</strong>。操作码指明做什么，地址码指明操作数在哪里。</p>
<p><strong>52.</strong> <strong>指令执行三步骤</strong>：<strong>取指—分析—执行</strong>。流水线计算的基础框架。</p>
<p><strong>53.</strong> <strong>CPU与主存之间的速度矛盾</strong>由Cache缓解；<strong>主存与辅存之间的容量矛盾</strong>由虚拟存储缓解。</p>
<p><strong>54.</strong> <strong>Spooling技术</strong>：将独占设备改造为<strong>虚拟共享设备</strong>，解决CPU与设备速度不匹配。</p>
<p><strong>55.</strong> <strong>总线结构</strong>：单总线、双总线、三总线。三总线包括<strong>数据总线、地址总线、控制总线</strong>。</p>
<p><strong>56.</strong> <strong>磁盘容量计算</strong>：容量 = 磁头数 × 柱面数 × 每道扇区数 × 每扇区字节数。</p>
<p><strong>57.</strong> <strong>磁盘存取时间</strong> = <strong>寻道时间 + 旋转延迟 + 传输时间</strong>。寻道时间通常最长。</p>
<p><strong>58.</strong> <strong>RAID</strong>：RAID 0条带化无冗余；RAID 1镜像；RAID 5分布式奇偶校验。</p>
<p><strong>59.</strong> <strong>编码</strong>：ASCII码7位表示128个字符；汉字国标码/区位码；<strong>机内码</strong>用于计算机内部存储。</p>
<p><strong>60.</strong> <strong>逻辑运算</strong>：与（AND）、或（OR）、非（NOT）、异或（XOR）。异或：相同为0，不同为1。</p>
<p><strong>61.</strong> <strong>码制转换</strong>：原码→反码→补码→移码的转换链条必须熟练。</p>
<p><strong>62.</strong> <strong>零的表示</strong>：原码和反码中零有<strong>正零和负零</strong>两种；补码和移码中零<strong>唯一</strong>。</p>
<p><strong>63.</strong> <strong>浮点数溢出判断</strong>：阶码溢出才是真正的溢出，尾数溢出可通过规格化纠正。</p>
<p><strong>64.</strong> <strong>CPU中的寄存器</strong>：用户可见的有PC、通用寄存器；用户不可见（透明）的有IR、MAR、MDR。</p>
<p><strong>65.</strong> <strong>多核CPU</strong>：每个核心有独立的运算器和控制器，共享Cache和内存。</p>
<h2 id="二操作系统66130">二、操作系统（66—130）</h2>
<p><strong>66.</strong> <strong>进程三态转换</strong>：<strong>运行 → 阻塞</strong>（等待I/O或事件）；<strong>阻塞 → 就绪</strong>（事件完成）；<strong>就绪 → 运行</strong>（被调度）。</p>
<p><strong>67.</strong> 进程<strong>不能从阻塞直接转为运行</strong>，必须先转为就绪再被调度。</p>
<p><strong>68.</strong> <strong>进程与线程的资源共享</strong>：同一进程内线程<strong>共享</strong>代码段、全局变量、打开文件；但<strong>每个线程有独立栈和寄存器</strong>。</p>
<p><strong>69.</strong> 线程<strong>共享</strong>内容：进程代码段、公有数据、打开的文件描述符、信号处理器、当前目录、用户ID与组ID。</p>
<p><strong>70.</strong> 线程<strong>独有</strong>内容：线程ID、独立寄存器组、线程堆栈（栈指针）、错误返回码、信号屏蔽码。</p>
<p><strong>71.</strong> <strong>线程是CPU调度的基本单位，进程是资源分配的基本单位</strong>。</p>
<p><strong>72.</strong> <strong>PV操作方向</strong>：前趋图中，箭线<strong>起点是V</strong>（前驱完成通知），<strong>终点是P</strong>（后继开始前等待）。互斥信号量初值<strong>1</strong>，同步信号量初值通常<strong>0</strong>。</p>
<p><strong>73.</strong> <strong>P操作</strong>：P(S)使S = S − 1；若S &lt; 0，进程阻塞。</p>
<p><strong>74.</strong> <strong>V操作</strong>：V(S)使S = S + 1；若S ≤ 0，唤醒一个等待进程。</p>
<p><strong>75.</strong> <strong>死锁四个必要条件</strong>：<strong>互斥、请求与保持、不可剥夺、循环等待</strong>。四个同时满足才可能死锁。</p>
<p><strong>76.</strong> <strong>不死锁判定公式</strong>：<strong>m × (w − 1) + 1 ≤ n</strong> 时不可能死锁（m进程数，w每进程最大需求，n资源总数）。</p>
<p><strong>77.</strong> <strong>银行家算法</strong>：用于<strong>避免死锁</strong>，判断系统是否存在<strong>安全序列</strong>。</p>
<p><strong>78.</strong> <strong>死锁预防</strong>：<strong>破坏四个必要条件之一</strong>；<strong>死锁避免</strong>：银行家算法动态判断。概念辨析是2025年考点。</p>
<p><strong>79.</strong> <strong>页式存储地址转换</strong>：逻辑地址 = <strong>页号 + 页内偏移</strong>；页表把页号映射为物理块号。</p>
<p><strong>80.</strong> <strong>页面淘汰原则</strong>：<strong>被修改过的脏页要写回磁盘</strong>，未修改的直接丢弃。</p>
<p><strong>81.</strong> <strong>FIFO页面置换</strong>可能产生<strong>Belady异常</strong>（物理块增多，缺页率反而上升）；<strong>LRU不会</strong>。</p>
<p><strong>82.</strong> <strong>LRU（最近最少使用）</strong> ：淘汰最长时间未被访问的页面。</p>
<p><strong>83.</strong> <strong>OPT（最优置换）</strong> ：淘汰未来最长时间内不被访问的页面，理论最优但不可实现。</p>
<p><strong>84.</strong> <strong>页表的作用</strong>：记录页号到物理块号的映射，页表项还包含有效位、修改位、访问位等。</p>
<p><strong>85.</strong> <strong>快表（TLB）</strong> ：存放最近使用的页表项，加速地址转换，依据局部性原理。</p>
<p><strong>86.</strong> <strong>段页式存储</strong>：先分段，段内再分页。地址 = <strong>段号 + 页号 + 页内偏移</strong>。</p>
<p><strong>87.</strong> <strong>内存碎片</strong>：<strong>固定分区</strong>产生内部碎片；<strong>动态分区</strong>产生外部碎片；<strong>页式</strong>有内部碎片无外部碎片；<strong>段式</strong>有外部碎片。</p>
<p><strong>88.</strong> <strong>文件物理结构</strong>：<strong>连续结构</strong>存取快但产生碎片；<strong>链接结构</strong>无碎片但只能顺序访问；<strong>索引结构</strong>支持随机访问，索引表占空间。</p>
<p><strong>89.</strong> <strong>绝对路径</strong>：从<strong>根目录</strong>开始；<strong>相对路径</strong>：从<strong>当前工作目录</strong>开始。</p>
<p><strong>90.</strong> <strong>树形目录结构</strong>：支持文件重名、便于分类管理，绝对路径和相对路径结合使用。</p>
<p><strong>91.</strong> <strong>索引文件</strong>：通过索引表访问文件块，支持随机存取。</p>
<p><strong>92.</strong> <strong>位示图</strong>：用二进制位表示磁盘块占用情况，常考字号、位号计算。</p>
<p><strong>93.</strong> <strong>磁盘调度算法</strong>：<strong>SSTF（最短寻道时间优先）</strong> 可能饥饿；<strong>SCAN（电梯算法）</strong> 兼顾方向；<strong>FCFS</strong> 最简单。</p>
<p><strong>94.</strong> <strong>进程调度算法</strong>：FCFS简单但平均等待时间长；短作业优先平均等待最短但可能饥饿；时间片轮转公平但上下文切换开销大。</p>
<p><strong>95.</strong> <strong>临界区</strong>：一次只允许一个进程进入的代码段。PV操作实现互斥访问临界区。</p>
<p><strong>96.</strong> <strong>信号量</strong>：互斥信号量用于<strong>互斥</strong>，初值1；同步信号量用于<strong>同步</strong>，初值视条件而定（常为0）。</p>
<p><strong>97.</strong> <strong>进程同步的临界资源</strong>：打印机、共享变量等一次只允许一个进程使用的资源。</p>
<p><strong>98.</strong> <strong>线程的实现方式</strong>：用户级线程（内核不可见，切换快）和内核级线程（内核管理，可多核并行）。</p>
<p><strong>99.</strong> <strong>死锁检测</strong>：通过<strong>资源分配图</strong>判断是否存在环路；有环不一定死锁（同类资源多个时）。</p>
<p><strong>100.</strong> <strong>虚拟存储的容量</strong>：由<strong>地址空间</strong>决定（逻辑地址位数），不是由内存和外存容量简单相加决定。</p>
<p><strong>101.</strong> <strong>I/O软件层次</strong>：用户层I/O → 设备独立性软件 → 设备驱动程序 → 中断处理程序 → 硬件。</p>
<p><strong>102.</strong> <strong>Spooling系统的组成</strong>：输入井、输出井、输入缓冲区、输出缓冲区、输入进程、输出进程。</p>
<p><strong>103.</strong> <strong>操作系统的四大功能</strong>：<strong>进程管理、存储管理、文件管理、设备管理</strong>。</p>
<p><strong>104.</strong> <strong>操作系统的特征</strong>：<strong>并发、共享、虚拟、异步</strong>。</p>
<p><strong>105.</strong> <strong>批处理系统</strong>：吞吐量大，但交互性差；<strong>分时系统</strong>：交互性好，响应时间短；<strong>实时系统</strong>：对响应时间有严格限制。</p>
<p><strong>106.</strong> <strong>PCB（进程控制块）</strong> ：进程存在的唯一标志，包含进程标识符、状态、优先级、资源清单等。</p>
<p><strong>107.</strong> <strong>进程创建</strong>：分配PCB、分配资源、调入内存、转入就绪态。</p>
<p><strong>108.</strong> <strong>进程终止</strong>：释放资源、回收PCB。</p>
<p><strong>109.</strong> <strong>进程切换</strong>：保存当前进程上下文，恢复新进程上下文，开销较大。</p>
<p><strong>110.</strong> <strong>线程切换</strong>：同进程内线程切换只需保存/恢复寄存器和栈指针，开销小于进程切换。</p>
<p><strong>111.</strong> <strong>作业调度</strong>：从后备队列选择作业调入内存；<strong>进程调度</strong>：从就绪队列选择进程分配CPU。</p>
<p><strong>112.</strong> <strong>多级反馈队列调度</strong>：兼顾不同类型进程，短作业优先、长作业不饥饿。</p>
<p><strong>113.</strong> <strong>优先级调度</strong>：静态优先级和动态优先级；可能产生<strong>优先级反转</strong>问题。</p>
<p><strong>114.</strong> <strong>优先级反转</strong>：低优先级进程持有高优先级进程需要的资源，导致高优先级进程被阻塞。</p>
<p><strong>115.</strong> <strong>管程</strong>：封装共享资源和操作，简化同步编程，避免PV操作的分散错误。</p>
<p><strong>116.</strong> <strong>生产者—消费者问题</strong>：用PV操作实现同步与互斥，缓冲区是临界资源。</p>
<p><strong>117.</strong> <strong>读者—写者问题</strong>：允许多个读者同时读，写者与任何人互斥。</p>
<p><strong>118.</strong> <strong>哲学家进餐问题</strong>：死锁的经典场景，通过限制同时拿筷子的哲学家数量来预防。</p>
<p><strong>119.</strong> <strong>文件逻辑结构</strong>：有结构文件（记录式）和无结构文件（流式）。</p>
<p><strong>120.</strong> <strong>文件目录</strong>：FCB（文件控制块）存放文件名、物理位置、大小、权限等。</p>
<p><strong>121.</strong> <strong>文件共享</strong>：硬链接（多个目录项指向同一inode）和软链接（符号链接，指向路径名）。</p>
<p><strong>122.</strong> <strong>文件保护</strong>：访问控制矩阵、访问控制表（ACL）、能力表。</p>
<p><strong>123.</strong> <strong>磁盘空间管理</strong>：空闲表法、空闲链表法、位示图法、成组链接法。</p>
<p><strong>124.</strong> <strong>成组链接法</strong>：UNIX常用，将空闲块分组管理，节省空间。</p>
<p><strong>125.</strong> <strong>设备管理</strong>：独占设备、共享设备、虚拟设备。</p>
<p><strong>126.</strong> <strong>设备驱动程序</strong>：与硬件直接交互，屏蔽硬件差异。</p>
<p><strong>127.</strong> <strong>中断处理程序</strong>：处理I/O完成中断，唤醒等待进程。</p>
<p><strong>128.</strong> <strong>缓冲技术</strong>：单缓冲、双缓冲、多缓冲，缓解CPU与I/O速度不匹配。</p>
<p><strong>129.</strong> <strong>操作系统启动过程</strong>：BIOS自检 → 引导程序 → 加载内核 → 初始化 → 启动用户态。</p>
<p><strong>130.</strong> <strong>微内核结构</strong>：内核只保留最基本功能，其他服务在用户态运行，提高可靠性和可扩展性。</p>
<h2 id="三数据结构与算法131215">三、数据结构与算法（131—215）</h2>
<p><strong>131.</strong> <strong>栈的特性</strong>：<strong>后进先出（LIFO）</strong> 。2025年直接考“栈遵循先进先出”为错误选项。</p>
<p><strong>132.</strong> <strong>队列的特性</strong>：<strong>先进先出（FIFO）</strong> 。</p>
<p><strong>133.</strong> <strong>循环队列</strong>解决顺序队列的假溢出。判满：<strong>(rear + 1) % maxSize == front</strong>（牺牲一个单元）。</p>
<p><strong>134.</strong> <strong>链表操作</strong>：<strong>删除最后一个结点需要遍历链表</strong>；删除/插入头结点只需操作头指针。</p>
<p><strong>135.</strong> <strong>顺序表</strong>：随机访问O(1)，插入/删除平均O(n)。</p>
<p><strong>136.</strong> <strong>单链表</strong>：不支持随机访问，查找O(n)，插入/删除已知前驱时O(1)。</p>
<p><strong>137.</strong> <strong>双向链表</strong>：可双向遍历，插入/删除更方便。</p>
<p><strong>138.</strong> <strong>栈的应用</strong>：表达式求值、括号匹配、递归实现、函数调用。</p>
<p><strong>139.</strong> <strong>队列的应用</strong>：BFS、缓冲区、任务调度。</p>
<p><strong>140.</strong> <strong>二叉树遍历</strong>：前序“<strong>根左右</strong>”，中序“<strong>左根右</strong>”，后序“<strong>左右根</strong>”。</p>
<p><strong>141.</strong> <strong>二叉树还原</strong>：已知<strong>中序 + 前序</strong>或<strong>中序 + 后序</strong>可唯一还原；已知前序 + 后序<strong>不能</strong>唯一确定。</p>
<p><strong>142.</strong> <strong>完全二叉树</strong>：结点编号与满二叉树对应，叶子只出现在最后两层。</p>
<p><strong>143.</strong> <strong>满二叉树结点数</strong>：深度为k的满二叉树有 <strong>2^k − 1</strong> 个结点。</p>
<p><strong>144.</strong> <strong>二叉树性质</strong>：第i层最多 <strong>2^(i−1)</strong> 个结点；深度为k最多 <strong>2^k − 1</strong> 个结点。</p>
<p><strong>145.</strong> <strong>哈夫曼树</strong>：<strong>权值小的优先合并</strong>，带权路径长度最小。频率高的字符编码短。</p>
<p><strong>146.</strong> <strong>哈夫曼编码</strong>是<strong>前缀编码</strong>，任一编码不是另一编码的前缀。</p>
<p><strong>147.</strong> <strong>二叉排序树（BST）</strong> ：左子树 &lt; 根 &lt; 右子树；<strong>中序遍历得到有序序列</strong>。</p>
<p><strong>148.</strong> <strong>平衡二叉树（AVL）</strong> ：左右子树高度差绝对值 ≤ 1。插入/删除可能需要旋转（LL、RR、LR、RL）。</p>
<p><strong>149.</strong> <strong>B树</strong>：多路平衡查找树，每个结点既存关键字也存数据。</p>
<p><strong>150.</strong> <strong>B+树</strong>：只有<strong>叶子结点存数据</strong>，非叶子只存索引，叶子间用指针连成链表，<strong>范围查询更快</strong>。数据库索引常用。</p>
<p><strong>151.</strong> <strong>图的存储</strong>：<strong>邻接矩阵适合稠密图</strong>，<strong>邻接表适合稀疏图</strong>。</p>
<p><strong>152.</strong> <strong>邻接矩阵</strong>：空间O(n²)，判断两点是否相邻O(1)；<strong>邻接表</strong>：空间O(n+e)，遍历邻接点高效。</p>
<p><strong>153.</strong> <strong>图的遍历</strong>：<strong>DFS用栈</strong>（递归），<strong>BFS用队列</strong>。</p>
<p><strong>154.</strong> <strong>拓扑排序</strong>：用于AOV网，每次输出<strong>入度为0</strong>的顶点；能输出全部顶点则无环。</p>
<p><strong>155.</strong> <strong>关键路径</strong>：AOE网中<strong>最长路径</strong>，关键活动时间裕量为<strong>0</strong>。</p>
<p><strong>156.</strong> <strong>最小生成树</strong>：<strong>Prim适合稠密图</strong>，<strong>Kruskal适合稀疏图</strong>。</p>
<p><strong>157.</strong> <strong>最短路径</strong>：<strong>Dijkstra</strong>单源、不能负权；<strong>Floyd</strong>多源、可负权但不能负环。</p>
<p><strong>158.</strong> <strong>排序平均复杂度O(n log n)</strong> ：<strong>快排、归并、堆排</strong>（“快归堆”）。</p>
<p><strong>159.</strong> <strong>排序平均复杂度O(n²)</strong> ：<strong>冒泡、插入、选择</strong>（“冒插选”）。</p>
<p><strong>160.</strong> <strong>不稳定排序</strong>：<strong>快排、希尔、选择、堆排</strong>（“快希选堆”）。</p>
<p><strong>161.</strong> <strong>稳定排序</strong>：冒泡、插入、归并、基数。</p>
<p><strong>162.</strong> <strong>快速排序最坏情况</strong>：基本有序时退化为 <strong>O(n²)</strong> 。</p>
<p><strong>163.</strong> <strong>归并排序空间复杂度</strong>：<strong>O(n)</strong> ，需要辅助数组。</p>
<p><strong>164.</strong> <strong>堆排序建堆时间</strong>：<strong>O(n)</strong> ；每次调整 <strong>O(log n)</strong> ，总时间 <strong>O(n log n)</strong> 。</p>
<p><strong>165.</strong> <strong>冒泡排序</strong>：稳定，最好O(n)（已有序），最坏O(n²)。</p>
<p><strong>166.</strong> <strong>插入排序</strong>：稳定，基本有序时效率高。</p>
<p><strong>167.</strong> <strong>选择排序</strong>：不稳定，无论初始顺序如何都是O(n²)。</p>
<p><strong>168.</strong> <strong>希尔排序</strong>：不稳定，是插入排序的改进，按增量分组。</p>
<p><strong>169.</strong> <strong>基数排序</strong>：稳定，非比较排序，时间复杂度O(d(n+r))。</p>
<p><strong>170.</strong> <strong>二分查找</strong>：必须<strong>有序</strong>，时间复杂度 <strong>O(log n)</strong> 。</p>
<p><strong>171.</strong> <strong>二分查找最多比较次数</strong>：⌈log₂(n+1)⌉。</p>
<p><strong>172.</strong> <strong>哈希冲突处理</strong>：<strong>开放定址法</strong>（线性探测、二次探测）、<strong>链地址法</strong>。链地址法不产生聚集。</p>
<p><strong>173.</strong> <strong>哈希表查找</strong>：理想情况O(1)，冲突严重时退化。</p>
<p><strong>174.</strong> <strong>时间复杂度增长顺序</strong>：<strong>O(1) &lt; O(log n) &lt; O(n) &lt; O(n log n) &lt; O(n²) &lt; O(n³) &lt; O(2ⁿ) &lt; O(n!)</strong> 。</p>
<p><strong>175.</strong> <strong>分治法</strong>：将问题分解为<strong>相互独立</strong>的子问题，递归求解后合并（归并排序、二分查找）。</p>
<p><strong>176.</strong> <strong>动态规划</strong>：子问题<strong>重叠</strong>，用表格保存已解子问题，避免重复计算。典型：0-1背包、最长公共子序列。</p>
<p><strong>177.</strong> <strong>贪心法</strong>：每步选<strong>当前最优</strong>，不回溯，不保证全局最优。典型：活动安排、哈夫曼编码、Dijkstra。</p>
<p><strong>178.</strong> <strong>回溯法</strong>：<strong>递归尝试 + 剪枝 + 恢复现场</strong>。典型：N皇后、排列组合。</p>
<p><strong>179.</strong> <strong>递归与栈</strong>：递归调用使用<strong>系统栈</strong>保存返回地址和局部变量。</p>
<p><strong>180.</strong> <strong>字符串</strong>：朴素匹配最坏O(mn)；KMP利用next数组达到O(m+n)。</p>
<p><strong>181.</strong> <strong>数组与矩阵</strong>：二维数组按行存储和按列存储的地址计算公式不同。</p>
<p><strong>182.</strong> <strong>特殊矩阵压缩</strong>：对称矩阵、三角矩阵、对角矩阵的压缩存储。</p>
<p><strong>183.</strong> <strong>稀疏矩阵压缩</strong>：三元组表（行、列、值）或十字链表。</p>
<p><strong>184.</strong> <strong>广义表</strong>：表头（第一个元素）、表尾（除表头外的剩余元素）。长度和深度的计算。</p>
<p><strong>185.</strong> <strong>树转二叉树</strong>：<strong>左孩子右兄弟</strong>。</p>
<p><strong>186.</strong> <strong>森林转二叉树</strong>：每棵树转二叉树后，根结点依次相连。</p>
<p><strong>187.</strong> <strong>顺序查找</strong>：O(n)，不要求有序。</p>
<p><strong>188.</strong> <strong>分块查找</strong>：块间有序、块内无序，平均性能介于顺序和二分之间。</p>
<p><strong>189.</strong> <strong>动态查找表</strong>：二叉排序树、AVL树、B树、B+树、哈希表。</p>
<p><strong>190.</strong> <strong>静态查找表</strong>：顺序查找、二分查找、分块查找。</p>
<p><strong>191.</strong> <strong>算法效率度量</strong>：时间复杂度和空间复杂度，关注最坏情况。</p>
<p><strong>192.</strong> <strong>递归算法</strong>：必须有递归出口，否则无限递归。</p>
<p><strong>193.</strong> <strong>迭代与递归</strong>：迭代通常效率更高（无函数调用开销），递归代码更简洁。</p>
<p><strong>194.</strong> <strong>稳定排序的选择</strong>：需要保留相等元素原始顺序时，选择冒泡、插入、归并、基数。</p>
<p><strong>195.</strong> <strong>基本有序时的排序选择</strong>：插入排序或冒泡排序效率最高。</p>
<p><strong>196.</strong> <strong>空间紧张时的排序选择</strong>：堆排序（原地排序，空间O(1)）。</p>
<p><strong>197.</strong> <strong>求Top K问题</strong>：用堆排序或快速选择，不需要完全排序。</p>
<p><strong>198.</strong> <strong>外部排序</strong>：数据量大于内存时使用，多路归并。</p>
<p><strong>199.</strong> <strong>贪心法与动态规划的区别</strong>：贪心不回溯、不保证最优；动态规划保存子问题解、保证最优。</p>
<p><strong>200.</strong> <strong>分治法与动态规划的区别</strong>：分治子问题独立；动态规划子问题重叠。</p>
<p><strong>201.</strong> <strong>回溯法的剪枝</strong>：提前判断当前路径不可能产生解，减少搜索空间。</p>
<p><strong>202.</strong> <strong>N皇后问题</strong>：n=4时解为<strong>2</strong>。2025下半年算法题直接考查。</p>
<p><strong>203.</strong> <strong>背包问题</strong>：0-1背包用动态规划；部分背包用贪心法。</p>
<p><strong>204.</strong> <strong>最长公共子序列（LCS）</strong> ：动态规划，时间复杂度O(mn)。</p>
<p><strong>205.</strong> <strong>Dijkstra算法</strong>：贪心策略，不能处理负权边。</p>
<p><strong>206.</strong> <strong>Floyd算法</strong>：动态规划思想，三重循环，O(n³)。</p>
<p><strong>207.</strong> <strong>Prim算法</strong>：从顶点出发，每次选连接已选集合和未选集合的最小边。</p>
<p><strong>208.</strong> <strong>Kruskal算法</strong>：按边权排序，用并查集判断是否成环。</p>
<p><strong>209.</strong> <strong>拓扑排序的应用</strong>：判断有向图是否有环、确定任务执行顺序。</p>
<p><strong>210.</strong> <strong>关键路径的应用</strong>：确定工程最短工期，关键活动不能延误。</p>
<p><strong>211.</strong> <strong>AOV网</strong>：顶点表示活动，边表示活动之间的优先关系。</p>
<p><strong>212.</strong> <strong>AOE网</strong>：边表示活动，顶点表示事件，边权表示活动持续时间。</p>
<p><strong>213.</strong> <strong>事件的最早发生时间</strong>：从源点正向计算，取最大值。</p>
<p><strong>214.</strong> <strong>事件的最迟发生时间</strong>：从汇点反向计算，取最小值。</p>
<p><strong>215.</strong> <strong>活动的时间裕量</strong> = 最迟开始时间 − 最早开始时间。裕量为0的活动是关键活动。</p>
<h2 id="四程序设计语言基础216265">四、程序设计语言基础（216—265）</h2>
<p><strong>216.</strong> <strong>编译过程顺序</strong>：<strong>词法分析 → 语法分析 → 语义分析 → 中间代码生成 → 优化 → 目标代码生成</strong>。</p>
<p><strong>217.</strong> <strong>词法分析</strong>：识别单词（标识符、关键字、常数），依据<strong>正规式/有限自动机</strong>。</p>
<p><strong>218.</strong> <strong>语法分析</strong>：判断句子结构是否符合文法，<strong>括号不匹配</strong>在此阶段发现。</p>
<p><strong>219.</strong> <strong>语义分析</strong>：检查语义合法性（变量未声明、类型不匹配）。</p>
<p><strong>220.</strong> <strong>中间代码</strong>：与具体机器无关，便于优化和移植。常见形式：四元式、三元式、逆波兰式。</p>
<p><strong>221.</strong> <strong>目标代码生成</strong>：生成汇编或机器码，与具体硬件相关。</p>
<p><strong>222.</strong> <strong>编译型语言</strong>：先翻译后执行，<strong>效率高但可移植性差</strong>。C、C++是编译型。</p>
<p><strong>223.</strong> <strong>解释型语言</strong>：边翻译边执行，<strong>可移植性好但效率低</strong>。Python、JavaScript是解释型。</p>
<p><strong>224.</strong> <strong>Python特点</strong>：跨平台、开源、解释型、面向对象、动态编程。<strong>不是编译型</strong>。</p>
<p><strong>225.</strong> <strong>有限自动机DFA</strong>：每个状态对同一输入只有<strong>唯一后继</strong>；NFA可有多个后继。</p>
<p><strong>226.</strong> <strong>正规式与有限自动机</strong>：两者<strong>等价</strong>，都用于描述词法规则。</p>
<p><strong>227.</strong> <strong>文法</strong>：上下文无关文法用于描述程序设计语言的语法结构。</p>
<p><strong>228.</strong> <strong>语法树</strong>：根是开始符号，叶子是终结符，内部结点是非终结符。</p>
<p><strong>229.</strong> <strong>传值调用</strong>：形参是实参的<strong>副本</strong>，修改形参不影响实参。</p>
<p><strong>230.</strong> <strong>传址/引用调用</strong>：形参是实参的<strong>地址或别名</strong>，修改形参会改变实参。</p>
<p><strong>231.</strong> <strong>后缀表达式（逆波兰式）</strong> ：操作数在前，运算符在后；用<strong>栈</strong>求值。</p>
<p><strong>232.</strong> <strong>中缀转后缀</strong>：按运算符优先级和括号规则转换，操作数顺序不变。</p>
<p><strong>233.</strong> <strong>程序设计语言的基本成分</strong>：<strong>数据、运算、控制、传输</strong>。控制包括顺序、选择、循环。</p>
<p><strong>234.</strong> <strong>标识符作用域</strong>：局部变量、全局变量、静态局部变量的生命周期和可见性不同。</p>
<p><strong>235.</strong> <strong>静态变量</strong>：生命周期贯穿程序运行，但作用域限于定义它的函数/文件。</p>
<p><strong>236.</strong> <strong>动态语义与静态语义</strong>：静态语义在<strong>编译期</strong>检查（类型匹配）；动态语义在<strong>运行期</strong>体现（数组越界）。</p>
<p><strong>237.</strong> <strong>编译优化</strong>：局部优化、循环优化、全局优化。优化不改变程序语义。</p>
<p><strong>238.</strong> <strong>词法分析的工具</strong>：LEX；<strong>语法分析的工具</strong>：YACC。</p>
<p><strong>239.</strong> <strong>上下文无关文法</strong>：用产生式描述语法，四元组（非终结符、终结符、产生式、开始符号）。</p>
<p><strong>240.</strong> <strong>正规式</strong>：描述词法规则，与有限自动机等价。</p>
<p><strong>241.</strong> <strong>表达式求值</strong>：中缀、前缀、后缀三种形式，后缀最适合计算机求值。</p>
<p><strong>242.</strong> <strong>函数调用的参数传递</strong>：传值、传地址、传名。传名调用在每次使用时重新求值实参。</p>
<p><strong>243.</strong> <strong>递归函数的实现</strong>：依赖系统栈保存返回地址和局部变量。</p>
<p><strong>244.</strong> <strong>变量的存储分配</strong>：静态分配（编译期确定）和动态分配（运行期确定）。</p>
<p><strong>245.</strong> <strong>堆和栈</strong>：栈由编译器自动管理，存放局部变量和返回地址；堆由程序员手动管理（如malloc/new）。</p>
<p><strong>246.</strong> <strong>内存泄漏</strong>：动态分配的内存未释放，导致可用内存逐渐减少。</p>
<p><strong>247.</strong> <strong>编译错误的类型</strong>：词法错误（非法字符）、语法错误（结构错误）、语义错误（类型不匹配）。</p>
<p><strong>248.</strong> <strong>解释器的组成</strong>：词法分析、语法分析、语义分析、执行。</p>
<p><strong>249.</strong> <strong>编译器与解释器的区别</strong>：编译器生成目标代码，解释器直接执行源程序或中间代码。</p>
<p><strong>250.</strong> <strong>程序设计语言的分类</strong>：命令式、函数式、逻辑式、面向对象。Prolog是逻辑式语言。</p>
<p><strong>251.</strong> <strong>C语言的特点</strong>：编译型、静态类型、接近硬件、可移植性较好。</p>
<p><strong>252.</strong> <strong>面向对象语言的特征</strong>：封装、继承、多态。</p>
<p><strong>253.</strong> <strong>动态绑定</strong>：运行时根据对象实际类型决定调用哪个方法（多态的实现机制）。</p>
<p><strong>254.</strong> <strong>静态绑定</strong>：编译时确定方法调用（如重载）。</p>
<p><strong>255.</strong> <strong>重载（Overload）</strong> ：同一作用域内方法名相同、参数列表不同。</p>
<p><strong>256.</strong> <strong>重写（Override）</strong> ：子类重新定义父类的方法，方法签名相同。</p>
<p><strong>257.</strong> <strong>值传递与引用传递的输出判断</strong>：常考程序运行后变量的值，注意形参是否改变实参。</p>
<p><strong>258.</strong> <strong>变量的初始化</strong>：局部变量未初始化时值不确定；全局变量默认初始化为0。</p>
<p><strong>259.</strong> <strong>短路求值</strong>：逻辑与/或运算可能不计算右侧表达式。</p>
<p><strong>260.</strong> <strong>运算符优先级</strong>：括号 &gt; 单目 &gt; 算术 &gt; 关系 &gt; 逻辑 &gt; 赋值。</p>
<p><strong>261.</strong> <strong>类型转换</strong>：隐式转换和显式转换（强制类型转换），注意精度丢失。</p>
<p><strong>262.</strong> <strong>数组名</strong>：在表达式中通常退化为指向首元素的指针。</p>
<p><strong>263.</strong> <strong>指针与引用</strong>：指针可以为空、可以重新赋值；引用必须初始化且不能更换绑定。</p>
<p><strong>264.</strong> <strong>函数指针</strong>：指向函数的指针，可用于回调机制。</p>
<p><strong>265.</strong> <strong>编译预处理</strong>：宏定义、文件包含、条件编译，在编译之前处理。</p>
<h2 id="五软件工程266350">五、软件工程（266—350）</h2>
<p><strong>266.</strong> <strong>瀑布模型</strong>：阶段严格顺序、文档驱动，<strong>不够灵活，难以适应需求变化</strong>。适合需求明确的项目。</p>
<p><strong>267.</strong> <strong>瀑布模型的五个阶段</strong>：需求分析、设计、编码、测试、维护。每阶段有评审和文档控制。</p>
<p><strong>268.</strong> <strong>V模型</strong>：瀑布模型的变体，开发阶段与测试阶段一一对应（需求↔验收测试，设计↔集成测试）。</p>
<p><strong>269.</strong> <strong>原型模型</strong>：快速构建原型验证需求，适合<strong>需求不明确</strong>的项目。</p>
<p><strong>270.</strong> <strong>螺旋模型</strong>：强调<strong>风险分析</strong>，适合大型复杂项目。</p>
<p><strong>271.</strong> <strong>增量模型</strong>：分批交付，每个增量是一个可运行的部分。第一个增量是核心产品。</p>
<p><strong>272.</strong> <strong>敏捷开发</strong>：迭代、快速响应变化；<strong>每日站会</strong>、<strong>结对编程（XP）</strong> 是典型实践。</p>
<p><strong>273.</strong> <strong>极限编程（XP）的价值观</strong>：沟通、简单、反馈、勇气。</p>
<p><strong>274.</strong> <strong>Scrum</strong>：迭代周期称为Sprint，角色有Product Owner、Scrum Master、开发团队。</p>
<p><strong>275.</strong> <strong>喷泉模型</strong>：面向对象开发，阶段<strong>无明显边界、迭代重叠</strong>。</p>
<p><strong>276.</strong> <strong>统一过程（RUP）</strong> ：四个阶段——起始、精化、构建、移交。</p>
<p><strong>277.</strong> <strong>CMM 5级</strong>：<strong>初始级 → 可重复级 → 已定义级 → 已管理级 → 优化级</strong>。</p>
<p><strong>278.</strong> <strong>CMM初始级</strong>：成功依赖个人英雄主义，过程混乱。</p>
<p><strong>279.</strong> <strong>CMM可重复级</strong>：建立基本项目管理过程，跟踪费用、进度、功能。</p>
<p><strong>280.</strong> <strong>CMM已定义级</strong>：过程文档化、标准化。</p>
<p><strong>281.</strong> <strong>CMM已管理级</strong>：对过程和产品质量进行定量管理。</p>
<p><strong>282.</strong> <strong>CMM优化级</strong>：持续过程改进，定量分析。</p>
<p><strong>283.</strong> <strong>CMMI两种表示法</strong>：阶段式模型和连续式模型。</p>
<p><strong>284.</strong> <strong>内聚从高到低</strong>：<strong>功能 &gt; 顺序 &gt; 通信 &gt; 过程 &gt; 时间 &gt; 逻辑 &gt; 偶然</strong>。</p>
<p><strong>285.</strong> <strong>功能内聚</strong>：模块内所有元素<strong>共同完成一个单一功能</strong>，内聚最高。</p>
<p><strong>286.</strong> <strong>偶然内聚</strong>：模块内元素之间没有实质联系，内聚最低。</p>
<p><strong>287.</strong> <strong>逻辑内聚</strong>：模块内执行逻辑上相似的功能，由参数决定执行哪一个。</p>
<p><strong>288.</strong> <strong>时间内聚</strong>：模块内任务必须在同一时间段内执行。</p>
<p><strong>289.</strong> <strong>过程内聚</strong>：模块内任务必须按特定次序执行。</p>
<p><strong>290.</strong> <strong>通信内聚</strong>：模块内所有元素操作同一数据结构或使用同一输入/输出。</p>
<p><strong>291.</strong> <strong>顺序内聚</strong>：模块内一个元素的输出是另一个元素的输入。</p>
<p><strong>292.</strong> <strong>耦合从低到高</strong>：<strong>非直接 &gt; 数据 &gt; 标记 &gt; 控制 &gt; 外部 &gt; 公共 &gt; 内容</strong>。</p>
<p><strong>293.</strong> <strong>内容耦合最高</strong>：一个模块直接访问另一个模块的内部数据或代码。</p>
<p><strong>294.</strong> <strong>数据耦合</strong>：模块间只传递数据参数，耦合最低（除非直接耦合外）。</p>
<p><strong>295.</strong> <strong>设计原则</strong>：<strong>高内聚、低耦合</strong>。</p>
<p><strong>296.</strong> <strong>信息隐蔽</strong>：模块内部实现细节对使用者隐藏，提高可修改性。</p>
<p><strong>297.</strong> <strong>McCabe环路复杂度</strong>：<strong>边 − 节点 + 2</strong>，或<strong>判定节点数 + 1</strong>。</p>
<p><strong>298.</strong> <strong>黑盒测试</strong>：等价类划分、边界值分析，<strong>不关心内部逻辑</strong>。</p>
<p><strong>299.</strong> <strong>白盒测试</strong>：逻辑覆盖（语句、判定、条件、路径）。<strong>路径覆盖覆盖程度最高</strong>。</p>
<p><strong>300.</strong> <strong>语句覆盖</strong>：每条语句至少执行一次，最弱的逻辑覆盖。</p>
<p><strong>301.</strong> <strong>判定覆盖</strong>：每个判断的真假分支至少各执行一次。</p>
<p><strong>302.</strong> <strong>条件覆盖</strong>：每个判断中每个条件的真假至少各出现一次。</p>
<p><strong>303.</strong> <strong>路径覆盖</strong>：所有可能路径至少执行一次，覆盖最强。</p>
<p><strong>304.</strong> <strong>测试顺序</strong>：<strong>单元测试 → 集成测试 → 系统测试 → 验收测试</strong>。</p>
<p><strong>305.</strong> <strong>单元测试</strong>：针对单个模块，常用白盒测试。</p>
<p><strong>306.</strong> <strong>集成测试策略</strong>：一次性集成、自顶向下、自底向上、三明治集成。</p>
<p><strong>307.</strong> <strong>自顶向下集成</strong>：需要桩模块（stub）模拟下层模块。</p>
<p><strong>308.</strong> <strong>自底向上集成</strong>：需要驱动模块（driver）模拟上层模块。</p>
<p><strong>309.</strong> <strong>系统测试</strong>：整个系统与环境集成后测试，黑盒为主。</p>
<p><strong>310.</strong> <strong>验收测试</strong>：用户参与，确认系统满足需求。</p>
<p><strong>311.</strong> <strong>回归测试</strong>：修改代码后重新测试，确保未引入新错误。</p>
<p><strong>312.</strong> <strong>软件维护类型</strong>：<strong>纠错性</strong>（修bug）、<strong>适应性</strong>（环境变化）、<strong>完善性</strong>（加功能/改善性能）、<strong>预防性</strong>（提高可维护性）。</p>
<p><strong>313.</strong> <strong>完善性维护</strong>：占比通常<strong>最大</strong>，为了增加功能或改善性能。</p>
<p><strong>314.</strong> <strong>纠错性维护</strong>：修复运行中发现的错误。</p>
<p><strong>315.</strong> <strong>适应性维护</strong>：适应硬件、软件环境的变化。</p>
<p><strong>316.</strong> <strong>预防性维护</strong>：为了提高未来可维护性而修改软件。</p>
<p><strong>317.</strong> <strong>软件危机表现</strong>：费用和进度失控、质量低下、难以维护、生产率低下。</p>
<p><strong>318.</strong> <strong>SRS（需求规格说明书）特性</strong>：完整性、一致性、可验证性、可修改性、可追踪性。</p>
<p><strong>319.</strong> <strong>需求分析阶段的任务</strong>：确定系统“做什么”，输出<strong>需求规格说明书</strong>。</p>
<p><strong>320.</strong> <strong>概要设计 vs 详细设计</strong>：概要设计定<strong>体系结构和模块划分</strong>；详细设计定<strong>模块内部算法和数据结构</strong>。</p>
<p><strong>321.</strong> <strong>结构化分析方法</strong>：数据流图（DFD）、数据字典、加工说明。核心是<strong>自顶向下逐层分解</strong>。</p>
<p><strong>322.</strong> <strong>结构化设计</strong>：模块化、信息隐蔽、模块独立性。<strong>信息隐蔽</strong>提高可修改性。</p>
<p><strong>323.</strong> <strong>模块独立性</strong>由<strong>内聚和耦合共同决定</strong>。</p>
<p><strong>324.</strong> <strong>软件质量特性（ISO 9126）</strong> ：功能性、可靠性、易用性、效率、可维护性、可移植性。</p>
<p><strong>325.</strong> <strong>可移植性设计</strong>：核心是<strong>屏蔽平台差异</strong>；<strong>“使用特定环境专用功能”是破坏可移植性的做法</strong>。</p>
<p><strong>326.</strong> <strong>风险管理</strong>：风险识别 → 风险预测 → 风险评估 → 风险控制。</p>
<p><strong>327.</strong> <strong>风险暴露</strong> = 风险概率 × 风险影响。</p>
<p><strong>328.</strong> <strong>PERT图</strong>：用于进度管理，边表示任务，节点表示事件，可计算关键路径。</p>
<p><strong>329.</strong> <strong>Gantt图</strong>：横道图，直观展示任务时间，但不能显示任务间依赖关系。</p>
<p><strong>330.</strong> <strong>关键路径</strong>：PERT图中最长路径，决定项目最短工期。</p>
<p><strong>331.</strong> <strong>软件文档</strong>：开发文档、管理文档、用户文档。文档是软件产品的一部分。</p>
<p><strong>332.</strong> <strong>数据流图（DFD）</strong> ：描述数据流动和加工，四种元素——外部实体、加工、数据存储、数据流。</p>
<p><strong>333.</strong> <strong>数据字典</strong>：对DFD中每个元素的定义，包括数据项、数据结构、数据流、数据存储、加工。</p>
<p><strong>334.</strong> <strong>结构化语言</strong>：用于描述加工逻辑，包括顺序、选择、循环。</p>
<p><strong>335.</strong> <strong>判定表/判定树</strong>：描述复杂条件组合下的加工逻辑。</p>
<p><strong>336.</strong> <strong>系统总体结构设计</strong>：将系统划分为模块，确定模块间调用关系。</p>
<p><strong>337.</strong> <strong>模块扇入扇出</strong>：扇入指调用该模块的上级模块数；扇出指该模块调用的下级模块数。</p>
<p><strong>338.</strong> <strong>深度与宽度</strong>：深度指模块调用层次；宽度指同一层模块数。</p>
<p><strong>339.</strong> <strong>软件过程</strong>：软件生命周期中的一系列活动，包括开发过程、管理过程、支持过程。</p>
<p><strong>340.</strong> <strong>软件生命周期</strong>：可行性分析、需求分析、设计、编码、测试、维护。</p>
<p><strong>341.</strong> <strong>可行性分析</strong>：技术可行性、经济可行性、操作可行性、法律可行性。</p>
<p><strong>342.</strong> <strong>成本估算</strong>：COCOMO模型、Putnam模型、专家判断法。</p>
<p><strong>343.</strong> <strong>COCOMO模型</strong>：基于代码行数估算工作量，分为基本、中间、详细三级。</p>
<p><strong>344.</strong> <strong>软件配置管理</strong>：版本控制、变更管理、配置审计。</p>
<p><strong>345.</strong> <strong>基线</strong>：经过评审和批准的配置项，变更需走变更控制流程。</p>
<p><strong>346.</strong> <strong>版本控制工具</strong>：Git、SVN，管理代码版本和协作开发。</p>
<p><strong>347.</strong> <strong>软件复用</strong>：代码复用、设计复用、分析复用、测试复用。</p>
<p><strong>348.</strong> <strong>逆向工程</strong>：从代码恢复设计信息，用于维护和改造遗留系统。</p>
<p><strong>349.</strong> <strong>再工程</strong>：逆向工程 + 重构 + 正向工程，改造旧系统。</p>
<p><strong>350.</strong> <strong>软件质量保证（SQA）</strong> ：通过过程审计和产品评审确保质量。</p>
<h2 id="六面向对象与uml351410">六、面向对象与UML（351—410）</h2>
<p><strong>351.</strong> <strong>类图关系强弱</strong>：<strong>依赖 &lt; 关联 &lt; 聚合 &lt; 组合 &lt; 泛化（继承）&lt; 实现</strong>。</p>
<p><strong>352.</strong> <strong>聚合 vs 组合</strong>：聚合<strong>空心菱形</strong>，整体消失部分还在；组合<strong>实心菱形</strong>，整体消失部分随之销毁。</p>
<p><strong>353.</strong> <strong>菱形位置</strong>：聚合/组合的<strong>菱形永远在整体一端</strong>。</p>
<p><strong>354.</strong> <strong>泛化</strong>：<strong>实线 + 空心三角</strong>，三角指向<strong>父类</strong>。</p>
<p><strong>355.</strong> <strong>实现</strong>：<strong>虚线 + 空心三角</strong>，三角指向<strong>接口</strong>。</p>
<p><strong>356.</strong> <strong>依赖</strong>：<strong>虚线 + 开口箭头</strong>，指向被依赖类。方法参数/局部变量中使用，临时关系。</p>
<p><strong>357.</strong> <strong>关联</strong>：<strong>实线</strong>，成员变量持有，长期结构关系。</p>
<p><strong>358.</strong> <strong>用例关系include</strong>：<strong>虚线箭头指向被包含用例</strong>，表示<strong>一定会执行</strong>的公共行为。</p>
<p><strong>359.</strong> <strong>用例关系extend</strong>：<strong>虚线箭头指向基础用例</strong>，表示<strong>有条件才执行</strong>的扩展行为。</p>
<p><strong>360.</strong> <strong>include与extend箭头口诀</strong>：<strong>“包箭被，扩箭基”</strong> ——include指被包含，extend指基础。</p>
<p><strong>361.</strong> <strong>参与者</strong>：<strong>火柴人</strong>，画在系统边界<strong>之外</strong>。<strong>“时间”不能作为参与者</strong>。</p>
<p><strong>362.</strong> <strong>用例名</strong>：<strong>动词 + 名词</strong>，对参与者有可观测的完整结果；<strong>不拆内部步骤</strong>（“验证密码”不是用例）。</p>
<p><strong>363.</strong> <strong>UML静态图</strong>：类图、对象图、用例图、构件图、部署图。</p>
<p><strong>364.</strong> <strong>UML动态图</strong>：序列图、通信图、状态图、活动图。</p>
<p><strong>365.</strong> <strong>通信图</strong>：强调<strong>对象之间的链</strong>和消息，与序列图语义相近。</p>
<p><strong>366.</strong> <strong>对象图</strong>：某一时刻<strong>一组对象及关系</strong>的快照。</p>
<p><strong>367.</strong> <strong>状态图</strong>：描述<strong>一个对象</strong>在生命周期内的状态变迁。</p>
<p><strong>368.</strong> <strong>活动图</strong>：描述<strong>流程和并行行为</strong>，有分叉/汇合。</p>
<p><strong>369.</strong> <strong>部署图</strong>：<strong>实施阶段</strong>使用，描述物理节点和构件分布。</p>
<p><strong>370.</strong> <strong>序列图</strong>：强调<strong>消息传递的时间顺序</strong>。对象框、生命线（虚线）、激活期（长条）。</p>
<p><strong>371.</strong> <strong>序列图消息类型</strong>：同步消息（实心三角）、异步消息（空心三角）、返回消息（虚线开口箭头）。</p>
<p><strong>372.</strong> <strong>多重度</strong>：<code>1</code>恰好一个；<code>0..1</code>零或一个；<code>0..*</code>或<code>*</code>零个或多个；<code>1..*</code>至少一个。</p>
<p><strong>373.</strong> <strong>多重度判定</strong>：<strong>站在每一端看对方</strong>，两端都要标。</p>
<p><strong>374.</strong> <strong>开闭原则</strong>：对<strong>扩展开放</strong>，对<strong>修改关闭</strong>。</p>
<p><strong>375.</strong> <strong>单一职责原则</strong>：一个类只做一种类型的功能，修改原因只有一个。</p>
<p><strong>376.</strong> <strong>里氏替换原则</strong>：子类可以替换父类而不改变程序正确性。</p>
<p><strong>377.</strong> <strong>依赖倒置原则</strong>：依赖抽象，不依赖具体实现。</p>
<p><strong>378.</strong> <strong>接口隔离原则</strong>：使用多个专门接口，而不是一个庞大接口。</p>
<p><strong>379.</strong> <strong>迪米特法则</strong>：一个对象应尽可能少地了解其他对象（最少知识原则）。</p>
<p><strong>380.</strong> <strong>封装</strong>：隐藏内部实现细节，只暴露必要接口。</p>
<p><strong>381.</strong> <strong>继承</strong>：子类获得父类的属性和方法，实现代码复用。</p>
<p><strong>382.</strong> <strong>多态</strong>：同一接口，不同实现。运行时根据实际对象类型决定调用哪个方法（动态绑定）。</p>
<p><strong>383.</strong> <strong>抽象类</strong>：不能实例化，可包含抽象方法和具体方法。</p>
<p><strong>384.</strong> <strong>接口</strong>：只包含抽象方法（Java 8后可有默认方法），类可以实现多个接口。</p>
<p><strong>385.</strong> <strong>面向对象分析（OOA）</strong> ：识别对象、类、关系，建立分析模型。</p>
<p><strong>386.</strong> <strong>面向对象设计（OOD）</strong> ：设计类结构、接口、交互，建立设计模型。</p>
<p><strong>387.</strong> <strong>面向对象测试</strong>：单元测试类，集成测试类间交互，系统测试整体行为。</p>
<p><strong>388.</strong> <strong>继承的透明性</strong>：子类应能完全替代父类（里氏替换）。</p>
<p><strong>389.</strong> <strong>组合优于继承</strong>：组合更灵活，避免继承层次过深。</p>
<p><strong>390.</strong> <strong>UML中的接口</strong>：用类框加<code>«interface»</code>构造型表示，或圆形表示。</p>
<p><strong>391.</strong> <strong>构件图</strong>：描述软件构件及其依赖关系。</p>
<p><strong>392.</strong> <strong>包图</strong>：将模型元素分组，管理复杂度。</p>
<p><strong>393.</strong> <strong>UML用例图的作用</strong>：描述系统功能需求，参与者与用例的关系。</p>
<p><strong>394.</strong> <strong>UML类图的作用</strong>：描述系统静态结构，类的属性和方法及关系。</p>
<p><strong>395.</strong> <strong>UML序列图的作用</strong>：描述对象间按时间顺序的消息交互。</p>
<p><strong>396.</strong> <strong>UML状态图的作用</strong>：描述对象状态变迁及触发事件。</p>
<p><strong>397.</strong> <strong>UML活动图的作用</strong>：描述业务流程或算法流程，支持并行。</p>
<p><strong>398.</strong> <strong>UML部署图的作用</strong>：描述系统物理部署，节点和构件的分布。</p>
<p><strong>399.</strong> <strong>用例之间的泛化</strong>：子用例继承父用例的行为，空心三角指向父用例。</p>
<p><strong>400.</strong> <strong>参与者之间的泛化</strong>：子参与者继承父参与者的用例。</p>
<p><strong>401.</strong> <strong>包含关系是必须的</strong>，扩展关系是可选的。</p>
<p><strong>402.</strong> <strong>类图中的可见性</strong>：<code>+</code>公开，<code>-</code>私有，<code>#</code>保护，<code>~</code>包内可见。</p>
<p><strong>403.</strong> <strong>抽象操作</strong>：斜体表示，子类必须实现。</p>
<p><strong>404.</strong> <strong>静态成员</strong>：下划线表示，属于类而非实例。</p>
<p><strong>405.</strong> <strong>关联类</strong>：关联本身也可以有属性，用类表示并连接到关联线上。</p>
<p><strong>406.</strong> <strong>限定关联</strong>：用限定符缩小多重度范围。</p>
<p><strong>407.</strong> <strong>依赖的构造型</strong>：<code>«use»</code>、<code>«call»</code>、<code>«create»</code>等。</p>
<p><strong>408.</strong> <strong>UML中的注释</strong>：折角矩形，用虚线连接到被注释元素。</p>
<p><strong>409.</strong> <strong>UML图的选择</strong>：静态结构选类图，交互选序列图/通信图，流程选活动图，状态变迁选状态图。</p>
<p><strong>410.</strong> <strong>面向对象方法的优点</strong>：可复用、可扩展、可维护，映射问题域自然。</p>
<h2 id="七数据库系统411465">七、数据库系统（411—465）</h2>
<p><strong>411.</strong> <strong>三级模式</strong>：<strong>外模式（用户视图）、模式（逻辑结构）、内模式（物理存储）</strong>。</p>
<p><strong>412.</strong> <strong>两级映像</strong>：<strong>外模式/模式映像</strong>保证<strong>逻辑独立性</strong>；<strong>模式/内模式映像</strong>保证<strong>物理独立性</strong>。</p>
<p><strong>413.</strong> <strong>E-R转关系模式 1:N</strong>：<strong>1端主键放入N端</strong>作外键。方向不能反。</p>
<p><strong>414.</strong> <strong>E-R转关系模式 M:N</strong>：<strong>必须单独建表</strong>，主键 = 双方主键<strong>组合</strong>，双方主键各自也是外键。</p>
<p><strong>415.</strong> <strong>E-R转关系模式 1:1</strong>：任一方主键放入另一方作外键（或两表合并）。</p>
<p><strong>416.</strong> <strong>范式 1NF</strong>：属性<strong>不可再分</strong>。</p>
<p><strong>417.</strong> <strong>范式 2NF</strong>：消除<strong>非主属性对码的部分函数依赖</strong>。</p>
<p><strong>418.</strong> <strong>范式 3NF</strong>：消除<strong>非主属性对码的传递函数依赖</strong>。</p>
<p><strong>419.</strong> <strong>BCNF</strong>：消除主属性对码的部分和传递依赖，比3NF更严格。</p>
<p><strong>420.</strong> <strong>函数依赖</strong>：X→Y表示X决定Y。候选键是能决定所有属性的最小属性集。</p>
<p><strong>421.</strong> <strong>主属性</strong>：包含在任一候选键中的属性；<strong>非主属性</strong>：不包含在任何候选键中的属性。</p>
<p><strong>422.</strong> <strong>部分函数依赖</strong>：非主属性只依赖联合主键的一部分。</p>
<p><strong>423.</strong> <strong>传递函数依赖</strong>：A→B，B→C，且B不是候选键，则A→C是传递依赖。</p>
<p><strong>424.</strong> <strong>事务ACID</strong>：<strong>原子性、一致性、隔离性、持久性</strong>。</p>
<p><strong>425.</strong> <strong>脏读</strong>：读到其他事务<strong>未提交</strong>的数据。避免脏读的隔离级别至少是<strong>读已提交（Read Committed）</strong> 。</p>
<p><strong>426.</strong> <strong>不可重复读</strong>：同一事务内两次读同一数据结果不同（其他事务修改并提交了）。</p>
<p><strong>427.</strong> <strong>幻读</strong>：同一事务内两次查询返回不同行数（其他事务插入/删除了行）。</p>
<p><strong>428.</strong> <strong>隔离级别从低到高</strong>：读未提交 → 读已提交 → 可重复读 → 串行化。</p>
<p><strong>429.</strong> <strong>封锁协议</strong>：一级封锁防<strong>丢失修改</strong>；二级防<strong>脏读</strong>；三级防<strong>不可重复读</strong>。</p>
<p><strong>430.</strong> <strong>共享锁（S锁）</strong> ：读锁，多个事务可同时持有。</p>
<p><strong>431.</strong> <strong>排他锁（X锁）</strong> ：写锁，独占，与任何锁互斥。</p>
<p><strong>432.</strong> <strong>两段锁协议</strong>：加锁阶段和解锁阶段，保证可串行化。</p>
<p><strong>433.</strong> <strong>SQL的WHERE与HAVING</strong>：<strong>WHERE过滤行</strong>（分组前）；<strong>HAVING过滤分组后的组</strong>。</p>
<p><strong>434.</strong> <strong>SQL聚合函数</strong>：COUNT、SUM、AVG、MAX、MIN。</p>
<p><strong>435.</strong> <strong>多表查询</strong>：必须写<strong>JOIN和连接条件</strong>，漏掉连接条件会变成<strong>笛卡尔积</strong>。</p>
<p><strong>436.</strong> <strong>内连接</strong>：只返回匹配的行；<strong>外连接</strong>：返回不匹配的行（左外、右外、全外）。</p>
<p><strong>437.</strong> <strong>子查询</strong>：嵌套在另一个查询中的查询，可用于IN、EXISTS、比较运算。</p>
<p><strong>438.</strong> <strong>GROUP BY</strong>：按列分组，常与聚合函数配合使用。</p>
<p><strong>439.</strong> <strong>ORDER BY</strong>：排序，ASC升序（默认），DESC降序。</p>
<p><strong>440.</strong> <strong>数据库设计六步骤</strong>：<strong>需求分析 → 概念设计 → 逻辑设计 → 物理设计 → 实施 → 维护和优化</strong>。</p>
<p><strong>441.</strong> <strong>概念设计</strong>：输出E-R图，与具体DBMS无关。</p>
<p><strong>442.</strong> <strong>逻辑设计</strong>：将E-R图转换为关系模式，进行规范化。</p>
<p><strong>443.</strong> <strong>物理设计</strong>：确定存储结构、索引、分区等。</p>
<p><strong>444.</strong> <strong>关系代数运算</strong>：并、差、笛卡尔积、选择、投影、连接。</p>
<p><strong>445.</strong> <strong>选择（σ）</strong> ：按条件筛选行；<strong>投影（π）</strong> ：选择列。</p>
<p><strong>446.</strong> <strong>自然连接</strong>：自动按同名属性等值连接，并去掉重复列。</p>
<p><strong>447.</strong> <strong>笛卡尔积</strong>：两个关系的所有元组组合，通常需要配合选择条件才有意义。</p>
<p><strong>448.</strong> <strong>主键</strong>：唯一标识元组，不能为空，不能重复。</p>
<p><strong>449.</strong> <strong>外键</strong>：引用另一个关系的主键，可以为空，但非空时必须存在。</p>
<p><strong>450.</strong> <strong>实体完整性</strong>：主键不能为空。</p>
<p><strong>451.</strong> <strong>参照完整性</strong>：外键必须引用存在的主键值，或为空。</p>
<p><strong>452.</strong> <strong>用户定义完整性</strong>：业务规则约束（如年龄范围）。</p>
<p><strong>453.</strong> <strong>视图</strong>：虚表，不存储数据，简化查询和权限控制。</p>
<p><strong>454.</strong> <strong>索引</strong>：加速查询，但增加插入/删除/更新的开销。</p>
<p><strong>455.</strong> <strong>聚集索引</strong>：数据物理顺序与索引顺序一致；<strong>非聚集索引</strong>：不一致。</p>
<p><strong>456.</strong> <strong>分布式数据库</strong>：数据分布在多个节点，透明性包括位置透明、分片透明、复制透明。</p>
<p><strong>457.</strong> <strong>数据仓库</strong>：面向主题、集成、相对稳定、反映历史变化，用于分析决策。</p>
<p><strong>458.</strong> <strong>OLTP vs OLAP</strong>：OLTP面向事务处理，OLAP面向分析。</p>
<p><strong>459.</strong> <strong>数据挖掘</strong>：从大量数据中发现模式，分类、聚类、关联规则。</p>
<p><strong>460.</strong> <strong>NoSQL</strong>：非关系型数据库，适合大数据和高并发，如键值、文档、列族、图数据库。</p>
<p><strong>461.</strong> <strong>CAP定理</strong>：一致性、可用性、分区容错性三者不可兼得。</p>
<p><strong>462.</strong> <strong>数据库备份</strong>：完全备份、增量备份、差异备份。</p>
<p><strong>463.</strong> <strong>数据库恢复</strong>：利用日志 undo/redo，将数据库恢复到一致状态。</p>
<p><strong>464.</strong> <strong>检查点</strong>：减少恢复时需要重做的日志量。</p>
<p><strong>465.</strong> <strong>数据库安全</strong>：认证、授权（GRANT/REVOKE）、加密、审计。</p>
<h2 id="八计算机网络与信息安全466510">八、计算机网络与信息安全（466—510）</h2>
<p><strong>466.</strong> <strong>网络设备层次</strong>：<strong>Hub物理层，交换机数据链路层，路由器网络层，网关应用层及以上</strong>。VLAN工作在<strong>数据链路层</strong>。</p>
<p><strong>467.</strong> <strong>集线器（Hub）</strong> ：物理层设备，共享带宽，冲突域扩大。</p>
<p><strong>468.</strong> <strong>交换机</strong>：数据链路层设备，基于MAC地址转发，隔离冲突域。</p>
<p><strong>469.</strong> <strong>路由器</strong>：网络层设备，基于IP地址转发，隔离广播域。</p>
<p><strong>470.</strong> <strong>网关</strong>：应用层及以上，协议转换。</p>
<p><strong>471.</strong> <strong>OSI七层</strong>：物理层 → 数据链路层 → 网络层 → 传输层 → 会话层 → 表示层 → 应用层。</p>
<p><strong>472.</strong> <strong>TCP/IP四层</strong>：网络接口层 → 网际层 → 传输层 → 应用层。</p>
<p><strong>473.</strong> <strong>TCP特点</strong>：<strong>可靠、面向连接、面向字节流</strong>。三次握手建立连接，四次挥手释放连接。</p>
<p><strong>474.</strong> <strong>UDP特点</strong>：<strong>快、无连接、面向报文</strong>，适合视频/语音/DNS。</p>
<p><strong>475.</strong> <strong>三次握手</strong>：SYN → SYN+ACK → ACK，防止已失效的连接请求突然到达。</p>
<p><strong>476.</strong> <strong>四次挥手</strong>：FIN → ACK → FIN → ACK，因为TCP是全双工的。</p>
<p><strong>477.</strong> <strong>常见端口</strong>：<strong>80-HTTP，443-HTTPS，21-FTP，53-DNS，25-SMTP，110-POP3</strong>。</p>
<p><strong>478.</strong> <strong>SMTP</strong>：发送邮件；<strong>POP3/IMAP</strong>：接收邮件。</p>
<p><strong>479.</strong> <strong>HTTP</strong>：超文本传输协议，无状态，默认端口80。</p>
<p><strong>480.</strong> <strong>HTTPS</strong>：HTTP over SSL/TLS，默认端口443，加密传输。</p>
<p><strong>481.</strong> <strong>DNS</strong>：域名解析，通常使用UDP 53端口。</p>
<p><strong>482.</strong> <strong>FTP</strong>：文件传输，使用TCP，控制连接21，数据连接20（主动模式）。</p>
<p><strong>483.</strong> <strong>IP地址分类</strong>：A类1—126，B类128—191，C类192—223，D类组播，E类保留。</p>
<p><strong>484.</strong> <strong>私有地址</strong>：10.0.0.0/8、172.16.0.0/12、192.168.0.0/16。</p>
<p><strong>485.</strong> <strong>子网掩码核心公式</strong>：可用主机数 = <strong>2^h − 2</strong>（h主机位）；前缀 = <strong>32 − h</strong>。</p>
<p><strong>486.</strong> <strong>子网掩码对照表</strong>：/25→126，/26→62，<strong>/27→30</strong>，/28→14，/29→6，/30→2。</p>
<p><strong>487.</strong> 看到“<strong>30台</strong>”直接反应 <strong>/27，255.255.255.224</strong>；看到“62台”直接反应 <strong>/26，255.255.255.192</strong>。</p>
<p><strong>488.</strong> <strong>CIDR</strong>：无分类域间路由，如192.168.1.0/26，前缀表示网络位数。</p>
<p><strong>489.</strong> <strong>网络地址</strong>：主机位全0；<strong>广播地址</strong>：主机位全1。</p>
<p><strong>490.</strong> <strong>加密与签名</strong>：<strong>公钥加密，私钥签名</strong>。加密用对方公钥，签名用自己私钥。</p>
<p><strong>491.</strong> <strong>对称加密</strong>：加密解密同一密钥，速度快，如DES、3DES、AES。</p>
<p><strong>492.</strong> <strong>非对称加密</strong>：公钥和私钥配对，如RSA、ECC。</p>
<p><strong>493.</strong> <strong>数字签名的作用</strong>：保证<strong>不可否认性</strong>和<strong>完整性</strong>，不保证机密性。</p>
<p><strong>494.</strong> <strong>数字证书</strong>：由<strong>CA</strong>签发，绑定<strong>公钥与身份</strong>。</p>
<p><strong>495.</strong> <strong>哈希算法</strong>：<strong>SHA-256</strong>属于哈希；<strong>RSA、ECC</strong>属于非对称加密。</p>
<p><strong>496.</strong> <strong>哈希函数特性</strong>：任意长度输入 → <strong>固定长度输出</strong>，<strong>不可逆</strong>。</p>
<p><strong>497.</strong> <strong>防火墙类型</strong>：包过滤防火墙（速度快、粒度粗）；应用代理网关防火墙（检查应用层、速度慢）；状态检测防火墙（兼顾安全与速度）。</p>
<p><strong>498.</strong> <strong>网络攻击类型</strong>：<strong>DDoS</strong>（流量压垮）、<strong>ARP欺骗</strong>（伪造MAC-IP）、<strong>SQL注入</strong>（拼恶意SQL）、<strong>XSS</strong>（塞恶意脚本）、<strong>重放攻击</strong>（重发截获的认证报文）。</p>
<p><strong>499.</strong> <strong>病毒类型</strong>：Worm蠕虫（自我复制传播）、Trojan木马（伪装）、Backdoor后门、Macro宏病毒（感染文档）。</p>
<p><strong>500.</strong> <strong>入侵检测</strong>：专家系统、模型检测、简单匹配，分为基于主机和基于网络。</p>
<p><strong>501.</strong> <strong>VPN</strong>：虚拟专用网，通过加密隧道在公共网络上传输私有数据。</p>
<p><strong>502.</strong> <strong>SSL/TLS</strong>：传输层安全，为应用层提供加密和认证。</p>
<p><strong>503.</strong> <strong>IPSec</strong>：网络层安全，常用于VPN。</p>
<p><strong>504.</strong> <strong>访问控制</strong>：自主访问控制（DAC）、强制访问控制（MAC）、基于角色的访问控制（RBAC）。</p>
<p><strong>505.</strong> <strong>RBAC</strong>：将权限赋予角色，用户通过角色获得权限，简化管理。</p>
<p><strong>506.</strong> <strong>安全威胁</strong>：中断（破坏可用性）、截获（破坏机密性）、篡改（破坏完整性）、伪造（破坏真实性）。</p>
<p><strong>507.</strong> <strong>计算机犯罪</strong>：非法侵入、破坏数据、传播病毒、网络诈骗。</p>
<p><strong>508.</strong> <strong>网络安全等级保护</strong>：分等级保护、分等级监管。</p>
<p><strong>509.</strong> <strong>无线网络安全</strong>：WEP（已不安全）、WPA/WPA2（较安全）。</p>
<p><strong>510.</strong> <strong>电子邮件安全</strong>：PGP、S/MIME，用于加密和签名邮件。</p>
<p><strong>使用建议</strong>：510条中，真题重复密度最高的是 <strong>1—10（CPU与码制）、13—18（校验与流水线）、66—78（进程与死锁）、131—162（数据结构与排序）、266—316（软件工程）、351—360（UML关系）、411—432（数据库范式与事务）、466—497（网络与加密）</strong> 。优先把这些背到条件反射，其余条目在刷真题时遇到再回头巩固。</p>
]]></content:encoded></item><item><title>应用技术科目</title><link>https://blog.dart.xin/softexam/s105-key-apply/</link><pubDate>Thu, 22 Oct 2026 13:31:21 +0800</pubDate><guid>https://blog.dart.xin/softexam/s105-key-apply/</guid><description>&lt;p&gt;下午应用技术科目共 5 道大题，每题 15 分，合计 75 分。前 4 题必答（数据流图、数据库设计、UML 建模、C 语言算法），第 5/6 题二选一（Java 或 C++ 设计模式）。整体难度分层很明显：&lt;strong&gt;数据流图、数据库、UML 是“套路题”，背熟规则就能拿 12 分以上；算法是“稳分题”，目标 7—10 分；设计模式是“送分题”，模板背熟可以冲满分&lt;/strong&gt;。下面按题型逐一展开，每条都落在可背诵、可套用的规则上。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="一数据流图dfd-15-分必拿题"&gt;一、数据流图（DFD）—— 15 分必拿题&lt;/h3&gt;
&lt;p&gt;数据流图考的是“把业务文字翻译成图，再把图里的错误找出来”。四个元素必须条件反射地识别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外部实体&lt;/strong&gt;：系统边界之外的“人/组织/外部系统”。题干中的“用户、客户、管理员、银行、供应商”都是实体词。&lt;strong&gt;注意&lt;/strong&gt;：如果系统本身是中间件，后台数据库反而应识别为外部实体。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据存储&lt;/strong&gt;：名字带“&lt;strong&gt;文件、表、库、清单、档案&lt;/strong&gt;”的，基本就是存储。找到存储后，看哪个加工需要读写它，补连线。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加工&lt;/strong&gt;：命名必须是“&lt;strong&gt;动词 + 名词&lt;/strong&gt;”（如“生成报告”“验证借书证”），不能只有一个动词。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据流&lt;/strong&gt;：命名必须是&lt;strong&gt;名词短语&lt;/strong&gt;（如“工资数据”“付款通知”），&lt;strong&gt;不能写成“计算工资”&lt;/strong&gt; ——用加工名命名数据流是最高频的扣分点之一。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;答题顺序&lt;/strong&gt;：先读题干圈出所有实体词和存储词 → 对比顶层图与 0 层图的输入输出是否一致 → 逐句读题干，看哪个加工“提到要读某存储但图上没有入线/出线” → 补全缺失的数据流。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三大铁律（违反必扣分）&lt;/strong&gt; ：数据存储之间不能直连；外部实体之间不能直连；每个加工必须有入有出，不能是“数据黑洞”或“数据奇迹”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;采分要点&lt;/strong&gt;：补充数据流时用题干原词，不要自己发明口语化名称；答案写成“&lt;strong&gt;在加工 Y 与存储 Z 之间添加数据流‘XX 信息’&lt;/strong&gt;”，方向要明确。&lt;/p&gt;
&lt;h3 id="二数据库设计--15-分必拿题"&gt;二、数据库设计 —— 15 分必拿题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;先找实体，不要先想表&lt;/strong&gt;。实体通常是业务中需要长期管理的对象（客户、订单、商品、学生、课程）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;E-R 转关系模式三规则（直接背）&lt;/strong&gt; ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1 : 1&lt;/strong&gt;：任一方主键放入另一方作外键（或两表合并）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1 : N&lt;/strong&gt;：&lt;strong&gt;1 端&lt;/strong&gt;的主键放入 &lt;strong&gt;N 端&lt;/strong&gt;作外键。例如客户和订单，订单表里保存客户编号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M : N&lt;/strong&gt;：&lt;strong&gt;必须单独建一张联系表&lt;/strong&gt;，字段 = 双方主键 + 联系自身的属性（如选课成绩、选课时间）。&lt;strong&gt;不能把课程编号直接塞进学生表&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;主键与外键判定&lt;/strong&gt;：题干出现“&lt;strong&gt;唯一标识&lt;/strong&gt;”“&lt;strong&gt;编号&lt;/strong&gt;”“&lt;strong&gt;身份证号&lt;/strong&gt;”的，基本是主键候选。A 表里出现了 B 表的主键，它就是外键。M:N 联系表中，双方主键的组合是主键，同时各自也是外键。&lt;/p&gt;</description><content:encoded><![CDATA[<p>下午应用技术科目共 5 道大题，每题 15 分，合计 75 分。前 4 题必答（数据流图、数据库设计、UML 建模、C 语言算法），第 5/6 题二选一（Java 或 C++ 设计模式）。整体难度分层很明显：<strong>数据流图、数据库、UML 是“套路题”，背熟规则就能拿 12 分以上；算法是“稳分题”，目标 7—10 分；设计模式是“送分题”，模板背熟可以冲满分</strong>。下面按题型逐一展开，每条都落在可背诵、可套用的规则上。</p>
<hr>
<h3 id="一数据流图dfd-15-分必拿题">一、数据流图（DFD）—— 15 分必拿题</h3>
<p>数据流图考的是“把业务文字翻译成图，再把图里的错误找出来”。四个元素必须条件反射地识别：</p>
<ul>
<li><strong>外部实体</strong>：系统边界之外的“人/组织/外部系统”。题干中的“用户、客户、管理员、银行、供应商”都是实体词。<strong>注意</strong>：如果系统本身是中间件，后台数据库反而应识别为外部实体。</li>
<li><strong>数据存储</strong>：名字带“<strong>文件、表、库、清单、档案</strong>”的，基本就是存储。找到存储后，看哪个加工需要读写它，补连线。</li>
<li><strong>加工</strong>：命名必须是“<strong>动词 + 名词</strong>”（如“生成报告”“验证借书证”），不能只有一个动词。</li>
<li><strong>数据流</strong>：命名必须是<strong>名词短语</strong>（如“工资数据”“付款通知”），<strong>不能写成“计算工资”</strong> ——用加工名命名数据流是最高频的扣分点之一。</li>
</ul>
<p><strong>答题顺序</strong>：先读题干圈出所有实体词和存储词 → 对比顶层图与 0 层图的输入输出是否一致 → 逐句读题干，看哪个加工“提到要读某存储但图上没有入线/出线” → 补全缺失的数据流。</p>
<p><strong>三大铁律（违反必扣分）</strong> ：数据存储之间不能直连；外部实体之间不能直连；每个加工必须有入有出，不能是“数据黑洞”或“数据奇迹”。</p>
<p><strong>采分要点</strong>：补充数据流时用题干原词，不要自己发明口语化名称；答案写成“<strong>在加工 Y 与存储 Z 之间添加数据流‘XX 信息’</strong>”，方向要明确。</p>
<h3 id="二数据库设计--15-分必拿题">二、数据库设计 —— 15 分必拿题</h3>
<p><strong>先找实体，不要先想表</strong>。实体通常是业务中需要长期管理的对象（客户、订单、商品、学生、课程）。</p>
<p><strong>E-R 转关系模式三规则（直接背）</strong> ：</p>
<ul>
<li><strong>1 : 1</strong>：任一方主键放入另一方作外键（或两表合并）。</li>
<li><strong>1 : N</strong>：<strong>1 端</strong>的主键放入 <strong>N 端</strong>作外键。例如客户和订单，订单表里保存客户编号。</li>
<li><strong>M : N</strong>：<strong>必须单独建一张联系表</strong>，字段 = 双方主键 + 联系自身的属性（如选课成绩、选课时间）。<strong>不能把课程编号直接塞进学生表</strong>。</li>
</ul>
<p><strong>主键与外键判定</strong>：题干出现“<strong>唯一标识</strong>”“<strong>编号</strong>”“<strong>身份证号</strong>”的，基本是主键候选。A 表里出现了 B 表的主键，它就是外键。M:N 联系表中，双方主键的组合是主键，同时各自也是外键。</p>
<p><strong>范式判断（只看依赖关系）</strong> ：</p>
<ul>
<li>1NF：属性不可再分。</li>
<li>2NF：<strong>消除部分依赖</strong>（非主属性不能只依赖联合主键的一部分）。</li>
<li>3NF：<strong>消除传递依赖</strong>（非主属性不能经由另一个非主属性间接依赖主键）。题给 A→B、B→C 且 B 不是候选键，就是传递依赖。</li>
</ul>
<p><strong>SQL 题</strong>：多表查询必须写 <strong>JOIN 和连接条件</strong>，漏掉连接条件会变成笛卡尔积。<code>WHERE</code> 过滤行，<code>HAVING</code> 过滤分组后的组。</p>
<h3 id="三uml-建模--15-分必拿题">三、UML 建模 —— 15 分必拿题</h3>
<p>高频图种：<strong>用例图、类图、顺序图</strong>（时序图）。</p>
<p><strong>类图六种关系（由弱到强，必须背熟）</strong> ：<strong>依赖 → 关联 → 聚合 → 组合 → 泛化 → 实现</strong>。</p>
<ul>
<li><strong>依赖</strong>：一个类的变化“波及”另一个（方法参数/局部变量中使用）。</li>
<li><strong>关联</strong>：成员变量，“拥有”关系。</li>
<li><strong>聚合</strong>：空心菱形，“has-a”，整体消失部分还在（如部门与员工）。</li>
<li><strong>组合</strong>：实心菱形，“contains-a”，同生共死（如订单与订单项）。</li>
<li><strong>泛化</strong>：继承，空心三角实线。</li>
<li><strong>实现</strong>：类实现接口，虚线三角。</li>
</ul>
<p><strong>多重度</strong>：<code>1</code> 恰好一个；<code>0..*</code> 或 <code>*</code> 零个或多个；<code>1..*</code> 至少一个。注意“0”的含义（新系统刚上线可以没有订单）。</p>
<p><strong>用例关系</strong>：<strong>include 是“一定会做”</strong>（如借书一定验证借书证），<strong>extend 是“有条件才做”</strong>（如超期才触发罚款）。常见错误是把系统内部模块标成参与者，或把实现步骤拆成过细的用例。</p>
<p><strong>类图扣分点</strong>：业务分析层面的类图<strong>不应该出现 getter/setter 等纯技术方法</strong>，应列出与业务规则相关的核心操作。</p>
<h3 id="四c-语言算法设计--15-分稳分题">四、C 语言算法设计 —— 15 分稳分题</h3>
<p>这道题不需要从零写算法，而是在已搭好框架的代码里补全关键几行。<strong>题型结构固定</strong>：代码填空（6—9 分）+ 策略识别（约 3 分）+ 复杂度分析（约 3 分）+ 实例推演（约 3 分）。</p>
<p><strong>四种策略的“长相”必须条件反射</strong>：</p>
<ul>
<li><strong>分治</strong>：递归框架 + 合并步骤（归并排序、二分查找、最大子数组）。填空常考<strong>递归终止条件</strong>和<strong>合并操作</strong>。</li>
<li><strong>动态规划</strong>：有<strong>递推式/状态转移方程</strong>，通常填表自底向上。常考<strong>状态转移方程</strong>和<strong>填表顺序</strong>（0-1 背包、最长公共子序列）。</li>
<li><strong>贪心</strong>：每步选当前最优、不回溯（活动选择、哈夫曼、Dijkstra）。常考<strong>贪心选择条件</strong>。</li>
<li><strong>回溯</strong>：<strong>递归尝试 + 剪枝 + 恢复现场</strong>。填空三件套：<strong>递归出口、递归调用（参数推进）、回溯还原（把标记恢复为初始值）</strong> 。没有还原语句，回溯法就不成立。</li>
</ul>
<p><strong>“背了就有分”的部分</strong>：策略识别题从“分治/动态规划/贪心/回溯”中选；快排/归并的时间复杂度是 (O(n\log n))，暴力递归是 (O(2^n))，这些直接写。</p>
<p><strong>考场策略</strong>：目标 7—10 分，不要在一道题上超过 25 分钟。就算代码没完全读懂，根据题干描述和代码结构，策略题蒙一个也有概率对。</p>
<h3 id="五设计模式选答java--c-15-分送分题">五、设计模式选答（Java / C++）—— 15 分送分题</h3>
<p>第 5/6 题二选一，<strong>选自己最擅长的语言</strong>。题型是“业务场景 + UML 类图 + 挖 5—6 个空的代码”，填空内容基本是<strong>类名、方法名、继承/实现关键字、返回类型、new 具体类</strong>这些有规律的东西。</p>
<p><strong>高频模式（背 6 个足够）</strong> ：单例、简单工厂/工厂方法、策略、装饰器、观察者、适配器。</p>
<p><strong>固定做题顺序</strong>：① 先认模式（看类名和场景关键词）→ ② 给类分角色（谁是接口、谁是具体实现、谁是客户端）→ ③ 扫完整段代码再填 → ④ 判断每个空属于哪一类：<strong>关键字 / 类型 / 方法签名 / 返回值 / new 对象 / 方法调用</strong> → ⑤ 利用后文反推前文。</p>
<p><strong>各模式填空重点</strong>：</p>
<ul>
<li><strong>单例</strong>：私有静态实例、私有构造器、<code>getInstance()</code>。</li>
<li><strong>工厂方法</strong>：抽象工厂方法返回<strong>接口类型</strong>，具体工厂里 <code>return new 具体产品()</code>。</li>
<li><strong>策略/装饰器/观察者</strong>：重点在<strong>接口方法的实现</strong>和<strong>上下文/主题中持有抽象类型的成员变量</strong>。</li>
</ul>
<p><strong>避免 Java 细节丢分</strong>：方法签名要完整（返回类型 + 方法名 + 参数）；字段声明不能漏类型；非 static 方法调用写成 <code>obj.method()</code>；父类字段的 <code>private/protected</code> 看后文谁在访问来决定；题目已有的名字直接照抄，注意大小写。</p>
<p><strong>目标</strong>：这题模板背熟，拿 10—12 分是正常水平，冲满分完全可能。</p>
<h3 id="六下午题整体考场策略">六、下午题整体考场策略</h3>
<p><strong>时间分配建议</strong>：数据流图、数据库、UML 三题控制在 25—30 分钟内完成，确保 12 分以上；算法题留 20—25 分钟，目标 7—10 分；设计模式题 15—20 分钟，目标 10 分以上。先做有把握的题型，把“必拿分”锁死，再攻算法。</p>
<p><strong>最容易丢分的细节汇总</strong>：数据流用动词命名；M:N 忘记单独建表；聚合/组合菱形实心空心搞反；回溯填空漏掉“还原”；设计模式填空方法签名不完整或返回类型写错。</p>
<p>把以上规则练到条件反射，下午卷的底线分（45 分）会非常稳，往上冲 55—60 分也有清晰路径。</p>
]]></content:encoded></item><item><title>软件设计师考点背诵</title><link>https://blog.dart.xin/softexam/s104-key-point/</link><pubDate>Thu, 22 Oct 2026 12:33:13 +0800</pubDate><guid>https://blog.dart.xin/softexam/s104-key-point/</guid><description>&lt;p&gt;下面把前面三块内容合并去重、按“上午选择题必背 → CPU 组成专题 → 下午应用题必背 → 优先级与速记”四部分完整整理。通俗解释和记忆抓手保留，便于直接背诵。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="一上午选择题必背清单"&gt;一、上午选择题必背清单&lt;/h1&gt;
&lt;h2 id="一计算机组成与体系结构约12分"&gt;（一）计算机组成与体系结构（约12分）&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 流水线计算&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：流水线周期 = 各段中&lt;strong&gt;最慢的那段&lt;/strong&gt;时间。执行时间 = 单条指令完整时间 +（指令数 − 1）× 流水线周期。吞吐率 = 指令数 / 总时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通俗&lt;/strong&gt;：像工厂装配线，速度由最慢工位决定；第1条走完全程，之后每条只再等“最慢工位”那么久。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 浮点数&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：&lt;strong&gt;阶码决定范围，尾数决定精度&lt;/strong&gt;。阶码越长范围越大，尾数越长精度越高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Cache&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：Cache 是除寄存器外&lt;strong&gt;最快的存储层次&lt;/strong&gt;，缓解 CPU 与主存之间的&lt;strong&gt;速度&lt;/strong&gt;矛盾，依据是&lt;strong&gt;局部性原理&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 校验码&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：奇偶校验&lt;strong&gt;只检错不纠错&lt;/strong&gt;；CRC &lt;strong&gt;可检错不可纠错&lt;/strong&gt;；海明码&lt;strong&gt;可检错也可纠错&lt;/strong&gt;，校验位公式 2^r ≥ r + m + 1（m 数据位，r 校验位）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. 码制转换&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：正数原码、反码、补码相同；负数反码 = 原码数值位按位取反（符号位不变），补码 = 反码末位加1；移码 = 补码符号位取反。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通俗&lt;/strong&gt;：补码存在的唯一目的，是让减法能用加法电路完成，省掉一套减法硬件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常考&lt;/strong&gt;：问“补码的作用”，选“将减法转化为加法，简化运算器设计”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;6. 寻址方式（高频辨析）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：&lt;strong&gt;立即寻址最快&lt;/strong&gt;（操作数写在指令里，不访存）；&lt;strong&gt;间接寻址最慢&lt;/strong&gt;（多次访存）；寄存器寻址也快；直接寻址指令中放的是操作数地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通俗&lt;/strong&gt;：立即=答案写在题目里；直接=题目给房间号；间接=题目给信封，信封里才是房间号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;7. 总线分类&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：&lt;strong&gt;数据总线双向&lt;/strong&gt;，&lt;strong&gt;地址总线单向&lt;/strong&gt;（CPU发出地址），&lt;strong&gt;控制总线双向&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;8. 系统可靠性计算&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;背熟&lt;/strong&gt;：&lt;strong&gt;串联可靠度 = 各部件相乘&lt;/strong&gt;；&lt;strong&gt;并联可靠度 = 1 − 各部件失效率相乘&lt;/strong&gt;（即 1 − (1−R)ⁿ）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通俗&lt;/strong&gt;：串联像铁链，一环断全断，所以乘；并联像多条备份路线，全断才断，所以算“全坏”再1减。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易错&lt;/strong&gt;：并联不是直接相乘可靠度，是&lt;strong&gt;先求全失效概率再1减&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;9. I/O 数据传输方式&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>下面把前面三块内容合并去重、按“上午选择题必背 → CPU 组成专题 → 下午应用题必背 → 优先级与速记”四部分完整整理。通俗解释和记忆抓手保留，便于直接背诵。</p>
<hr>
<h1 id="一上午选择题必背清单">一、上午选择题必背清单</h1>
<h2 id="一计算机组成与体系结构约12分">（一）计算机组成与体系结构（约12分）</h2>
<p><strong>1. 流水线计算</strong></p>
<ul>
<li><strong>背熟</strong>：流水线周期 = 各段中<strong>最慢的那段</strong>时间。执行时间 = 单条指令完整时间 +（指令数 − 1）× 流水线周期。吞吐率 = 指令数 / 总时间。</li>
<li><strong>通俗</strong>：像工厂装配线，速度由最慢工位决定；第1条走完全程，之后每条只再等“最慢工位”那么久。</li>
</ul>
<p><strong>2. 浮点数</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>阶码决定范围，尾数决定精度</strong>。阶码越长范围越大，尾数越长精度越高。</li>
</ul>
<p><strong>3. Cache</strong></p>
<ul>
<li><strong>背熟</strong>：Cache 是除寄存器外<strong>最快的存储层次</strong>，缓解 CPU 与主存之间的<strong>速度</strong>矛盾，依据是<strong>局部性原理</strong>。</li>
</ul>
<p><strong>4. 校验码</strong></p>
<ul>
<li><strong>背熟</strong>：奇偶校验<strong>只检错不纠错</strong>；CRC <strong>可检错不可纠错</strong>；海明码<strong>可检错也可纠错</strong>，校验位公式 2^r ≥ r + m + 1（m 数据位，r 校验位）。</li>
</ul>
<p><strong>5. 码制转换</strong></p>
<ul>
<li><strong>背熟</strong>：正数原码、反码、补码相同；负数反码 = 原码数值位按位取反（符号位不变），补码 = 反码末位加1；移码 = 补码符号位取反。</li>
<li><strong>通俗</strong>：补码存在的唯一目的，是让减法能用加法电路完成，省掉一套减法硬件。</li>
<li><strong>常考</strong>：问“补码的作用”，选“将减法转化为加法，简化运算器设计”。</li>
</ul>
<p><strong>6. 寻址方式（高频辨析）</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>立即寻址最快</strong>（操作数写在指令里，不访存）；<strong>间接寻址最慢</strong>（多次访存）；寄存器寻址也快；直接寻址指令中放的是操作数地址。</li>
<li><strong>通俗</strong>：立即=答案写在题目里；直接=题目给房间号；间接=题目给信封，信封里才是房间号。</li>
</ul>
<p><strong>7. 总线分类</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>数据总线双向</strong>，<strong>地址总线单向</strong>（CPU发出地址），<strong>控制总线双向</strong>。</li>
</ul>
<p><strong>8. 系统可靠性计算</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>串联可靠度 = 各部件相乘</strong>；<strong>并联可靠度 = 1 − 各部件失效率相乘</strong>（即 1 − (1−R)ⁿ）。</li>
<li><strong>通俗</strong>：串联像铁链，一环断全断，所以乘；并联像多条备份路线，全断才断，所以算“全坏”再1减。</li>
<li><strong>易错</strong>：并联不是直接相乘可靠度，是<strong>先求全失效概率再1减</strong>。</li>
</ul>
<p><strong>9. I/O 数据传输方式</strong></p>
<ul>
<li><strong>背熟</strong>：效率 <strong>DMA &gt; 中断 &gt; 程序查询</strong>。程序查询 CPU 一直等，利用率最低；中断 CPU 不用等但每次传输要介入；DMA 硬件直接搬数据，CPU 几乎不参与。</li>
</ul>
<h2 id="二操作系统约10分">（二）操作系统（约10分）</h2>
<p><strong>1. PV操作与前趋图</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>互斥信号量初值1，同步信号量初值通常0</strong>。前趋图中，箭线<strong>起点是 V</strong>（前驱完成通知），<strong>终点是 P</strong>（后继开始前等待）。</li>
<li><strong>通俗</strong>：P 是申请/等待，V 是释放/通知；谁先完成谁 V，谁要开始谁先 P 等着。</li>
</ul>
<p><strong>2. 死锁判断</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>不会死锁</strong>的条件 m * (w-1) + 1 &lt;= n（m 进程数，w 每进程最大需求，n 资源总数）。</li>
<li><strong>通俗</strong>：最坏情况每个进程都拿到“差一个就满足”，此时再多1个资源，必能先让一个进程跑完，打破死锁。</li>
</ul>
<p><strong>3. 页式存储</strong></p>
<ul>
<li><strong>背熟</strong>：逻辑地址 = <strong>页号 + 页内偏移</strong>。淘汰页面时，<strong>被修改过的脏页要写回磁盘</strong>，没修改的直接丢弃。</li>
</ul>
<p><strong>4. 线程与进程资源共享（高频易混）</strong></p>
<ul>
<li><strong>背熟</strong>：同进程内多线程<strong>共享</strong>代码段、全局变量、打开的文件；但<strong>每个线程有独立栈和寄存器</strong>，栈不共享。</li>
<li><strong>通俗</strong>：进程像办公室，代码和文件是公共资源；但每个人的**工位（栈）**是自己的。</li>
</ul>
<p><strong>5. 文件物理结构</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>连续结构</strong>存取快但产生碎片；<strong>链接结构</strong>无碎片但只能顺序访问；<strong>索引结构</strong>支持随机访问，索引表占空间。</li>
<li><strong>常考</strong>：“支持随机存取且不产生外部碎片”选<strong>索引结构</strong>。</li>
</ul>
<h2 id="三数据结构与算法约10分性价比最高">（三）数据结构与算法（约10分，性价比最高）</h2>
<p><strong>1. 排序复杂度与稳定性（几乎年年沾边）</strong></p>
<ul>
<li><strong>背熟</strong>：
<ul>
<li>平均 (O(n\log n))：<strong>快排、归并、堆排</strong>（口诀“<strong>快归堆</strong>”）。</li>
<li>平均 (O(n^2))：<strong>冒泡、插入、选择</strong>（口诀“<strong>冒插选</strong>”）。</li>
<li><strong>不稳定</strong>：<strong>快排、希尔、选择、堆排</strong>（口诀“<strong>快希选堆</strong>”）。</li>
<li><strong>稳定</strong>：冒泡、插入、归并、基数。</li>
</ul>
</li>
<li><strong>通俗</strong>：稳定性指“值相等的元素排序后相对顺序不变”，需要保留原始先后顺序的场景必须用稳定排序。</li>
</ul>
<p><strong>2. 二叉树遍历</strong></p>
<ul>
<li><strong>背熟</strong>：前序“<strong>根左右</strong>”，中序“<strong>左根右</strong>”，后序“<strong>左右根</strong>”。已知<strong>中序 + 前序</strong>（或中序 + 后序）可唯一还原二叉树。</li>
</ul>
<p><strong>3. 哈夫曼树</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>权值小的优先合并</strong>，带权路径长度最小；频率高的字符编码短。</li>
</ul>
<p><strong>4. 图的存储与遍历</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>邻接矩阵适合稠密图</strong>，<strong>邻接表适合稀疏图</strong>。DFS 用栈（递归），BFS 用队列。</li>
<li><strong>通俗</strong>：邻接矩阵像“谁和谁相连”的大表格，边多填得满才划算；边少大部分是空的，就用邻接表只记朋友。</li>
</ul>
<p><strong>5. B树与B+树</strong></p>
<ul>
<li><strong>背熟</strong>：B树节点既存关键字也存数据；<strong>B+树只有叶子节点存数据</strong>，非叶子只存索引，叶子间用指针连成链表，<strong>范围查询更快</strong>。数据库索引多用 B+ 树。</li>
</ul>
<p><strong>6. 关键路径</strong></p>
<ul>
<li><strong>背熟</strong>：关键路径是 AOE 网中<strong>最长路径</strong>，关键活动时间裕量为 <strong>0</strong>，任何关键活动延误都拖长总工期。</li>
</ul>
<h2 id="四程序设计语言基础">（四）程序设计语言基础</h2>
<p><strong>1. 编译各阶段</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>词法分析 → 语法分析 → 语义分析 → 中间代码生成 → 优化 → 目标代码生成</strong>。词法管“单词拼写”，语法管“句子结构”，语义管“意思通不通”。</li>
<li><strong>常考</strong>：“括号不匹配”在<strong>语法分析</strong>阶段发现。</li>
</ul>
<p><strong>2. 有限自动机与正规式</strong></p>
<ul>
<li><strong>背熟</strong>：DFA 每个状态对同一输入只有<strong>唯一后继</strong>；NFA 可有多个后继。正规式与有限自动机<strong>等价</strong>，都描述词法规则。</li>
</ul>
<p><strong>3. 编译 vs 解释</strong></p>
<ul>
<li><strong>背熟</strong>：编译效率高但可移植性差；解释可移植性好但效率低。</li>
</ul>
<h2 id="五软件工程">（五）软件工程</h2>
<p><strong>1. 开发模型</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>瀑布</strong>阶段严格顺序，适合需求明确稳定；<strong>敏捷</strong>迭代、快速响应变化（Scrum 每日站会、XP 结对编程）；<strong>螺旋</strong>强调风险分析，适合大型复杂项目。</li>
</ul>
<p><strong>2. 内聚与耦合</strong></p>
<ul>
<li><strong>背熟</strong>：内聚从高到低：功能 &gt; 顺序 &gt; 通信 &gt; 过程 &gt; 时间 &gt; 逻辑 &gt; 偶然。耦合从低到高：非直接 &gt; 数据 &gt; 标记 &gt; 控制 &gt; 外部 &gt; 公共 &gt; 内容。<strong>设计目标：高内聚低耦合</strong>。</li>
</ul>
<p><strong>3. McCabe 环路复杂度</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>边 − 节点 + 2</strong>，或 <strong>判定节点数 + 1</strong>。两种结果一样，考场用“判定节点数 + 1”更快。</li>
<li><strong>通俗</strong>：数的是代码里有多少个岔路口，岔路越多测试要覆盖的路径越多。</li>
</ul>
<p><strong>4. 测试与维护</strong></p>
<ul>
<li><strong>黑盒</strong>：等价类划分、边界值分析（不关心内部逻辑）。</li>
<li><strong>白盒</strong>：逻辑覆盖（语句、判定、条件、路径覆盖）。</li>
<li><strong>维护类型</strong>：纠错性（修bug）、适应性（环境变化）、完善性（加功能）、预防性（提高可维护性）。</li>
</ul>
<p><strong>5. 可移植性设计</strong></p>
<ul>
<li><strong>背熟</strong>：核心是<strong>屏蔽平台差异</strong>——设备相关代码与无关代码分离、用跨平台语言、不用平台专属类库。<strong>“使用特定环境专用功能”是破坏可移植性的做法</strong>，选项里出现不要选。</li>
</ul>
<h2 id="六面向对象">（六）面向对象</h2>
<p><strong>1. UML 核心图</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>用例图</strong>看“系统能做什么”；<strong>类图</strong>看静态结构；<strong>时序图</strong>看按时间顺序的消息交互；<strong>活动图</strong>看流程；<strong>状态图</strong>看对象状态变迁。</li>
</ul>
<p><strong>2. 类图关系强弱</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>依赖 &lt; 关联 &lt; 聚合 &lt; 组合 &lt; 泛化（继承）&lt; 实现</strong>。聚合空心菱形（整体没了部分还能活），组合实心菱形（整体没了部分跟着销毁）。</li>
</ul>
<p><strong>3. 设计模式三大分类</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>创建型</strong>管“怎么造对象”（单例、工厂、建造者、原型）；<strong>结构型</strong>管“怎么拼对象”（适配器、桥接、组合、装饰、外观、享元、代理）；<strong>行为型</strong>管“对象之间怎么通信分责”（观察者、策略、命令、模板方法、责任链、状态、迭代器等）。</li>
<li><strong>场景对应</strong>：只需一个实例→<strong>单例</strong>；接口不兼容要转换→<strong>适配器</strong>；一对多通知→<strong>观察者</strong>；多种算法可互换→<strong>策略</strong>；两个独立变化维度→<strong>桥接</strong>。</li>
</ul>
<h2 id="七数据库系统">（七）数据库系统</h2>
<p><strong>1. E-R 转关系模式（送分核心）</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>1:1</strong> 任一方主键放进另一方作外键；<strong>1:N</strong> 把 <strong>1 端</strong>主键放进 <strong>N 端</strong>作外键；<strong>M:N 必须单独建表</strong>，主键 = 双方主键的<strong>组合</strong>，联系自身属性也放这张表。</li>
<li><strong>通俗</strong>：多对多像“学生选课”，选课关系（成绩）塞不进学生表或课程表，只能单独建“选课表”。</li>
</ul>
<p><strong>2. 规范化</strong></p>
<ul>
<li><strong>背熟</strong>：1NF 属性不可再分；2NF 消除<strong>部分依赖</strong>（联合主键中某字段不能只依赖主键一部分）；3NF 消除<strong>传递依赖</strong>（非主属性不能依赖另一个非主属性）。</li>
</ul>
<p><strong>3. 三级模式与两级映像</strong></p>
<ul>
<li><strong>背熟</strong>：三级模式 = <strong>外模式（用户视图）、模式（逻辑结构）、内模式（物理存储）</strong>；两级映像 = <strong>外模式/模式</strong>（逻辑独立性）、<strong>模式/内模式</strong>（物理独立性）。</li>
<li><strong>通俗</strong>：外模式是“你看到的报表”，模式是“数据库表结构设计”，内模式是“数据在硬盘怎么存”。改硬盘不影响表结构（物理独立性），改表结构不影响报表（逻辑独立性）。</li>
</ul>
<p><strong>4. 事务与封锁</strong></p>
<ul>
<li><strong>背熟</strong>：ACID = 原子性、一致性、隔离性、持久性。一级封锁防丢失修改，二级防脏读，三级防不可重复读。</li>
</ul>
<p><strong>5. SQL 要点</strong></p>
<ul>
<li><strong>背熟</strong>：<code>WHERE</code> 过滤<strong>行</strong>，<code>HAVING</code> 过滤<strong>分组后的组</strong>。多对多查询必须通过<strong>中间关联表 + JOIN</strong>。</li>
</ul>
<h2 id="八计算机网络与信息安全">（八）计算机网络与信息安全</h2>
<p><strong>1. 子网划分（必考计算）</strong></p>
<ul>
<li><strong>背熟核心</strong>：可用主机数 = (2^h - 2)（h 主机位）；子网掩码前缀 = (32 - h)；子网数 = (2^n)（n 借位数）。</li>
<li><strong>必背对照表</strong>：</li>
</ul>
<table>
	<thead>
			<tr>
					<th>前缀</th>
					<th>子网掩码</th>
					<th>主机位 h</th>
					<th>地址总数</th>
					<th>可用主机数</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>/24</td>
					<td>255.255.255.0</td>
					<td>8</td>
					<td>256</td>
					<td>254</td>
			</tr>
			<tr>
					<td>/25</td>
					<td>255.255.255.128</td>
					<td>7</td>
					<td>128</td>
					<td>126</td>
			</tr>
			<tr>
					<td>/26</td>
					<td>255.255.255.192</td>
					<td>6</td>
					<td>64</td>
					<td>62</td>
			</tr>
			<tr>
					<td>/27</td>
					<td>255.255.255.224</td>
					<td>5</td>
					<td>32</td>
					<td>30</td>
			</tr>
			<tr>
					<td>/28</td>
					<td>255.255.255.240</td>
					<td>4</td>
					<td>16</td>
					<td>14</td>
			</tr>
			<tr>
					<td>/29</td>
					<td>255.255.255.248</td>
					<td>3</td>
					<td>8</td>
					<td>6</td>
			</tr>
			<tr>
					<td>/30</td>
					<td>255.255.255.252</td>
					<td>2</td>
					<td>4</td>
					<td>2</td>
			</tr>
	</tbody>
</table>
<p>看到“30台”→<strong>/27，255.255.255.224</strong>；看到“62台”→/26。</p>
<p><strong>2. 网络设备工作层次</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>集线器（Hub）</strong> 物理层，<strong>交换机</strong>数据链路层，<strong>路由器</strong>网络层，<strong>网关</strong>应用层及以上。</li>
</ul>
<p><strong>3. 协议端口</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>80-HTTP，443-HTTPS，21-FTP，53-DNS，25-SMTP，110-POP3</strong>。TCP 可靠、面向字节流、有连接；UDP 快、无连接、面向报文（适合视频/语音/DNS）。</li>
</ul>
<p><strong>4. 加密与签名</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>公钥加密，私钥签名</strong>。加密用对方公钥，签名用自己的私钥。数字签名保证<strong>不可否认性</strong>；数字证书由 CA 签发，绑定公钥与身份。</li>
</ul>
<p><strong>5. 常见攻击</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>DDoS</strong> 分布式拒绝服务（流量压垮）；<strong>ARP 欺骗</strong>伪造 MAC-IP 对应；<strong>SQL 注入</strong>拼恶意 SQL；<strong>XSS</strong> 塞恶意脚本。防护对应：流量清洗、ARP 绑定、参数化查询、输入转义。</li>
</ul>
<h2 id="九知识产权与标准化纯记忆">（九）知识产权与标准化（纯记忆）</h2>
<p><strong>1. 保护期限</strong></p>
<ul>
<li><strong>背熟</strong>：公民软件著作权 <strong>作者终生 + 死后50年</strong>；单位软件著作权 <strong>首次发表后50年</strong>；注册商标 <strong>10年</strong>（期满前6个月续注）；发明专利 <strong>20年</strong>；实用新型和外观设计 <strong>10年</strong>。</li>
<li><strong>特殊</strong>：<strong>署名权、修改权、保护作品完整权</strong>保护期<strong>不受时间限制</strong>。</li>
</ul>
<p><strong>2. 标准代号</strong></p>
<ul>
<li><strong>背熟</strong>：<strong>GB</strong> 强制性国标，<strong>GB/T</strong> 推荐性国标，<strong>ISO</strong> 国际标准，<strong>IEEE</strong> 行业标准。</li>
</ul>
<hr>
<h1 id="二cpu-组成结构专题你特别问的">二、CPU 组成结构专题（你特别问的）</h1>
<p><strong>总原则</strong>：不需要逐字背教材每个寄存器，但<strong>核心部件 + 归属判断必须背熟</strong>，因为常考“下列哪个属于运算器/控制器”。</p>
<h2 id="1-必须背熟的核心结论">1. 必须背熟的核心结论</h2>
<p>CPU 两大核心部件：<strong>运算器 + 控制器</strong>（外加寄存器组、内部总线）。</p>
<p><strong>运算器：背“算、存、状”</strong></p>
<ul>
<li><strong>ALU 算术逻辑单元</strong>：做算术和逻辑运算，运算器核心。</li>
<li><strong>AC 累加寄存器</strong>：暂存运算结果或操作数。</li>
<li><strong>DR 数据缓冲寄存器</strong>：暂存从内存读出或要写入内存的数据。</li>
<li><strong>PSW 状态条件寄存器</strong>（标志寄存器）：存进位、溢出、零标志等状态。</li>
</ul>
<p>记忆：<strong>运算器 = ALU + AC + DR + PSW</strong>。</p>
<p><strong>控制器：背“取、译、时、发”</strong></p>
<ul>
<li><strong>PC 程序计数器</strong>：存<strong>下一条</strong>要执行指令的地址。</li>
<li><strong>IR 指令寄存器</strong>：存<strong>当前正在执行</strong>的指令。</li>
<li><strong>指令译码器</strong>：把操作码翻译成控制信号。</li>
<li><strong>时序部件/微操作信号发生器</strong>：产生各阶段时序控制信号。</li>
</ul>
<p>记忆：<strong>控制器 = PC + IR + 译码器 + 时序部件</strong>。</p>
<h2 id="2-必须背熟的归属判断高频易错">2. 必须背熟的归属判断（高频易错）</h2>
<ul>
<li><strong>PC 属于控制器</strong>（不是运算器）。</li>
<li><strong>IR 属于控制器</strong>（不是运算器）。</li>
<li><strong>ALU 属于运算器</strong>。</li>
<li><strong>PSW 属于运算器</strong>。</li>
<li><strong>指令译码器属于控制器</strong>。</li>
</ul>
<p>只要能把上面几个正确归类，这类选择题就不会丢分。</p>
<h2 id="3-理解即可不必死背">3. 理解即可、不必死背</h2>
<ul>
<li><strong>通用寄存器组</strong>：CPU 内部暂存操作数，软考有时单列，不一定强行归入运算器。</li>
<li><strong>暂存寄存器、内部总线</strong>：知道是 CPU 内部数据通路一部分即可。</li>
<li><strong>控制器产生控制信号的具体流程</strong>：理解“取指—译码—执行”顺序即可，不必背微操作序列。</li>
</ul>
<h2 id="4-一句话背诵版">4. 一句话背诵版</h2>
<blockquote>
<p><strong>运算器四件套：ALU、AC、DR、PSW；控制器四件套：PC、IR、译码器、时序部件。PC 存下一条，IR 存当前条；PC、IR 归控制器，ALU、PSW 归运算器。</strong></p>
</blockquote>
<hr>
<h1 id="三下午应用题必背题型固定背模板拿底线分">三、下午应用题必背（题型固定，背模板拿底线分）</h1>
<p>下午前4题必答，题型极其固定。目标不是现场设计，而是“看到题就知道第一步找什么”。</p>
<h2 id="一数据流图第1题15分">（一）数据流图（第1题，15分）</h2>
<p><strong>背熟三件事</strong>：</p>
<ol>
<li><strong>外部实体</strong>：题干里的“人/部门/外部系统”（客户、管理员、银行、财务部门等），系统边界之外。</li>
<li><strong>数据存储</strong>：名字带“<strong>文件、表、清单、档案、库</strong>”的基本是数据存储。</li>
<li><strong>缺失数据流</strong>：先找“只有入没有出”或“只有出没有入”的加工；再逐句读题干，看哪个加工“提到要读某存储但图上没入线”。数据流<strong>起点或终点必须有一个是加工</strong>。</li>
</ol>
<p><strong>三条铁律</strong>：数据存储之间不能直连；外部实体之间不能直连；加工不能只有入或只有出。都必须经过加工中转。</p>
<p><strong>命名规范</strong>：数据流写<strong>名词</strong>（如“付款通知”），不能写成“计算成本”这种加工名；加工名写<strong>动词+名词</strong>。</p>
<p><strong>数据平衡三原则</strong>：</p>
<ul>
<li><strong>父图与子图平衡</strong>：子图边界流 = 父图该加工的流，一条不多一条不少。</li>
<li><strong>加工自身平衡</strong>：每个加工有进有出，不能是黑洞或奇迹。</li>
<li><strong>顶层图与0层图平衡</strong>：系统对外交互数量名称一致。</li>
</ul>
<p><strong>父图子图通俗</strong>：父图是粗图，子图是把父图中<strong>某一个加工</strong>展开后的细图；平衡要求这个框的对外接口展开前后不能变。</p>
<h2 id="二数据库设计第2题15分">（二）数据库设计（第2题，15分）</h2>
<p><strong>E-R 转关系模式规则（送分核心）</strong>：</p>
<ul>
<li><strong>1:1</strong>：任一方主键放进另一方作外键（或合并）。</li>
<li><strong>1:N</strong>：<strong>N 端</strong>加 <strong>1 端</strong>主键作外键。</li>
<li><strong>M:N</strong>：<strong>必须单独建表</strong>，主键 = 双方主键<strong>组合</strong>，联系自身属性也放这张表。</li>
</ul>
<p><strong>主键判定</strong>：题干出现“<strong>唯一标识</strong>”“<strong>编号</strong>”“<strong>身份证号</strong>”的基本是主键候选。</p>
<p><strong>外键判定</strong>：A 表出现 B 表主键，它就是外键；M:N 单独建的表，双方主键组合为主键，同时各自也是外键。</p>
<p><strong>范式一句话</strong>：1NF 列不可分；2NF 消除部分依赖；3NF 消除传递依赖。</p>
<h2 id="三uml-建模第3题15分">（三）UML 建模（第3题，15分）</h2>
<p><strong>类图关系</strong>：泛化（空心三角实线，继承）、聚合（空心菱形，整体没了部分还能活）、组合（实心菱形，部分随整体销毁）、依赖（虚线箭头，一个类变化波及另一个）。</p>
<p><strong>多重度</strong>：1 恰好一个；0..* 或 * 零个或多个；1..* 至少一个。注意“0”的含义（新系统刚上线没有订单）。</p>
<p><strong>用例关系</strong>：<strong>include 是“一定会做”</strong>，<strong>extend 是“有条件才做”</strong>。</p>
<h2 id="四c-语言算法设计第4题15分">（四）C 语言算法设计（第4题，15分）</h2>
<p><strong>四种策略的“长相”</strong>：</p>
<ul>
<li><strong>分治</strong>：大问题拆小问题，递归求解再合并（归并排序、二分查找）。</li>
<li><strong>动态规划</strong>：有<strong>递推式/状态转移方程</strong>，通常用表格自底向上算。</li>
<li><strong>贪心</strong>：每步选当前最优，不回头（活动安排、部分背包）。</li>
<li><strong>回溯</strong>：<strong>递归尝试 + 不满足就撤销</strong>（N皇后、排列组合）。</li>
</ul>
<p><strong>填空三件套</strong>：</p>
<ul>
<li><strong>递归出口</strong>：函数开头，看终止条件（如 <code>row &gt; n</code>），不要填成推进条件。</li>
<li><strong>递归调用</strong>：参数推进通常是 <code>row+1</code>、<code>k+1</code>、<code>i+1</code>，看“处理下一个”用哪个变量控制。</li>
<li><strong>回溯还原</strong>：找前面“标记占用”的语句，把它<strong>恢复初始值</strong>（如 <code>queen[row]=0</code>、<code>visited[i]=0</code>）。没有还原语句，回溯法就不成立。</li>
</ul>
<p><strong>经典结论</strong>：N皇后 n=4 时解的个数 = <strong>2</strong>。</p>
<p><strong>策略</strong>：目标6—8分，不要在一道算法题耗超过25分钟。</p>
<h2 id="五设计模式选答第56题15分选一">（五）设计模式选答（第5/6题，15分选一）</h2>
<p><strong>背熟“场景→模式”对应</strong>：</p>
<ul>
<li><strong>单例</strong>：系统只需要一个实例（连接池、配置管理器、中央控制器）。</li>
<li><strong>工厂</strong>：创建对象逻辑集中管理，调用方只说“要什么”。</li>
<li><strong>观察者</strong>：一对多通知，一个对象变了所有依赖它的自动更新（事件监听、温度触发空调）。</li>
<li><strong>策略</strong>：多种可互换算法，运行时选一种（不同促销规则）。</li>
<li><strong>适配器</strong>：接口不兼容要转换。</li>
<li><strong>桥接</strong>：两个独立变化的维度，用组合而非继承。</li>
</ul>
<p><strong>代码填空三处</strong>：<strong>接口/抽象类的实现关键字</strong>（Java 是 <code>implements</code>，C++ 是 <code>: public</code>）、<strong>工厂方法的返回类型</strong>（填接口名）、<strong>具体对象的创建</strong>（填 <code>new 具体类()</code>）。</p>
<p><strong>策略</strong>：选自己最擅长的语言，考前集中练真题填空；能拿一半分就足以通过。</p>
<hr>
<h1 id="四背诵优先级与速记口诀">四、背诵优先级与速记口诀</h1>
<h2 id="时间紧张先背这些">时间紧张先背这些</h2>
<p><strong>上午题最容易被忽略但几乎年年出现</strong>：码制补码作用、寻址方式快慢排序、总线单向/双向、串联并联可靠度、DMA&gt;中断&gt;查询、线程不共享栈、McCabe 边−节点+2、可移植性“不用专用功能”、三级模式两级映像、设计模式三大分类、Hub/交换机/路由器层次、CPU 运算器/控制器归属。</p>
<p><strong>下午题最容易丢分的细节</strong>：数据流命名不能用动词、缺失流必须一端是加工、M:N 单独建表且主键为组合、聚合 vs 组合看菱形实心空心、回溯填空必有“还原”、设计模式填空看 <code>implements</code> 和 <code>new</code>。</p>
<h2 id="一句话总口诀">一句话总口诀</h2>
<blockquote>
<p><strong>上午背</strong>：流水线周期 + PV方向 + 排序口诀 + 子网对照表 + 保护期限 + CPU四件套归属。<br>
<strong>下午背</strong>：DFD找实体/存储 + E-R的M:N单独建表 + 类图关系/多重度 + 四种算法长相 + 六种模式场景。<br>
<strong>CPU专题</strong>：运算器 ALU/AC/DR/PSW，控制器 PC/IR/译码器/时序部件；PC、IR 归控制器，ALU、PSW 归运算器。</p>
</blockquote>
<p>把以上条目背到条件反射，上午选择题的“概念辨析+小计算”和下午前四题的“模板采分”就基本没有大块盲区。专业英语（5分）不建议专门背单词，靠做真题积累计算机术语即可；知识产权分值低，背列出的保护期限和标准代号就够，把时间优先给软件工程、面向对象、数据结构与算法这三块约30分的重心。</p>
<p>关于“已知每个子网主机数，求子网掩码”这一类题，需要记住的核心是<strong>主机位公式 + 掩码换算 + 常见掩码表</strong>。下面按类别列出关键知识点和常考形式。</p>
<hr>
<h3 id="一必须背熟的核心公式">一、必须背熟的核心公式</h3>
<p><strong>1. 可用主机数公式</strong></p>
<p>[
可用主机数 = 2^h - 2
]</p>
<ul>
<li>(h)：主机位数</li>
<li>减 2：去掉<strong>网络地址</strong>和<strong>广播地址</strong></li>
<li>题目说“每个子网主机数是30台”，就是求满足 (2^h-2 \ge 30) 的<strong>最小 (h)</strong></li>
</ul>
<p><strong>2. 子网掩码前缀</strong></p>
<p>[
子网掩码前缀 = 32 - h
]</p>
<p>因为 IPv4 共 32 位，主机位占 (h) 位，剩下全是网络位/子网位。</p>
<p><strong>3. 子网数公式</strong></p>
<p>[
子网数 = 2^s
]</p>
<ul>
<li>(s)：从主机位借来用作子网位的位数</li>
<li>常考：“划分成若干子网”求子网数或掩码</li>
</ul>
<p><strong>4. 每个子网地址总数</strong></p>
<p>[
地址总数 = 2^h
]</p>
<p>可用主机数 = 地址总数 − 2。</p>
<hr>
<h3 id="二必须背熟的常见掩码表">二、必须背熟的常见掩码表</h3>
<table>
	<thead>
			<tr>
					<th>前缀</th>
					<th>子网掩码</th>
					<th>主机位 h</th>
					<th>地址总数</th>
					<th>可用主机数</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>/24</td>
					<td>255.255.255.0</td>
					<td>8</td>
					<td>256</td>
					<td>254</td>
			</tr>
			<tr>
					<td>/25</td>
					<td>255.255.255.128</td>
					<td>7</td>
					<td>128</td>
					<td>126</td>
			</tr>
			<tr>
					<td>/26</td>
					<td>255.255.255.192</td>
					<td>6</td>
					<td>64</td>
					<td>62</td>
			</tr>
			<tr>
					<td>/27</td>
					<td>255.255.255.224</td>
					<td>5</td>
					<td>32</td>
					<td>30</td>
			</tr>
			<tr>
					<td>/28</td>
					<td>255.255.255.240</td>
					<td>4</td>
					<td>16</td>
					<td>14</td>
			</tr>
			<tr>
					<td>/29</td>
					<td>255.255.255.248</td>
					<td>3</td>
					<td>8</td>
					<td>6</td>
			</tr>
			<tr>
					<td>/30</td>
					<td>255.255.255.252</td>
					<td>2</td>
					<td>4</td>
					<td>2</td>
			</tr>
	</tbody>
</table>
<p><strong>记忆重点</strong>：</p>
<ul>
<li>看到“30台主机” → 直接想到 <strong>/27，255.255.255.224</strong></li>
<li>看到“62台” → /26</li>
<li>看到“14台” → /28</li>
<li>看到“6台” → /29</li>
<li>看到“2台” → /30（常用于点对点链路）</li>
</ul>
<hr>
<h3 id="三c类地址的默认知识">三、C类地址的默认知识</h3>
<ul>
<li>C类地址默认网络位：<strong>24位</strong></li>
<li>默认子网掩码：<strong>255.255.255.0（/24）</strong></li>
<li>默认主机位：<strong>8位</strong></li>
<li>划分子网时，只能从最后 8 位主机位中“借位”作子网位</li>
</ul>
<p>所以 C 类题中，掩码变化只在<strong>最后一个字节</strong>。</p>
<hr>
<h3 id="四常考题型与对应解法">四、常考题型与对应解法</h3>
<p><strong>题型1：已知每个子网主机数，求子网掩码</strong></p>
<ul>
<li>解法：用 (2^h-2 \ge 主机数) 求最小 (h)，再算前缀 (32-h)，转点分十进制。</li>
<li>例：30台 → h=5 → /27 → 255.255.255.224</li>
</ul>
<p><strong>题型2：已知子网掩码，求每个子网可用主机数</strong></p>
<ul>
<li>解法：先看前缀，求 (h=32-前缀)，再算 (2^h-2)。</li>
<li>例：/28 → h=4 → 14台</li>
</ul>
<p><strong>题型3：已知需要划分的子网数，求子网掩码</strong></p>
<ul>
<li>解法：用 (2^s \ge 子网数) 求最小 (s)，新前缀 = 默认前缀 + s。</li>
<li>注意：这题问的是“子网数”，不是“每子网主机数”，不要混。</li>
</ul>
<p><strong>题型4：求网络地址、广播地址、可用IP范围</strong></p>
<ul>
<li>解法：用“块大小 = 256 − 掩码最后字节”定位。</li>
<li>例：/27 最后字节224，块大小 = 256−224 = 32。</li>
<li>网络地址是块大小的整数倍起点；广播地址是下一个块起点减1。</li>
</ul>
<p><strong>题型5：判断两个IP是否在同一子网</strong></p>
<ul>
<li>解法：分别与子网掩码做<strong>按位与</strong>，结果相同则同子网。</li>
</ul>
<hr>
<h3 id="五易错点软考常设陷阱">五、易错点（软考常设陷阱）</h3>
<ol>
<li><strong>忘记减2</strong>：可用主机数不是 (2^h)，而是 (2^h-2)。</li>
<li><strong>把“主机数”当成“地址总数”</strong>：30台主机对应32个地址，不是30个地址。</li>
<li><strong>借位方向搞反</strong>：子网位是从主机位借，前缀变大，主机位变小。</li>
<li><strong>C类默认/24忘记</strong>：C类不是/16，A类/8、B类/16、C类/24。</li>
<li><strong>/30不是不能用</strong>：/30只有2个可用主机，点对点链路常用；/31是特殊点对点，软考一般按传统减2理解。</li>
<li><strong>“子网数”和“每子网主机数”混淆</strong>：一个用 (2^s)，一个用 (2^h-2)。</li>
</ol>
<hr>
<h3 id="六速记口诀">六、速记口诀</h3>
<blockquote>
<p><strong>主机数先加2，找最小2的幂；<br>
主机位定h，前缀就是32减h；<br>
C类默认24，最后字节来换算；<br>
30台记/27，掩码末位224。</strong></p>
</blockquote>
<p>你给的题“C类地址每个子网30台主机”，直接背成：<strong>30台 → h=5 → /27 → 255.255.255.224</strong>。这是软考网络计算题里最高频的结论之一。</p>
]]></content:encoded></item><item><title>CSP（Communicating Sequential Processes）详解</title><link>https://blog.dart.xin/golang/go109-csp/</link><pubDate>Thu, 22 Oct 2026 12:07:40 +0800</pubDate><guid>https://blog.dart.xin/golang/go109-csp/</guid><description>&lt;h1 id="csp通信顺序进程详解"&gt;CSP（通信顺序进程）详解&lt;/h1&gt;
&lt;p&gt;CSP（通信顺序进程）是由图灵奖得主托尼·霍尔提出的并发系统代数理论‌，核心思想是通过独立进程间的显式消息通信替代共享内存，从根源上降低并发编程的复杂度。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📜 理论起源与发展&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1978年，英国计算机科学家 ‌托尼·霍尔‌ 首次正式阐述CSP理论，1985年出版同名专著《Communicating Sequential Processes》。&lt;/li&gt;
&lt;li&gt;1984年，霍尔与合作者提出TCSP理论，扩展出事件隐蔽、递归等核心算子，形成完整的基于事件的代数演算系统。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;CSP 是 Tony Hoare（图灵奖得主，快速排序的发明者）在 &lt;strong&gt;1978 年&lt;/strong&gt;发表的一篇同名论文中提出的并发编程理论模型。它的核心主张极其简洁：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;不要通过共享内存来通信，而应该通过通信来共享内存。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Do not communicate by sharing memory; instead, share memory by communicating.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这句话后来成为 Go 语言并发哲学的灵魂，也是 Go 官方文档里反复强调的设计原则。理解 CSP，才能理解为什么 Go 的并发原语长成 &lt;code&gt;goroutine&lt;/code&gt; + &lt;code&gt;channel&lt;/code&gt; + &lt;code&gt;select&lt;/code&gt; 这个样子。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一csp-要解决什么问题"&gt;一、CSP 要解决什么问题&lt;/h2&gt;
&lt;p&gt;在 CSP 出现之前，并发编程的主流范式是&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;竞态条件（Race Condition）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;结果依赖线程调度的时序，偶发、难复现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;死锁（Deadlock）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;两个线程各持一把锁、互相等对方的锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;锁粒度难把握&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;锁太粗性能差，锁太细正确性难保证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;可读性差&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;锁的获取/释放散落在各处，逻辑不连贯&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CSP 提出的思路是&lt;strong&gt;从根本上取消共享内存这个媒介&lt;/strong&gt;：每个并发实体（在 CSP 里叫&amp;quot;进程&amp;quot;，在 Go 里叫 &lt;code&gt;goroutine&lt;/code&gt;）拥有&lt;strong&gt;私有的状态&lt;/strong&gt;，它们之间&lt;strong&gt;只能通过消息传递&lt;/strong&gt;来交换数据。既然没有共享内存，就不需要锁，竞态和死锁问题在模型层面被大幅消解。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二csp-的核心抽象"&gt;二、CSP 的核心抽象&lt;/h2&gt;
&lt;p&gt;CSP 理论模型里有三个基本要素：&lt;/p&gt;</description><content:encoded><![CDATA[<h1 id="csp通信顺序进程详解">CSP（通信顺序进程）详解</h1>
<p>CSP（通信顺序进程）是由图灵奖得主托尼·霍尔提出的并发系统代数理论‌，核心思想是通过独立进程间的显式消息通信替代共享内存，从根源上降低并发编程的复杂度。</p>
<blockquote>
<p>📜 理论起源与发展</p>
<ul>
<li>1978年，英国计算机科学家 ‌托尼·霍尔‌ 首次正式阐述CSP理论，1985年出版同名专著《Communicating Sequential Processes》。</li>
<li>1984年，霍尔与合作者提出TCSP理论，扩展出事件隐蔽、递归等核心算子，形成完整的基于事件的代数演算系统。</li>
</ul>
</blockquote>
<p>CSP 是 Tony Hoare（图灵奖得主，快速排序的发明者）在 <strong>1978 年</strong>发表的一篇同名论文中提出的并发编程理论模型。它的核心主张极其简洁：</p>
<blockquote>
<p><strong>不要通过共享内存来通信，而应该通过通信来共享内存。</strong></p>
<p><em>&ldquo;Do not communicate by sharing memory; instead, share memory by communicating.&rdquo;</em></p>
</blockquote>
<p>这句话后来成为 Go 语言并发哲学的灵魂，也是 Go 官方文档里反复强调的设计原则。理解 CSP，才能理解为什么 Go 的并发原语长成 <code>goroutine</code> + <code>channel</code> + <code>select</code> 这个样子。</p>
<hr>
<h2 id="一csp-要解决什么问题">一、CSP 要解决什么问题</h2>
<p>在 CSP 出现之前，并发编程的主流范式是<strong>共享内存 + 锁</strong>。多个线程读写同一块内存，靠互斥锁、信号量、条件变量保证正确性。这种范式的问题在实践中暴露得非常充分：</p>
<table>
	<thead>
			<tr>
					<th>问题</th>
					<th>表现</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>竞态条件（Race Condition）</strong></td>
					<td>结果依赖线程调度的时序，偶发、难复现</td>
			</tr>
			<tr>
					<td><strong>死锁（Deadlock）</strong></td>
					<td>两个线程各持一把锁、互相等对方的锁</td>
			</tr>
			<tr>
					<td><strong>锁粒度难把握</strong></td>
					<td>锁太粗性能差，锁太细正确性难保证</td>
			</tr>
			<tr>
					<td><strong>可读性差</strong></td>
					<td>锁的获取/释放散落在各处，逻辑不连贯</td>
			</tr>
	</tbody>
</table>
<p>CSP 提出的思路是<strong>从根本上取消共享内存这个媒介</strong>：每个并发实体（在 CSP 里叫&quot;进程&quot;，在 Go 里叫 <code>goroutine</code>）拥有<strong>私有的状态</strong>，它们之间<strong>只能通过消息传递</strong>来交换数据。既然没有共享内存，就不需要锁，竞态和死锁问题在模型层面被大幅消解。</p>
<hr>
<h2 id="二csp-的核心抽象">二、CSP 的核心抽象</h2>
<p>CSP 理论模型里有三个基本要素：</p>
<h3 id="21-进程process">2.1 进程（Process）</h3>
<p>一个独立的、顺序执行的实体。它有自己的局部状态，按照既定逻辑一步步执行。在 Go 里，这就是 <strong><code>goroutine</code></strong>——一个由运行时调度的轻量级执行单元。</p>
<h3 id="22-通道channel">2.2 通道（Channel）</h3>
<p>进程之间唯一的通信媒介。通道是<strong>有类型的、单向或双向的、可阻塞的</strong>消息队列。发送方把数据放入通道，接收方从通道取出数据。CSP 的关键在于：</p>
<ul>
<li><strong>通道是同步点</strong>：无缓冲通道上的发送和接收必须&quot;会合&quot;才继续，这天然形成了一种&quot;握手&quot;式的同步。</li>
<li><strong>通道是唯一的数据通路</strong>：进程不共享内存，数据从发送方&quot;移动&quot;到接收方，而不是被两边同时看到。</li>
</ul>
<p>Go 的 <code>chan T</code> 就是通道的直接实现。</p>
<h3 id="23-选择choice--select">2.3 选择（Choice / Select）</h3>
<p>进程可以同时等待多个通道操作，哪个先就绪就走哪个分支。这是 CSP 的&quot;守卫命令&quot;（guarded command）在工程上的落地。Go 的 <strong><code>select</code></strong> 语句就是它的实现：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">msg</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ch1 有数据</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ch2 可以发送</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">timeout</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 超时</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 没有就绪的，走默认分支（非阻塞）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>select</code> 让&quot;等待多个通信事件&quot;成为一等公民，这是 CSP 区别于简单队列模型的标志性能力。</p>
<hr>
<h2 id="三csp-在-go-中的完整映射">三、CSP 在 Go 中的完整映射</h2>
<table>
	<thead>
			<tr>
					<th>CSP 概念</th>
					<th>Go 实现</th>
					<th>说明</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>进程</td>
					<td><code>goroutine</code></td>
					<td><code>go f()</code> 启动，运行时调度，栈动态增长</td>
			</tr>
			<tr>
					<td>通道</td>
					<td><code>chan T</code></td>
					<td>有缓冲/无缓冲，可关闭，有方向类型 <code>chan&lt;-</code> / <code>&lt;-chan</code></td>
			</tr>
			<tr>
					<td>守卫选择</td>
					<td><code>select</code></td>
					<td>多路复用，支持 <code>default</code> 非阻塞和 <code>timeout</code></td>
			</tr>
			<tr>
					<td>同步会合</td>
					<td>无缓冲通道的收发</td>
					<td>发送方阻塞直到接收方就绪，反之亦然</td>
			</tr>
			<tr>
					<td>异步解耦</td>
					<td>有缓冲通道</td>
					<td>缓冲满/空时才阻塞</td>
			</tr>
			<tr>
					<td>进程终止</td>
					<td>关闭通道 / <code>context</code> 取消</td>
					<td>接收方通过 <code>v, ok := &lt;-ch</code> 感知关闭</td>
			</tr>
	</tbody>
</table>
<p><strong>关键点：Go 的 <code>channel</code> 不是简单的&quot;线程安全队列&quot;</strong>，它是 CSP 同步语义的载体。无缓冲通道的阻塞行为是<strong>设计意图</strong>，不是性能缺陷——它强制两个 goroutine 在某个时刻&quot;对齐&quot;，这种对齐本身就是一种同步原语，可以替代很多场景下的锁。</p>
<hr>
<h2 id="四为什么通过通信共享内存更好">四、为什么&quot;通过通信共享内存&quot;更好</h2>
<p>用一个经典对比来理解。假设两个 goroutine 要&quot;谁先到谁设置一个标志&quot;：</p>
<p><strong>共享内存 + 锁的写法：</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">done</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// goroutine A</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">done</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">done</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 做事情</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这里 <code>done</code> 是共享状态，<code>mu</code> 保护它，任何新增访问点都必须记得加锁。锁的保护范围和生命周期是<strong>人工维护的契约</strong>，容易漏。</p>
<p><strong>CSP 的写法（用 channel 的关闭语义表达&quot;只发生一次&quot;）：</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">done</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// goroutine A</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 已经有人做过了</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">close</span><span class="p">(</span><span class="nx">done</span><span class="p">)</span><span class="w">  </span><span class="c1">// 只有 close 成功的人&#34;赢得&#34;了这次机会</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 做事情</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>数据（&ldquo;是否已完成&quot;这个信息）通过 <code>done</code> 通道的<strong>关闭事件</strong>传递，而不是通过一个共享的布尔变量。没有锁，正确性由通道的关闭语义（<code>close</code> 只能成功一次）保证。</p>
<p>再看你之前代码里的实际例子——<strong>信号处理的 <code>sigCh</code></strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">sigCh</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Signal</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">signal</span><span class="p">.</span><span class="nf">Notify</span><span class="p">(</span><span class="nx">sigCh</span><span class="p">,</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Interrupt</span><span class="p">,</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">SIGTERM</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sig</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">sigCh</span><span class="w">       </span><span class="c1">// 阻塞等待信号</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">cancel</span><span class="p">()</span><span class="w">             </span><span class="c1">// 通过 context 通知</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>信号从操作系统（一个&quot;进程&rdquo;）通过 <code>sigCh</code>（通道）传给处理 goroutine（另一个&quot;进程&quot;），数据是&quot;信号&quot;这个值。<code>main</code> 逻辑不共享任何&quot;信号标志位&quot;，而是通过 <code>ctx.Done()</code> 这个通道的关闭事件感知退出。整条链路没有一把锁，这正是 CSP 风格的典型体现。</p>
<hr>
<h2 id="五csp-vs-其他并发模型">五、CSP vs 其他并发模型</h2>
<table>
	<thead>
			<tr>
					<th>模型</th>
					<th>核心机制</th>
					<th>代表语言</th>
					<th>优点</th>
					<th>缺点</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>CSP</strong></td>
					<td>通道传递消息，进程私有状态</td>
					<td>Go、Occam</td>
					<td>无锁、易推理、同步语义明确</td>
					<td>通道设计不当也会死锁；大量细粒度通道有开销</td>
			</tr>
			<tr>
					<td><strong>Actor 模型</strong></td>
					<td>每个 Actor 有邮箱，异步消息</td>
					<td>Erlang、Elixir、Akka</td>
					<td>天然分布式、容错强（监督树）</td>
					<td>消息无序、无全局同步点，调试复杂</td>
			</tr>
			<tr>
					<td><strong>共享内存 + 锁</strong></td>
					<td>互斥锁保护共享数据</td>
					<td>C/C++/Java 传统写法</td>
					<td>直接、灵活、性能可控</td>
					<td>竞态、死锁、可读性差</td>
			</tr>
			<tr>
					<td><strong>async/await</strong></td>
					<td>单线程事件循环 + 协程</td>
					<td>JS、Python</td>
					<td>无锁、适合 IO 密集</td>
					<td>阻塞操作会卡整个循环，CPU 密集不适用</td>
			</tr>
	</tbody>
</table>
<p><strong>CSP 和 Actor 的关键区别</strong>：CSP 的通道是<strong>同步会合</strong>（无缓冲时发送方要等接收方），Actor 的邮箱是<strong>异步投递</strong>（发完就走）。CSP 里&quot;谁接收&quot;由通道的静态连接决定，Actor 里&quot;发给哪个 Actor&quot;是运行时动态路由。Go 的 <code>channel</code> 更接近 CSP，但通过缓冲通道也提供了部分异步能力，是一个折中实现。</p>
<p><strong>CSP 和共享内存的关系</strong>：CSP 并非完全禁止共享内存——Go 里你依然可以用 <code>sync.Mutex</code>、<code>atomic</code>。CSP 是一种<strong>优先推荐的范式</strong>，不是强制约束。Go 官方文档的说法是：&ldquo;channel 用于在 goroutine 之间传递数据的所有权；mutex 用于保护 goroutine 内部的数据结构&rdquo;。两者各司其职，不是非此即彼。</p>
<hr>
<h2 id="六csp-的局限性与实践中要注意的地方">六、CSP 的局限性与实践中要注意的地方</h2>
<p>CSP 不是银弹，用它写代码依然会踩坑：</p>
<p><strong>1. 通道也会死锁</strong></p>
<p>两个 goroutine 互相等待对方从通道接收，就死锁了。Go 运行时有死锁检测（所有 goroutine 都阻塞时 panic），但只在&quot;完全无进展&quot;时触发，复杂的多通道场景可能检测不到。</p>
<p><strong>2. 通道不是&quot;免费&quot;的</strong></p>
<p>每个通道操作都有调度开销，大量极细粒度的通道通信（比如每个元素一个通道）性能可能不如一把锁保护一个切片。CSP 适合<strong>粗粒度、有明确交接语义</strong>的通信。</p>
<p><strong>3. &ldquo;共享内存&quot;换了形式也可能回来</strong></p>
<p>如果通过通道传递的是<strong>指针或 map 引用</strong>，接收方拿到后修改它，本质上还是在共享内存。CSP 的&quot;私有状态&quot;要求传递的是<strong>值</strong>或<strong>所有权转移</strong>，传指针会破坏这个前提。</p>
<p><strong>4. <code>select</code> 的公平性</strong></p>
<p><code>select</code> 在多个 case 同时就绪时<strong>随机选择</strong>一个，不保证顺序。如果需要严格的优先级，得自己用嵌套 <code>select</code> 或额外逻辑实现。</p>
<p><strong>5. 取消与超时需要额外机制</strong></p>
<p>CSP 原生没有&quot;取消&quot;概念，Go 用 <code>context</code> 补上了这块。<code>context.Done()</code> 本身也是一个通道（<code>&lt;-chan struct{}</code>），本质上还是用 CSP 的思想实现取消传播。</p>
<hr>
<h2 id="七回到你的代码csp-视角的解读">七、回到你的代码：CSP 视角的解读</h2>
<p>回顾之前的 <code>StateGraph</code> 工程，几处设计都可以用 CSP 的语言来解释：</p>
<p><strong>信号处理</strong>：<code>sigCh</code> 是操作系统与程序之间的通道，<code>signal.Notify</code> 注册的是&quot;谁来接收&rdquo;，消费 goroutine 通过阻塞接收实现同步。</p>
<p><strong>并行扇出</strong>：<code>weather</code>、<code>geo</code>、<code>github</code> 三个节点在 <code>langgraphgo</code> 内部由独立的 goroutine 执行，它们各自返回<strong>状态副本</strong>（<code>copyState</code>），这是&quot;私有状态&quot;的体现；框架在合并时通过 <code>Reducer</code> 把结果&quot;通信&quot;回主状态，而不是让三个 goroutine 直接写同一个 map——这正是&quot;通过通信共享内存&quot;的实践。</p>
<p><strong>人工中断</strong>：<code>GraphInterrupt</code> 本质上是把&quot;暂停点&quot;的状态通过返回值&quot;传递&quot;给调用方，调用方决策后再把状态&quot;传回&quot;恢复执行。状态在两次调用之间不是被共享的，而是被<strong>移交</strong>的。</p>
<p><strong>HTTP 日志的 <code>sync.Mutex</code></strong>：这里用的恰恰是<strong>共享内存 + 锁</strong>。因为日志文件是一个被多个 goroutine 并发写入的单一资源，用 <code>Mutex</code> 保护它比&quot;每个请求一个通道再串行化写&quot;更简单直接。这印证了前面说的：<strong>CSP 和锁不是对立，而是分工</strong>。通道适合&quot;传递数据所有权&quot;，锁适合&quot;保护共享资源&quot;。</p>
<hr>
<h2 id="八总结">八、总结</h2>
<p>CSP 的价值不在于它提供了多少新原语，而在于它<strong>重新定义了并发实体之间的关系</strong>：从&quot;竞争同一块内存&quot;变成&quot;通过通道交换消息&quot;。这个视角转换带来的收益是：</p>
<ul>
<li><strong>正确性更容易推理</strong>：没有隐藏的共享状态，每个 goroutine 的行为只依赖它收到的消息</li>
<li><strong>同步语义显式化</strong>：无缓冲通道的阻塞行为本身就是同步点，不需要额外的锁或条件变量</li>
<li><strong>组合性更好</strong>：<code>select</code> 让多路通信的组合表达得非常自然，这是锁模型很难优雅实现的</li>
</ul>
<p>Go 把 CSP 从理论变成了工程现实——<code>goroutine</code> 足够轻、<code>channel</code> 足够快、<code>select</code> 足够灵活，三者组合让 CSP 风格成为 Go 并发代码的默认写法。但正如所有模型一样，它有自己的适用边界：<strong>用通道传递数据和信号，用锁保护内部数据结构，用 context 传播取消</strong>——这三者配合，才是 Go 并发编程的完整图景。</p>
]]></content:encoded></item><item><title>Template text/template 完全学习</title><link>https://blog.dart.xin/golang/go108-text-template/</link><pubDate>Tue, 13 Oct 2026 11:32:20 +0800</pubDate><guid>https://blog.dart.xin/golang/go108-text-template/</guid><description>text/template 专门用于生成任意文本输出（HTML、配置文件、代码、Prompt 等）</description><content:encoded><![CDATA[<h1 id="go-语言-texttemplate-完全指南">Go 语言 <code>text/template</code> 完全指南</h1>
<p><code>text/template</code> 是 Go 标准库自带的模板引擎，专门用于生成<strong>任意文本输出</strong>（HTML、配置文件、代码、Prompt 等）。下面从零讲到进阶，每个知识点都配可运行代码。</p>
<hr>
<h2 id="一为什么需要模板引擎">一、为什么需要模板引擎？</h2>
<p>假设你要生成一段 Prompt：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 字符串拼接</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">prompt</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">&#34;你是一个&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">role</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;助手。请用&#34;</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">style</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;风格回答。&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>一旦需求变复杂——要根据条件加 few-shot 示例、要循环生成示例、要做 A/B 实验——字符串拼接就变得难以维护。</p>
<p>模板引擎把&quot;文案&quot;和&quot;逻辑&quot;分离：</p>
<pre tabindex="0"><code>你是一个{{ .Role }}助手。请用{{ .Style }}风格回答。
</code></pre><p>代码只负责提供数据，模板负责渲染。</p>
<hr>
<h2 id="二最小可运行示例">二、最小可运行示例</h2>
<h3 id="maingo"><code>main.go</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;text/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;hello&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;你好，{{ .Name }}！你今年 {{ .Age }} 岁。\n&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;Age&#34;</span><span class="p">:</span><span class="w">  </span><span class="mi">18</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>运行：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">go run main.go
</span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>你好，小明！你今年 18 岁。
</code></pre><p>这就是模板的核心三步：<strong>Parse → Execute → 输出</strong>。</p>
<hr>
<h2 id="三三个核心概念">三、三个核心概念</h2>
<table>
	<thead>
			<tr>
					<th style="text-align: left">概念</th>
					<th style="text-align: left">说明</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><strong>Template</strong></td>
					<td style="text-align: left">解析后的模板对象，可重复使用</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>Data</strong></td>
					<td style="text-align: left">传给模板的数据，可以是 map、struct、基本类型</td>
			</tr>
			<tr>
					<td style="text-align: left"><strong>Execute</strong></td>
					<td style="text-align: left">把模板与数据结合，写入 <code>io.Writer</code></td>
			</tr>
	</tbody>
</table>
<p>关键点：</p>
<ul>
<li><code>Parse</code> 只做一次，<code>Execute</code> 可以反复调用（并发安全）。</li>
<li><code>Execute</code> 的第二个参数就是模板里的 <code>.</code>（点）。</li>
</ul>
<hr>
<h2 id="四模板语法详解">四、模板语法详解</h2>
<h3 id="1-输出变量--字段-">1. 输出变量 <code>{{ .字段 }}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;{{ .Name }} 在 {{ .City }}\n&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;City&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;杭州&#34;</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 输出：小明 在 杭州</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="2-struct-作为数据">2. struct 作为数据</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">User</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Name</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Age</span><span class="w">  </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;{{ .Name }} 今年 {{ .Age }} 岁\n&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">User</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Age</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 输出：小明 今年 18 岁</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p><strong>推荐用 struct</strong>：字段名写错会在编译期报错，比 map 更安全。</p>
</blockquote>
<h3 id="3-条件判断--if---else---end-">3. 条件判断 <code>{{ if }}</code> <code>{{ else }}</code> <code>{{ end }}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.IsVIP</span><span class="w"> </span><span class="cp">}}</span><span class="s">尊贵的 VIP 用户</span><span class="cp">{{</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="cp">}}</span><span class="s">普通用户</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">，你好！
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">bool</span><span class="p">{</span><span class="s">&#34;IsVIP&#34;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">bool</span><span class="p">{</span><span class="s">&#34;IsVIP&#34;</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>尊贵的 VIP 用户，你好！
普通用户，你好！
</code></pre><p><code>if</code> 会判断值是否为&quot;零值&quot;：<code>false</code>、<code>0</code>、<code>&quot;&quot;</code>、<code>nil</code>、空切片都是假。</p>
<h3 id="4-循环--range-">4. 循环 <code>{{ range }}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`示例：
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="na">.Examples</span><span class="w"> </span><span class="cp">}}</span><span class="s">输入：</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Input</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">输出：</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Output</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;Examples&#34;</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;这家餐厅很棒&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;正面&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;太失望了&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;负面&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;今天天气不错&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;中性&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>示例：
输入：这家餐厅很棒
输出：正面
输入：太失望了
输出：负面
输入：今天天气不错
输出：中性
</code></pre><p><code>range</code> 里 <code>.</code> 变成当前元素，所以 <code>{{ .Input }}</code> 取当前元素的字段。</p>
<h3 id="5-循环里的索引">5. 循环里的索引</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">$i</span><span class="o">,</span><span class="w"> </span><span class="nx">$v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Items</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="nx">$i</span><span class="w"> </span><span class="cp">}}</span><span class="s">: </span><span class="cp">{{</span><span class="w"> </span><span class="nx">$v</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="s">&#34;Items&#34;</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;a&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;b&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;c&#34;</span><span class="p">}})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>0: a
1: b
2: c
</code></pre><h3 id="6-变量--x---">6. 变量 <code>{{ $x := ... }}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$name</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="cp">}}</span><span class="s">你好，</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$name</span><span class="w"> </span><span class="cp">}}</span><span class="s">！</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$name</span><span class="w"> </span><span class="cp">}}</span><span class="s">，欢迎回来。
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>你好，小明！小明，欢迎回来。
</code></pre><p>变量在 <code>{{ end }}</code> 后失效，也可以在 <code>range</code> 外定义、内部使用。</p>
<h3 id="7-管道----func-">7. 管道 <code>{{ . | func }}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="k">printf</span><span class="w"> </span><span class="s">&#34;%s先生&#34;</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="na">.Text</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="k">len</span><span class="w"> </span><span class="cp">}}</span><span class="s"> 个字
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Text&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;你好世界&#34;</span><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>小明先生4 个字
</code></pre><p>管道把前一个结果作为后一个函数的最后一个参数。</p>
<h3 id="8-内建函数一览">8. 内建函数一览</h3>
<table>
	<thead>
			<tr>
					<th style="text-align: left">函数</th>
					<th style="text-align: left">作用</th>
					<th style="text-align: left">示例</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><code>len</code></td>
					<td style="text-align: left">长度</td>
					<td style="text-align: left"><code>{{ .Text | len }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>index</code></td>
					<td style="text-align: left">索引</td>
					<td style="text-align: left"><code>{{ index .Items 0 }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>printf</code></td>
					<td style="text-align: left">格式化</td>
					<td style="text-align: left"><code>{{ printf &quot;%d&quot; .Age }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>print</code> / <code>println</code></td>
					<td style="text-align: left">打印</td>
					<td style="text-align: left"><code>{{ print .Name }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>eq</code> / <code>ne</code></td>
					<td style="text-align: left">等于/不等于</td>
					<td style="text-align: left"><code>{{ if eq .A .B }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>lt</code> / <code>le</code> / <code>gt</code> / <code>ge</code></td>
					<td style="text-align: left">比较</td>
					<td style="text-align: left"><code>{{ if gt .Age 18 }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>and</code> / <code>or</code> / <code>not</code></td>
					<td style="text-align: left">逻辑</td>
					<td style="text-align: left"><code>{{ if and .A .B }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>urlquery</code></td>
					<td style="text-align: left">URL 转义</td>
					<td style="text-align: left"><code>{{ .Q | urlquery }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>html</code></td>
					<td style="text-align: left">HTML 转义</td>
					<td style="text-align: left"><code>{{ .T | html }}</code></td>
			</tr>
			<tr>
					<td style="text-align: left"><code>js</code></td>
					<td style="text-align: left">JS 转义</td>
					<td style="text-align: left"><code>{{ .T | js }}</code></td>
			</tr>
	</tbody>
</table>
<h3 id="9-去除空白---和--">9. 去除空白 <code>{{-</code> 和 <code>-}}</code></h3>
<p>模板里的换行和缩进会原样输出。用 <code>-</code> 可以吃掉两侧空白：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`示例：
</span></span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="na">.Examples</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">输入：</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Input</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出会去掉 <code>range</code> 和 <code>end</code> 行产生的空行。</p>
<h3 id="10-注释---">10. 注释 <code>{{/* ... */}}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cm">{{/* 这是注释，不会输出 */}}</span><span class="s">你好，</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="五多模板与嵌套">五、多模板与嵌套</h2>
<h3 id="1-定义命名模板--define-">1. 定义命名模板 <code>{{ define }}</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">define</span><span class="w"> </span><span class="s">&#34;greeting&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">你好，</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="cp">}}</span><span class="s">！</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">define</span><span class="w"> </span><span class="s">&#34;body&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">欢迎使用本系统。</span><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">template</span><span class="w"> </span><span class="s">&#34;greeting&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">template</span><span class="w"> </span><span class="s">&#34;body&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>你好，小明！
欢迎使用本系统。
</code></pre><h3 id="2-模板里调用模板">2. 模板里调用模板</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">define</span><span class="w"> </span><span class="s">&#34;example&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">输入：</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Input</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">输出：</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Output</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">示例：
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="na">.Examples</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="k">template</span><span class="w"> </span><span class="s">&#34;example&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;Examples&#34;</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;很好&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;正面&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;太差&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;负面&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>示例：
输入：很好
输出：正面
输入：太差
输出：负面
</code></pre><hr>
<h2 id="六从文件加载模板">六、从文件加载模板</h2>
<p>真实项目里模板通常放在文件里。</p>
<h3 id="目录结构">目录结构</h3>
<pre tabindex="0"><code>.
├── main.go
└── tmpl/
    ├── greeting.tmpl
    └── example.tmpl
</code></pre><h3 id="tmplgreetingtmpl"><code>tmpl/greeting.tmpl</code></h3>
<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">你好，{{ .Name }}！欢迎使用。
</code></pre><h3 id="tmplexampletmpl"><code>tmpl/example.tmpl</code></h3>
<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">示例：
{{- range .Examples }}
输入：{{ .Input }}
输出：{{ .Output }}
{{- end }}
</code></pre><h3 id="maingo-1"><code>main.go</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;text/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 加载单个文件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">t1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;tmpl/greeting.tmpl&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">t1</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;小明&#34;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 加载多个文件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">t2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseGlob</span><span class="p">(</span><span class="s">&#34;tmpl/*.tmpl&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 指定用哪个模板渲染</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">t2</span><span class="p">.</span><span class="nf">ExecuteTemplate</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;example.tmpl&#34;</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;Examples&#34;</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;很好&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;正面&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">{</span><span class="s">&#34;Input&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;太差&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Output&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;负面&#34;</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>关键点</strong>：用 <code>ParseFiles</code> / <code>ParseGlob</code> 加载多文件时，模板名就是文件名。渲染时用 <code>ExecuteTemplate(w, &quot;文件名&quot;, data)</code> 指定用哪个。</p>
<hr>
<h2 id="七自定义函数">七、自定义函数</h2>
<p>模板可以注册自定义函数，扩展表达能力。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;strings&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;text/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">funcs</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nx">FuncMap</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;upper&#34;</span><span class="p">:</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nx">ToUpper</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;trunc&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="nx">s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">s</span><span class="p">[:</span><span class="nx">n</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;...&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;add&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">b</span><span class="w"> </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">const</span><span class="w"> </span><span class="nx">t</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">upper</span><span class="w"> </span><span class="cp">}}</span><span class="s"> 
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="na">.Text</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">trunc</span><span class="w"> </span><span class="nx">10</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">1 + 2 = </span><span class="cp">{{</span><span class="w"> </span><span class="nx">add</span><span class="w"> </span><span class="nx">1</span><span class="w"> </span><span class="nx">2</span><span class="w"> </span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Funcs</span><span class="p">(</span><span class="nx">funcs</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">t</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;xiaoming&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="s">&#34;Text&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;这是一段很长的文本内容需要截断&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>XIAOMING 
这是一段很长的文...
1 + 2 = 3
</code></pre><p><strong>注意</strong>：</p>
<ul>
<li>自定义函数必须在 <code>Parse</code> 之前用 <code>Funcs</code> 注册。</li>
<li>函数只能返回一个值或 <code>(值, error)</code>；返回 error 时，模板执行会中止并报错。</li>
<li>函数名是模板里用的名字，可以和 Go 函数名不同。</li>
</ul>
<hr>
<h2 id="八错误处理">八、错误处理</h2>
<p><code>Parse</code> 和 <code>Execute</code> 都可能出错，生产代码必须处理。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;{{ .NotExist }}&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 解析错误：语法问题</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;Name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;x&#34;</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 执行错误：字段不存在</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>用 <code>template.Must</code> 可以在出错时直接 panic，适合初始化阶段：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;x&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;...&#34;</span><span class="p">))</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>规则</strong>：启动时加载模板用 <code>Must</code>，运行时 <code>Execute</code> 用 <code>if err != nil</code>。</p>
<hr>
<h2 id="九texttemplate-vs-htmltemplate">九、<code>text/template</code> vs <code>html/template</code></h2>
<table>
	<thead>
			<tr>
					<th style="text-align: left">维度</th>
					<th style="text-align: left"><code>text/template</code></th>
					<th style="text-align: left"><code>html/template</code></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left">用途</td>
					<td style="text-align: left">纯文本、代码、配置文件、Prompt</td>
					<td style="text-align: left">生成 HTML</td>
			</tr>
			<tr>
					<td style="text-align: left">输出转义</td>
					<td style="text-align: left">不转义</td>
					<td style="text-align: left">自动 HTML 转义</td>
			</tr>
			<tr>
					<td style="text-align: left">防 XSS</td>
					<td style="text-align: left">不负责</td>
					<td style="text-align: left">自动防 XSS</td>
			</tr>
			<tr>
					<td style="text-align: left">函数集</td>
					<td style="text-align: left">通用</td>
					<td style="text-align: left">增加 HTML 相关函数</td>
			</tr>
			<tr>
					<td style="text-align: left">用法</td>
					<td style="text-align: left">基本相同</td>
					<td style="text-align: left">基本相同</td>
			</tr>
	</tbody>
</table>
<p><strong>规则</strong>：</p>
<ul>
<li>生成 HTML → 用 <code>html/template</code></li>
<li>生成 Prompt、配置文件、代码 → 用 <code>text/template</code></li>
</ul>
<p>它们的模板语法完全一致，只是转义策略不同。</p>
<hr>
<h2 id="十完整可运行示例prompt-模板引擎">十、完整可运行示例：Prompt 模板引擎</h2>
<p>把上面所有知识点串起来，做一个 Prompt 工程里常见的例子。</p>
<h3 id="目录结构-1">目录结构</h3>
<pre tabindex="0"><code>.
├── main.go
├── config.json
└── prompts/
    └── classify.tmpl
</code></pre><h3 id="configjson"><code>config.json</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;文本情感分类助手&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;labels&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;正面&#34;</span><span class="p">,</span> <span class="s2">&#34;负面&#34;</span><span class="p">,</span> <span class="s2">&#34;中性&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;style&#34;</span><span class="p">:</span> <span class="s2">&#34;简洁&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;examples&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nt">&#34;Input&#34;</span><span class="p">:</span> <span class="s2">&#34;这家餐厅很棒&#34;</span><span class="p">,</span> <span class="nt">&#34;Output&#34;</span><span class="p">:</span> <span class="s2">&#34;正面&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nt">&#34;Input&#34;</span><span class="p">:</span> <span class="s2">&#34;太失望了&#34;</span><span class="p">,</span> <span class="nt">&#34;Output&#34;</span><span class="p">:</span> <span class="s2">&#34;负面&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nt">&#34;Input&#34;</span><span class="p">:</span> <span class="s2">&#34;今天天气不错&#34;</span><span class="p">,</span> <span class="nt">&#34;Output&#34;</span><span class="p">:</span> <span class="s2">&#34;中性&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="promptsclassifytmpl"><code>prompts/classify.tmpl</code></h3>
<pre tabindex="0"><code class="language-gotemplate" data-lang="gotemplate">你是一个{{ .Role }}。请对用户输入的文本进行分类。

{{- if .Labels }}
可选标签：{{ range $i, $l := .Labels }}{{ if $i }}、{{ end }}{{ $l }}{{ end }}
{{- end }}

风格要求：{{ .Style }}

{{- if .Examples }}
示例：
{{- range .Examples }}
输入：{{ .Input }}
输出：{{ .Output }}
{{- end }}
{{- end }}

输入：{{ .Input }}
输出：
</code></pre><h3 id="maingo-2"><code>main.go</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;encoding/json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;strings&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;text/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Example</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Input</span><span class="w">  </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;input&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Output</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;output&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Config</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Role</span><span class="w">     </span><span class="kt">string</span><span class="w">    </span><span class="s">`json:&#34;role&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Labels</span><span class="w">   </span><span class="p">[]</span><span class="kt">string</span><span class="w">  </span><span class="s">`json:&#34;labels&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Style</span><span class="w">    </span><span class="kt">string</span><span class="w">    </span><span class="s">`json:&#34;style&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Examples</span><span class="w"> </span><span class="p">[]</span><span class="nx">Example</span><span class="w"> </span><span class="s">`json:&#34;examples&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">PromptData</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Role</span><span class="w">     </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Labels</span><span class="w">   </span><span class="p">[]</span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Style</span><span class="w">    </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Examples</span><span class="w"> </span><span class="p">[]</span><span class="nx">Example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Input</span><span class="w">    </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 1. 读取配置</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">cfgBytes</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">ReadFile</span><span class="p">(</span><span class="s">&#34;config.json&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">cfg</span><span class="w"> </span><span class="nx">Config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">json</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">cfgBytes</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">cfg</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 2. 加载模板</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;prompts/classify.tmpl&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 3. 渲染</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">PromptData</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Role</span><span class="p">:</span><span class="w">     </span><span class="nx">cfg</span><span class="p">.</span><span class="nx">Role</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Labels</span><span class="p">:</span><span class="w">   </span><span class="nx">cfg</span><span class="p">.</span><span class="nx">Labels</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Style</span><span class="p">:</span><span class="w">    </span><span class="nx">cfg</span><span class="p">.</span><span class="nx">Style</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Examples</span><span class="p">:</span><span class="w"> </span><span class="nx">cfg</span><span class="p">.</span><span class="nx">Examples</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">Input</span><span class="p">:</span><span class="w">    </span><span class="s">&#34;Go 语言的并发模型非常优雅，我很喜欢。&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">sb</span><span class="w"> </span><span class="nx">strings</span><span class="p">.</span><span class="nx">Builder</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">sb</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">sb</span><span class="p">.</span><span class="nf">String</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="运行">运行</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">go run main.go
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="输出">输出</h3>
<pre tabindex="0"><code>你是一个文本情感分类助手。请对用户输入的文本进行分类。

可选标签：正面、负面、中性

风格要求：简洁

示例：
输入：这家餐厅很棒
输出：正面
输入：太失望了
输出：负面
输入：今天天气不错
输出：中性

输入：Go 语言的并发模型非常优雅，我很喜欢。
输出：
</code></pre><p>这个例子覆盖了：</p>
<ul>
<li>struct 作为数据</li>
<li><code>if</code> 条件</li>
<li><code>range</code> 循环</li>
<li>嵌套 <code>if</code> + <code>range</code></li>
<li><code>$i</code> 索引变量</li>
<li>空白控制 <code>{{-</code> / <code>-}}</code></li>
<li>从文件加载模板</li>
</ul>
<hr>
<h2 id="十一常见坑与最佳实践">十一、常见坑与最佳实践</h2>
<table>
	<thead>
			<tr>
					<th style="text-align: left">坑</th>
					<th style="text-align: left">说明</th>
					<th style="text-align: left">建议</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left">字段不存在</td>
					<td style="text-align: left"><code>{{ .NotExist }}</code> 执行时报错</td>
					<td style="text-align: left">用 struct 而非 map，编译期发现拼写</td>
			</tr>
			<tr>
					<td style="text-align: left">空白太多</td>
					<td style="text-align: left">换行缩进原样输出</td>
					<td style="text-align: left">用 <code>{{-</code> 和 <code>-}}</code> 控制</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>Parse</code> 只做一次</td>
					<td style="text-align: left">循环里重复 Parse 很浪费</td>
					<td style="text-align: left">Parse 一次，Execute 多次</td>
			</tr>
			<tr>
					<td style="text-align: left">并发安全</td>
					<td style="text-align: left"><code>Execute</code> 是并发安全的</td>
					<td style="text-align: left">一个 tmpl 可被多 goroutine 复用</td>
			</tr>
			<tr>
					<td style="text-align: left">自定义函数顺序</td>
					<td style="text-align: left">必须 Parse 前注册</td>
					<td style="text-align: left"><code>New().Funcs(...).Parse(...)</code></td>
			</tr>
			<tr>
					<td style="text-align: left">HTML 场景误用</td>
					<td style="text-align: left"><code>text/template</code> 不防 XSS</td>
					<td style="text-align: left">HTML 输出改用 <code>html/template</code></td>
			</tr>
			<tr>
					<td style="text-align: left">模板名混淆</td>
					<td style="text-align: left"><code>ParseFiles</code> 时模板名=文件名</td>
					<td style="text-align: left">用 <code>ExecuteTemplate</code> 指定</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="十二函数速查表">十二、函数速查表</h2>
<table>
	<thead>
			<tr>
					<th style="text-align: left">函数</th>
					<th style="text-align: left">说明</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><code>and</code> <code>or</code> <code>not</code></td>
					<td style="text-align: left">逻辑运算</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>eq</code> <code>ne</code> <code>lt</code> <code>le</code> <code>gt</code> <code>ge</code></td>
					<td style="text-align: left">比较</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>len</code></td>
					<td style="text-align: left">长度</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>index</code></td>
					<td style="text-align: left">索引</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>slice</code></td>
					<td style="text-align: left">切片</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>print</code> <code>printf</code> <code>println</code></td>
					<td style="text-align: left">格式化</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>urlquery</code></td>
					<td style="text-align: left">URL 查询转义</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>html</code> <code>js</code> <code>urlquery</code></td>
					<td style="text-align: left">转义</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>call</code></td>
					<td style="text-align: left">调用函数值</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="十三一句话总结">十三、一句话总结</h2>
<blockquote>
<p><strong><code>text/template</code> 用 <code>Parse</code> 解析模板、<code>Execute</code> 渲染数据，支持变量、条件、循环、管道、嵌套、自定义函数，把&quot;文案&quot;与&quot;逻辑&quot;分离。它语法简单、标准库自带、无依赖，是 Go 生成任意文本（包括 Prompt）的首选。</strong></p>
</blockquote>
<p>上面的完整示例可以直接 <code>go run</code>，覆盖了日常使用 90% 的场景。需要更复杂的场景（如热加载、多模板继承）时，再叠加 <code>ParseGlob</code> 和 <code>define</code> / <code>template</code> 即可。</p>
]]></content:encoded></item><item><title>Template 系统性学习</title><link>https://blog.dart.xin/golang/go107-template/</link><pubDate>Tue, 13 Oct 2026 11:05:20 +0800</pubDate><guid>https://blog.dart.xin/golang/go107-template/</guid><description>服务端渲染和数据驱动的文本生成工具</description><content:encoded><![CDATA[<h1 id="go-template-系统性学习指南">Go Template 系统性学习指南</h1>
<p>Go 标准库的模板引擎是服务端渲染和数据驱动的文本生成工具，核心设计理念是<strong>将表现层与数据模型分离</strong>。Go 用同一套语法提供了两个包：<code>text/template</code> 用于通用文本生成，<code>html/template</code> 用于 HTML 输出并自带上下文感知的自动转义安全机制。网络服务中生成 HTML 必须使用 <code>html/template</code>。</p>
<h2 id="一基础用法三步流程">一、基础用法：三步流程</h2>
<p>模板使用的核心流程固定为三步：<strong>定义/解析模板 → 准备数据 → 执行渲染</strong>。</p>
<h3 id="11-从字符串解析">1.1 从字符串解析</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;text/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Inventory</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Material</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Count</span><span class="w">    </span><span class="kt">uint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 步骤1：解析模板</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;test&#34;</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;{{.Count}} items are made of {{.Material}}&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 步骤2：准备数据</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sweaters</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Inventory</span><span class="p">{</span><span class="s">&#34;wool&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">17</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 步骤3：执行渲染</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">sweaters</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 输出：17 items are made of wool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>template.Must</code> 是对 <code>Parse</code> 错误的 panic 包装，适合在包初始化阶段使用。<strong>模板中只能访问结构体的导出字段（首字母大写）</strong>，小写字段在模板侧不可达，会导致执行期报错或呈现零值。</p>
<h3 id="12-从文件解析">1.2 从文件解析</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 解析单个文件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;templates/layout.html&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 解析多个文件（常用于主模板+子模板）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;templates/layout.html&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;templates/header.html&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 使用 Glob 模式批量解析</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseGlob</span><span class="p">(</span><span class="s">&#34;templates/*.html&#34;</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="13-数据访问方式">1.3 数据访问方式</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 结构体字段访问</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="kd">struct</span><span class="p">{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">}{</span><span class="s">&#34;Alice&#34;</span><span class="p">})</span><span class="w">  </span><span class="c1">// {{.Name}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// map 键访问（无大小写限制）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">})</span><span class="w"> </span><span class="c1">// {{.name}} 或 {{.Name}} 均可</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 点（.）表示当前作用域的值</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 在根模板中，{{.}} 就是 Execute 传入的整个数据</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="二模板语法核心">二、模板语法核心</h2>
<h3 id="21-注释">2.1 注释</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="cm">/* 这是注释，不会输出 */</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="cm">/* 注释，同时修剪前后空白 */</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="22-条件判断">2.2 条件判断</h3>
<p>Go 模板中，以下值被视为“假”：<code>false</code>、<code>0</code>、<code>nil</code>、空字符串、空切片/映射/数组。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="k">if</span><span class="w"> </span><span class="p">.</span><span class="nx">Attended</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">很高兴您能参加婚礼</span><span class="err">。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">else</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">很遗憾您未能参加</span><span class="err">。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>if</code> 支持 <code>else if</code>，也常与内置比较函数配合：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="k">if</span><span class="w"> </span><span class="nx">eq</span><span class="w"> </span><span class="p">.</span><span class="nx">Status</span><span class="w"> </span><span class="s">&#34;active&#34;</span><span class="p">}}</span><span class="w"> </span><span class="nx">活跃</span><span class="w"> </span><span class="p">{{</span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">gt</span><span class="w"> </span><span class="p">.</span><span class="nx">Score</span><span class="w"> </span><span class="mi">90</span><span class="p">}}</span><span class="w"> </span><span class="nx">优秀</span><span class="w"> </span><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>常用比较函数：<code>eq</code>（等于）、<code>ne</code>（不等于）、<code>lt</code>（小于）、<code>gt</code>（大于）、<code>le</code>（小于等于）、<code>ge</code>（大于等于）。</p>
<h3 id="23-循环-range">2.3 循环 range</h3>
<p><code>range</code> 支持 slice、array、map、channel。内部 <code>{{.}}</code> 为当前元素；可通过变量捕获索引/键和值。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 遍历切片：下标 + 值</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">range</span><span class="w"> </span><span class="err">$</span><span class="nx">idx</span><span class="p">,</span><span class="w"> </span><span class="err">$</span><span class="nx">email</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">.</span><span class="nx">Emails</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">第</span><span class="w"> </span><span class="p">{{</span><span class="err">$</span><span class="nx">idx</span><span class="p">}}</span><span class="w"> </span><span class="nx">项</span><span class="p">:</span><span class="w"> </span><span class="p">{{</span><span class="err">$</span><span class="nx">email</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 遍历 map：key + value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">range</span><span class="w"> </span><span class="err">$</span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="err">$</span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">.</span><span class="nx">MapContent</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{</span><span class="err">$</span><span class="nx">key</span><span class="p">}}:</span><span class="w"> </span><span class="p">{{</span><span class="err">$</span><span class="nx">value</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>range</code> 在空集合时不执行，<code>. </code> 不会改变。若只需值，使用 <code>{{range .Items}}</code> 即可。</p>
<h3 id="24-with-改变作用域">2.4 with 改变作用域</h3>
<p><code>with</code> 将点临时切换到某个值，适合简化深层字段访问：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">with</span><span class="w"> </span><span class="p">.</span><span class="nx">Gift</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">感谢您送的</span><span class="w"> </span><span class="p">{{.}}</span><span class="err">。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>若 <code>.Gift</code> 为“假值”，<code>with</code> 块不执行。</p>
<h3 id="25-变量">2.5 变量</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="err">$</span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">.</span><span class="nx">SomeValue</span><span class="p">}}</span><span class="w">      </span><span class="p">{{</span><span class="cm">/* 声明并赋值 */</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="err">$</span><span class="nx">v</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;new value&#34;</span><span class="p">}}</span><span class="w">      </span><span class="p">{{</span><span class="cm">/* 重新赋值（Go 1.11+） */</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>变量在 <code>range</code> 和 <code>with</code> 改变点作用域后仍然可读，这是保留根数据引用的常用手段。</p>
<h3 id="26-管道">2.6 管道</h3>
<p>管道语义类似 UNIX：<code>{{ a | f | g }}</code> 等价于 <code>g(f(a))</code>。前一个函数的结果作为下一个函数的<strong>最后一个参数</strong>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="p">.</span><span class="nx">Personal</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">printf</span><span class="w"> </span><span class="s">&#34;[%s]&#34;</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="p">.</span><span class="nx">Name</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">upper</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">printf</span><span class="w"> </span><span class="s">&#34;用户: %s&#34;</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>管道让展示层的轻量格式化（如 <code>printf</code>）保持在模板内，避免把“仅为排版服务”的逻辑塞进业务层。</p>
<h3 id="27-空白修剪">2.7 空白修剪</h3>
<p>模板中的换行和缩进会原样输出，这在生成代码或结构化文本时往往需要精确控制。</p>
<ul>
<li><code>{{-</code> ：修剪<strong>左侧</strong>紧邻文本的尾部空白</li>
<li><code>-}}</code> ：修剪<strong>右侧</strong>紧邻文本的前导空白</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 不修剪时，range 循环会引入多余换行</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">range</span><span class="w"> </span><span class="p">.</span><span class="nx">Items</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{.}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 双向修剪，保持紧凑输出</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="p">.</span><span class="nx">Items</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{.}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>注意：<code>{{- 3}}</code> 修剪左侧空白后解析为数字 <code>3</code>；而 <code>{{-3}}</code> 会被解析为<strong>负数 -3</strong> 的操作。修剪符后的空格必须存在。</p>
<h2 id="三自定义函数">三、自定义函数</h2>
<p><code>template.FuncMap</code> 用于将 Go 函数注册到模板命名空间中。<strong>必须先调用 <code>Funcs()</code> 注册函数，再调用 <code>Parse()</code></strong>，否则 AST 中出现的未知标识符会导致 Parse 失败。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">funcMap</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nx">FuncMap</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;upper&#34;</span><span class="p">:</span><span class="w">      </span><span class="nx">strings</span><span class="p">.</span><span class="nx">ToUpper</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;formatDate&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="p">)</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">t</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">&#34;2006-01-02&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;safeHTML&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nx">HTML</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">HTML</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w">  </span><span class="c1">// 慎重使用！</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;example&#34;</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Funcs</span><span class="p">(</span><span class="nx">funcMap</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Parse</span><span class="p">(</span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="na">.User</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">upper</span><span class="w"> </span><span class="cp">}}</span><span class="s"> - </span><span class="cp">{{</span><span class="w"> </span><span class="na">.Time</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">formatDate</span><span class="w"> </span><span class="cp">}}</span><span class="s">`</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>自定义函数应只做<strong>展示层的纯格式化或轻量映射</strong>（日期格式、单位转换、字符串处理）。复杂分支、聚合和权限决策应放在 Go 代码中，不要塞入模板。</p>
<h2 id="四嵌套模板与布局">四、嵌套模板与布局</h2>
<h3 id="41-define--template--block">4.1 define / template / block</h3>
<p><code>{{define &quot;name&quot;}}</code> 定义命名模板，<code>{{template &quot;name&quot; .}}</code> 调用它并传入数据。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">layout</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">define</span><span class="w"> </span><span class="s">&#34;base&#34;</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;head&gt;&lt;title&gt;</span><span class="cp">{{</span><span class="na">.Title</span><span class="cp">}}</span><span class="s">&lt;/title&gt;&lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;body&gt;</span><span class="cp">{{</span><span class="k">template</span><span class="w"> </span><span class="s">&#34;content&#34;</span><span class="w"> </span><span class="na">.</span><span class="cp">}}</span><span class="s">&lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/html&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">define</span><span class="w"> </span><span class="s">&#34;content&#34;</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;h1&gt;</span><span class="cp">{{</span><span class="na">.Heading</span><span class="cp">}}</span><span class="s">&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;p&gt;</span><span class="cp">{{</span><span class="na">.Body</span><span class="cp">}}</span><span class="s">&lt;/p&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>加载顺序很重要</strong>：Go 模板引擎按加载顺序查找模板，如果多个文件定义了同名模板，<strong>只有第一个被使用</strong>。因此不同页面的内容模板应使用不同的名称。</p>
<h3 id="42-block-用于可覆盖的默认实现">4.2 block 用于可覆盖的默认实现</h3>
<p><code>{{block &quot;name&quot; .}}...{{end}}</code> 等价于“定义默认模板 + 调用它”。子模板可以通过 <code>{{define &quot;name&quot;}}</code> 覆盖默认实现。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 主模板</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">master</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`Names:</span><span class="cp">{{</span><span class="k">block</span><span class="w"> </span><span class="s">&#34;list&#34;</span><span class="w"> </span><span class="na">.</span><span class="cp">}}{{</span><span class="k">range</span><span class="w"> </span><span class="na">.</span><span class="cp">}}{{</span><span class="k">println</span><span class="w"> </span><span class="s">&#34;-&#34;</span><span class="w"> </span><span class="na">.</span><span class="cp">}}{{</span><span class="k">end</span><span class="cp">}}{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 子模板覆盖</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">overlay</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="k">define</span><span class="w"> </span><span class="s">&#34;list&#34;</span><span class="cp">}}{{</span><span class="nx">join</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="s">&#34;, &#34;</span><span class="cp">}}{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>调用 <code>masterTmpl.Execute</code> 输出默认列表；克隆后解析 overlay 再执行，则输出用逗号连接的结果。</p>
<h3 id="43-gin-框架中的嵌套模板">4.3 Gin 框架中的嵌套模板</h3>
<p>在 Gin 中，通常用 <code>LoadHTMLGlob</code> 加载所有模板，配合 <code>{{define}}</code> 组织布局：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">gin</span><span class="p">.</span><span class="nf">Default</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">r</span><span class="p">.</span><span class="nf">LoadHTMLGlob</span><span class="p">(</span><span class="s">&#34;templates/**/*&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// templates/layout/base.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// {{define &#34;base&#34;}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// &lt;html&gt;&lt;head&gt;&lt;title&gt;{{.title}}&lt;/title&gt;&lt;/head&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// &lt;body&gt;{{template .content .}}&lt;/body&gt;&lt;/html&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// {{end}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// templates/pages/home.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// {{define &#34;home-content&#34;}}&lt;p&gt;首页内容&lt;/p&gt;{{end}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 处理器中</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">c</span><span class="p">.</span><span class="nf">HTML</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;base&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">gin</span><span class="p">.</span><span class="nx">H</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;title&#34;</span><span class="p">:</span><span class="w">   </span><span class="s">&#34;Home&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;content&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;home-content&#34;</span><span class="p">,</span><span class="w">  </span><span class="c1">// 动态指定要渲染的子模板名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>如果子模板名称是动态传入的变量，标准的 <code>{{template .content .}}</code> 语法不支持变量作为模板名，需要通过自定义函数（如示例中的 <code>DynamicTemplate</code>）来间接实现。</p>
<h2 id="五html-安全htmltemplate-的自动转义">五、HTML 安全：html/template 的自动转义</h2>
<p><code>html/template</code> 与 <code>text/template</code> 共享 API，但增加了<strong>上下文感知的自动转义</strong>。这是 Go 模板最重要的安全特性。</p>
<h3 id="51-自动转义演示">5.1 自动转义演示</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// text/template：直接输出，产生 XSS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;foo&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">`Hello, </span><span class="cp">{{</span><span class="na">.</span><span class="cp">}}</span><span class="s">!`</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 输出：Hello, &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;!</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// html/template：自动转义，安全</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">htmltemplate</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;foo&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">`Hello, </span><span class="cp">{{</span><span class="na">.</span><span class="cp">}}</span><span class="s">!`</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 输出：Hello, &amp;lt;script&amp;gt;alert(&#39;xss&#39;)&amp;lt;/script&amp;gt;!</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>转义是<strong>上下文感知</strong>的：同一个 <code>{{.}}</code> 出现在 HTML 文本、属性、JavaScript、CSS、URL 中时，会应用不同的转义规则。例如：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/search?q={{.}}&#34;</span><span class="p">&gt;</span>{{.}}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- 模板引擎在解析时自动改写为： --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/search?q={{. | urlescaper | attrescaper}}&#34;</span><span class="p">&gt;</span>{{. | htmlescaper}}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="52-安全模型">5.2 安全模型</h3>
<p><code>html/template</code> 的安全模型假设<strong>模板作者是可信的，而 Execute 传入的数据不可信</strong>。不应在模板中直接拼接用户输入。如果确实需要输出可信的 HTML 片段，使用 <code>template.HTML</code> 类型显式标记，但这<strong>绕过了自动转义</strong>，必须确保内容来源完全可控，绝不可用于用户输入。</p>
<h2 id="六性能优化">六、性能优化</h2>
<h3 id="61-核心原则启动时解析请求时只执行">6.1 核心原则：启动时解析，请求时只执行</h3>
<p><code>Parse</code> 包含 I/O 和编译成本，<strong>禁止在每个 HTTP 请求中调用 Parse</strong>。应在 <code>init()</code> 或程序启动阶段解析一次，请求路径只做 <code>Execute</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ✅ 正确：包级别初始化</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">reportTmpl</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;templates/report.html&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">handler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">reportTmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w">  </span><span class="c1">// 只执行，不解析</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="62-并发安全">6.2 并发安全</h3>
<p>模板一旦解析完成，<code>Execute</code> 可以安全地并发调用。但如果多个 goroutine 共享同一个 <code>io.Writer</code>，输出可能交错。每个请求应使用独立的 <code>Writer</code>（如 <code>http.ResponseWriter</code>）。</p>
<h3 id="63-需要不同数据隔离时使用-clone">6.3 需要不同数据隔离时使用 Clone</h3>
<p>如果需要在同一套模板基础上为不同请求做数据隔离的变体，使用 <code>Clone()</code> 创建副本。</p>
<h3 id="64-避免不必要的转义开销">6.4 避免不必要的转义开销</h3>
<p><code>html/template</code> 的自动转义会带来一定的性能开销。如果输出的是纯文本（如生成代码、配置文件），使用 <code>text/template</code> 可以避免转义成本。只有在生成 HTML 时才应使用 <code>html/template</code>。</p>
<h2 id="七测试策略">七、测试策略</h2>
<p>模板测试的核心是<strong>构造数据 → 执行 → 断言输出</strong>。推荐的模式是表驱动测试。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">TestLetterTemplate</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">const</span><span class="w"> </span><span class="nx">letter</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`Dear </span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">,</span><span class="cp">{{</span><span class="k">if</span><span class="w"> </span><span class="na">.Attended</span><span class="cp">}}</span><span class="s"> 感谢您的到来。</span><span class="cp">{{</span><span class="k">else</span><span class="cp">}}</span><span class="s"> 很遗憾您未能出席。</span><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">tests</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[]</span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">name</span><span class="w">     </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">data</span><span class="w">     </span><span class="nx">Recipient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">contains</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">name</span><span class="p">:</span><span class="w">     </span><span class="s">&#34;attended&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">data</span><span class="p">:</span><span class="w">     </span><span class="nx">Recipient</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Mildred&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Attended</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">contains</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;感谢您的到来&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">name</span><span class="p">:</span><span class="w">     </span><span class="s">&#34;not attended&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">data</span><span class="p">:</span><span class="w">     </span><span class="nx">Recipient</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;John&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Attended</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">contains</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;很遗憾您未能出席&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">tt</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">tests</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">t</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="nx">tt</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;letter&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">letter</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">var</span><span class="w"> </span><span class="nx">buf</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">buf</span><span class="p">,</span><span class="w"> </span><span class="nx">tt</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">strings</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nx">buf</span><span class="p">.</span><span class="nf">String</span><span class="p">(),</span><span class="w"> </span><span class="nx">tt</span><span class="p">.</span><span class="nx">contains</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;output %q does not contain %q&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">buf</span><span class="p">.</span><span class="nf">String</span><span class="p">(),</span><span class="w"> </span><span class="nx">tt</span><span class="p">.</span><span class="nx">contains</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>对于 HTML 模板，还应测试<strong>特殊字符是否被正确转义</strong>，以及子模板的嵌套执行是否正确。</p>
<h2 id="八实战完整-http-服务示例">八、实战：完整 HTTP 服务示例</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;html/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">User</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="w">      </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Age</span><span class="w">       </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">JoinedAt</span><span class="w">  </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">IsPremium</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">tmpl</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;user&#34;</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Funcs</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nx">FuncMap</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;formatDate&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="p">)</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="nx">t</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">&#34;2006-01-02&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Parse</span><span class="p">(</span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;head&gt;&lt;meta charset=&#34;UTF-8&#34;&gt;&lt;title&gt;用户信息&lt;/title&gt;&lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;h1&gt;</span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;p&gt;年龄：</span><span class="cp">{{</span><span class="na">.Age</span><span class="cp">}}</span><span class="s">&lt;/p&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;p&gt;注册日期：</span><span class="cp">{{</span><span class="na">.JoinedAt</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">formatDate</span><span class="cp">}}</span><span class="s">&lt;/p&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">if</span><span class="w"> </span><span class="na">.IsPremium</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    &lt;span class=&#34;badge&#34;&gt;尊贵会员&lt;/span&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">else</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    &lt;span&gt;普通用户&lt;/span&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/html&gt;`</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">userHandler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">user</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">User</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">Name</span><span class="p">:</span><span class="w">      </span><span class="s">&#34;张三&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">Age</span><span class="p">:</span><span class="w">       </span><span class="mi">28</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">JoinedAt</span><span class="p">:</span><span class="w">  </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">IsPremium</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">user</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">(),</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">userHandler</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">&#34;:8080&#34;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>模板在包级别解析一次，请求路径只执行 <code>Execute</code>；自定义 <code>formatDate</code> 函数在 <code>Parse</code> 之前通过 <code>Funcs</code> 注册；<code>html/template</code> 自动对 <code>{{.Name}}</code> 等内容进行 HTML 转义。</p>
<h2 id="九最佳实践总结">九、最佳实践总结</h2>
<p><strong>解析时机</strong>：模板在启动时解析一次，绝不在请求路径中重复解析。使用 <code>template.Must</code> 在初始化阶段暴露解析错误。</p>
<p><strong>函数注册顺序</strong>：<code>Funcs()</code> 必须在 <code>Parse()</code> 之前调用，否则解析阶段就会失败。</p>
<p><strong>选择正确的包</strong>：生成 HTML 用 <code>html/template</code>；生成代码、配置、纯文本用 <code>text/template</code>。绝不能用 <code>text/template</code> 渲染用户可见的 HTML 页面。</p>
<p><strong>安全边界</strong>：将 <code>template.HTML</code>、<code>template.JS</code>、<code>template.CSS</code> 视为“危险类型”。它们绕过自动转义，只应用于来源完全可控的内容，严禁包裹用户输入。</p>
<p><strong>逻辑归属</strong>：模板只负责展示。复杂条件分支、数据聚合、权限判断放在 Go 代码中完成，模板接收的是“已经准备好的展示数据”。</p>
<p><strong>模板命名</strong>：嵌套模板中不同页面的内容块使用<strong>唯一名称</strong>，避免加载顺序导致同名模板被覆盖的隐蔽 bug。</p>
<p><strong>空白控制</strong>：生成结构化文本（代码、JSON、YAML）时，在 <code>range</code>、<code>if</code>、<code>end</code> 动作上使用 <code>-</code> 修剪符精确控制输出格式，避免多余换行破坏结构。</p>
<p><strong>并发写入</strong>：多 goroutine 并发执行同一模板时，为每个执行者提供独立的 <code>io.Writer</code>，避免输出交错。</p>
]]></content:encoded></item><item><title>Goroutine sync.Mutex 互斥锁详解</title><link>https://blog.dart.xin/golang/go106-mutex/</link><pubDate>Tue, 13 Oct 2026 09:49:26 +0800</pubDate><guid>https://blog.dart.xin/golang/go106-mutex/</guid><description>Mutex是互斥锁，用来保护共享资源，防止多个 goroutine 同时读写导致数据竞争</description><content:encoded><![CDATA[<h1 id="go-语言-syncmutex-互斥锁详解">Go 语言 <code>sync.Mutex</code> 互斥锁详解</h1>
<p><code>sync.Mutex</code> 是 Go 标准库 <code>sync</code> 包提供的<strong>互斥锁</strong>，用来保护共享资源，防止多个 goroutine 同时读写导致数据竞争。</p>
<hr>
<h2 id="一为什么需要互斥锁">一、为什么需要互斥锁？</h2>
<p>先看一个没有锁的经典问题：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">counter</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">counter</span><span class="o">++</span><span class="w">   </span><span class="c1">// ⚠️ 数据竞争</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;counter =&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">counter</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>多次运行，输出经常<strong>小于 1000</strong>，比如 987、993、1000 不固定。原因：</p>
<ul>
<li><code>counter++</code> 不是原子操作，实际是三步：读值 → 加一 → 写回</li>
<li>多个 goroutine 同时执行时，可能两个都读到 5，都写回 6，结果少加了一次</li>
</ul>
<p>用 <code>go run -race main.go</code> 会明确报出 <code>DATA RACE</code>。</p>
<p><strong>加锁就能解决：</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">counter</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>多次运行都稳定输出 <code>counter = 1000</code>。</p>
<hr>
<h2 id="二基本用法">二、基本用法</h2>
<p><code>sync.Mutex</code> 只有两个方法：</p>
<table>
	<thead>
			<tr>
					<th style="text-align: left">方法</th>
					<th style="text-align: left">作用</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><code>Lock()</code></td>
					<td style="text-align: left">加锁，若已被其他 goroutine 持有则阻塞等待</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>Unlock()</code></td>
					<td style="text-align: left">解锁，唤醒一个正在等待的 goroutine</td>
			</tr>
	</tbody>
</table>
<p><strong>必须成对出现</strong>，<code>Lock</code> 后一定要 <code>Unlock</code>，否则其他 goroutine 会永久阻塞（死锁）。</p>
<p><strong>推荐写法：<code>defer</code> 解锁</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ... 临界区代码</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>即使临界区里 <code>panic</code> 或 <code>return</code>，<code>defer</code> 也能保证解锁，避免死锁。</p>
<p><strong><code>sync.Mutex</code> 零值可用</strong>，不需要显式初始化：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">   </span><span class="c1">// ✅ 直接可用</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="三典型示例并发安全的计数器">三、典型示例：并发安全的计数器</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">SafeCounter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="w">  </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">SafeCounter</span><span class="p">)</span><span class="w"> </span><span class="nf">Inc</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">SafeCounter</span><span class="p">)</span><span class="w"> </span><span class="nf">Value</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">SafeCounter</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">c</span><span class="p">.</span><span class="nf">Inc</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;counter =&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">Value</span><span class="p">())</span><span class="w">   </span><span class="c1">// 稳定输出 1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>要点</strong>：</p>
<ul>
<li>锁和它保护的数据放在同一个结构体里，是 Go 的惯用法</li>
<li>方法内部加锁，调用者无需关心锁的存在</li>
<li>通过指针接收者调用，避免复制结构体（也就避免了复制锁）</li>
</ul>
<hr>
<h2 id="四回到你的项目日志写入">四、回到你的项目：日志写入</h2>
<p>你的 <code>internal/logger/logger.go</code> 里已经用到了这个模式：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">logDir</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w">     </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">Write</span><span class="p">(</span><span class="nx">entry</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">filename</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;http_%s.log&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">().</span><span class="nf">Format</span><span class="p">(</span><span class="s">&#34;2006-01-02&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">path</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">filepath</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="nx">logDir</span><span class="p">,</span><span class="w"> </span><span class="nx">filename</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">f</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">OpenFile</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">O_APPEND</span><span class="p">|</span><span class="nx">os</span><span class="p">.</span><span class="nx">O_CREATE</span><span class="p">|</span><span class="nx">os</span><span class="p">.</span><span class="nx">O_WRONLY</span><span class="p">,</span><span class="w"> </span><span class="mo">0644</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;打开日志文件失败: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">f</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">f</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="nx">entry</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>为什么这里需要锁？</p>
<ul>
<li>如果你以后改成<strong>并发调用多个 LLM</strong>（比如同时跑 5 个 Prompt 做对比），多个 goroutine 会同时调用 <code>logger.Write</code></li>
<li>没有锁的话，两条日志内容可能在文件里<strong>交错</strong>，比如：</li>
</ul>
<pre tabindex="0"><code>[请求 URL] https://api.openai.com/v1/[请求方法] POST
</code></pre><ul>
<li>加锁后，一次只允许一个 goroutine 写文件，日志内容完整</li>
</ul>
<p><strong>注意</strong>：<code>os.File</code> 的 <code>Write</code> 本身在 Linux 上是原子的，但我们的日志入口做了&quot;打开文件 → 写入 → 关闭&quot;三步，中间可能被打断，所以锁是必要的。</p>
<hr>
<h2 id="五常见错误">五、常见错误</h2>
<h3 id="错误-1忘记解锁">错误 1：忘记解锁</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">   </span><span class="c1">// ❌ 忘了 Unlock，其他 goroutine 永远阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>修正</strong>：始终用 <code>defer mu.Unlock()</code> 紧跟 <code>Lock()</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="错误-2复制包含锁的结构体">错误 2：复制包含锁的结构体</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Counter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="w">  </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="nx">Counter</span><span class="p">)</span><span class="w"> </span><span class="nf">Inc</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">   </span><span class="c1">// ❌ 值接收者，复制了锁</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>值接收者会<strong>复制整个结构体</strong>，包括 <code>Mutex</code>。复制后的锁是另一个锁，保护不了原来的数据。</p>
<p><strong>修正</strong>：用指针接收者。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Counter</span><span class="p">)</span><span class="w"> </span><span class="nf">Inc</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">   </span><span class="c1">// ✅ 指针接收者</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="错误-3重入死锁">错误 3：重入死锁</h3>
<p><code>sync.Mutex</code> <strong>不可重入</strong>。同一个 goroutine 已经持有锁，再次 <code>Lock</code> 会永久阻塞。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">SafeCounter</span><span class="p">)</span><span class="w"> </span><span class="nf">A</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nf">B</span><span class="p">()</span><span class="w">   </span><span class="c1">// ❌ B 里又 Lock，死锁</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">SafeCounter</span><span class="p">)</span><span class="w"> </span><span class="nf">B</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">   </span><span class="c1">// 阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>修正</strong>：拆分内部方法，不要在持有锁的情况下调用会再次加锁的方法。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">SafeCounter</span><span class="p">)</span><span class="w"> </span><span class="nf">A</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nf">bLocked</span><span class="p">()</span><span class="w">   </span><span class="c1">// ✅ 内部方法，不再加锁</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">SafeCounter</span><span class="p">)</span><span class="w"> </span><span class="nf">bLocked</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 不加锁，假定调用者已持有锁</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="错误-4锁的粒度过大">错误 4：锁的粒度过大</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">fetchFromNetwork</span><span class="p">()</span><span class="w">     </span><span class="c1">// ❌ 网络请求耗时 2 秒</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">cache</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">data</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>锁里做耗时操作（网络、磁盘、大计算），会让其他 goroutine 长时间等待，性能急剧下降。</p>
<p><strong>修正</strong>：只在<strong>真正操作共享数据</strong>时加锁。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">data</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">fetchFromNetwork</span><span class="p">()</span><span class="w">     </span><span class="c1">// ✅ 网络请求不加锁</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">cache</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="六进阶syncrwmutex-读写锁">六、进阶：<code>sync.RWMutex</code> 读写锁</h2>
<p>如果读多写少，用 <code>sync.RWMutex</code> 性能更好：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Cache</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">RWMutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="w">  </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Cache</span><span class="p">)</span><span class="w"> </span><span class="nf">Get</span><span class="p">(</span><span class="nx">key</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RLock</span><span class="p">()</span><span class="w">          </span><span class="c1">// 读锁，多个 goroutine 可同时持有</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RUnlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Cache</span><span class="p">)</span><span class="w"> </span><span class="nf">Set</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">           </span><span class="c1">// 写锁，独占</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><table>
	<thead>
			<tr>
					<th style="text-align: left">锁类型</th>
					<th style="text-align: left">方法</th>
					<th style="text-align: left">并发性</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left">读锁</td>
					<td style="text-align: left"><code>RLock</code> / <code>RUnlock</code></td>
					<td style="text-align: left">多个读可并行</td>
			</tr>
			<tr>
					<td style="text-align: left">写锁</td>
					<td style="text-align: left"><code>Lock</code> / <code>Unlock</code></td>
					<td style="text-align: left">独占，读写互斥</td>
			</tr>
	</tbody>
</table>
<p><strong>使用场景</strong>：</p>
<ul>
<li><strong>读多写少</strong>（如缓存）→ <code>RWMutex</code></li>
<li><strong>读写均衡或写多</strong> → <code>Mutex</code></li>
</ul>
<p>不要为了&quot;看起来更快&quot;而盲目用 <code>RWMutex</code>，它的内部实现比 <code>Mutex</code> 复杂，写多时反而更慢。</p>
<hr>
<h2 id="七进阶defer-的代价">七、进阶：<code>defer</code> 的代价</h2>
<p><code>defer</code> 有轻微开销。如果在<strong>极端热路径</strong>（每秒调用数百万次），可以手动解锁：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>但大多数场景下，<strong>可读性和安全性比微小性能更重要</strong>，推荐用 <code>defer</code>。</p>
<hr>
<h2 id="八如何检测数据竞争">八、如何检测数据竞争</h2>
<p>Go 内置了 race detector：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">go run -race main.go
</span></span><span class="line"><span class="cl">go <span class="nb">test</span> -race ./...
</span></span><span class="line"><span class="cl">go build -race
</span></span></code></pre></td></tr></table>
</div>
</div><p>只要有数据竞争，运行时会打印：</p>
<pre tabindex="0"><code>==================
WARNING: DATA RACE
Write at 0x00c000014178 by goroutine 7:
  main.main.func1()
      main.go:15 +0x3c
...
==================
</code></pre><p><strong>开发阶段建议始终带 <code>-race</code></strong>，发现问题远比线上诡异 bug 好排查。</p>
<hr>
<h2 id="九什么时候不需要-mutex">九、什么时候不需要 <code>Mutex</code>？</h2>
<p>不是所有并发场景都要加锁，以下情况可以用更轻量的方案：</p>
<table>
	<thead>
			<tr>
					<th style="text-align: left">场景</th>
					<th style="text-align: left">方案</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left">单个整数/指针的原子读写</td>
					<td style="text-align: left"><code>sync/atomic</code></td>
			</tr>
			<tr>
					<td style="text-align: left">只需要执行一次</td>
					<td style="text-align: left"><code>sync.Once</code></td>
			</tr>
			<tr>
					<td style="text-align: left">等待一组 goroutine 完成</td>
					<td style="text-align: left"><code>sync.WaitGroup</code></td>
			</tr>
			<tr>
					<td style="text-align: left">只读共享数据（初始化后不再改）</td>
					<td style="text-align: left">无需加锁</td>
			</tr>
			<tr>
					<td style="text-align: left">goroutine 间通信</td>
					<td style="text-align: left"><code>channel</code></td>
			</tr>
			<tr>
					<td style="text-align: left">简单的 map 并发访问</td>
					<td style="text-align: left"><code>sync.Map</code>（特定场景）</td>
			</tr>
	</tbody>
</table>
<p><strong>Go 的哲学</strong>：不要通过共享内存来通信，而要通过通信来共享内存。</p>
<p>如果能用 <code>channel</code> 优雅解决，就优先用 <code>channel</code>；<code>Mutex</code> 适合<strong>保护一块明确的小临界区</strong>。</p>
<hr>
<h2 id="十一个完整可运行的例子">十、一个完整可运行的例子</h2>
<p>综合演示：多 goroutine 并发写入一个 <code>map</code>，用 <code>Mutex</code> 保护。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Counter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="w">  </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">NewCounter</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="nx">Counter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">Counter</span><span class="p">{</span><span class="nx">m</span><span class="p">:</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">)}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Counter</span><span class="p">)</span><span class="w"> </span><span class="nf">Add</span><span class="p">(</span><span class="nx">key</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Counter</span><span class="p">)</span><span class="w"> </span><span class="nf">Snapshot</span><span class="p">()</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">snapshot</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">snapshot</span><span class="p">[</span><span class="nx">k</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">v</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">snapshot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">NewCounter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">words</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;go&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;rust&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;go&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;python&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;go&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;rust&#34;</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">words</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">word</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">c</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="nx">word</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}(</span><span class="nx">w</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">Snapshot</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;%-8s %d\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>输出：</p>
<pre tabindex="0"><code>go       3000
rust     2000
python   1000
</code></pre><p>用 <code>go run -race main.go</code> 运行也不会有任何警告。</p>
<hr>
<h2 id="十一一句话总结">十一、一句话总结</h2>
<blockquote>
<p><strong><code>sync.Mutex</code> 是保护共享数据的最小工具：<code>Lock</code> 进入临界区，<code>defer Unlock</code> 保证退出；锁和数据放在同一个结构体里，用指针接收者操作；临界区要尽量小；开发时始终带 <code>-race</code>。</strong></p>
</blockquote>
<p>在你的 Prompt 工程里，日志写入、token 统计累加、多会话管理这些都是典型的加锁场景。目前的 <code>logger.Write</code> 已经用对了模式，可以直接参考。</p>
]]></content:encoded></item><item><title>Goroutine Context 详细讲解</title><link>https://blog.dart.xin/golang/go105-context/</link><pubDate>Mon, 12 Oct 2026 11:21:07 +0800</pubDate><guid>https://blog.dart.xin/golang/go105-context/</guid><description>&lt;h1 id="go-context-详细讲解"&gt;Go Context 详细讲解&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;context&lt;/code&gt; 是 Go 官方用来&lt;strong&gt;在 goroutine 之间传递取消信号、超时、截止时间和请求范围数据&lt;/strong&gt;的标准库。它解决了并发编程中的核心问题：&lt;strong&gt;如何优雅地通知一组 goroutine 停止工作&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一为什么需要-context"&gt;一、为什么需要 Context&lt;/h2&gt;
&lt;h3 id="11-没有-context-的痛点"&gt;1.1 没有 Context 的痛点&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 假设一个 HTTP 请求启动了 3 个 goroutine 处理子任务&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fetchFromDB&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 用户断开连接，它还在跑&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;callExternalAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 超时了，它还在跑&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;writeToCache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 没人需要结果了，它还在跑&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户取消请求，goroutine 收不到通知，继续浪费资源&lt;/li&gt;
&lt;li&gt;无法设置统一超时&lt;/li&gt;
&lt;li&gt;无法在调用链中传递请求 ID、用户身份等数据&lt;/li&gt;
&lt;li&gt;goroutine 泄漏&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="12-context-的设计哲学"&gt;1.2 Context 的设计哲学&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;一个请求，一棵 goroutine 树，一个 Context。&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Context 是&lt;strong&gt;树形结构&lt;/strong&gt;，根节点是 &lt;code&gt;context.Background()&lt;/code&gt; 或 &lt;code&gt;context.TODO()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;父 Context 取消 → &lt;strong&gt;所有子 Context 全部取消&lt;/strong&gt;（级联）&lt;/li&gt;
&lt;li&gt;Context 是&lt;strong&gt;并发安全&lt;/strong&gt;的，可被多个 goroutine 同时使用&lt;/li&gt;
&lt;li&gt;Context 应该作为&lt;strong&gt;函数的第一个参数&lt;/strong&gt;，命名为 &lt;code&gt;ctx&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二context-接口"&gt;二、Context 接口&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 返回 deadline 和是否设置了 deadline&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Deadline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 返回一个 channel，在 Context 被取消时关闭&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 返回取消原因（在 Done 关闭后才有意义）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 获取 key 对应的值&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键点：&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h1 id="go-context-详细讲解">Go Context 详细讲解</h1>
<p><code>context</code> 是 Go 官方用来<strong>在 goroutine 之间传递取消信号、超时、截止时间和请求范围数据</strong>的标准库。它解决了并发编程中的核心问题：<strong>如何优雅地通知一组 goroutine 停止工作</strong>。</p>
<hr>
<h2 id="一为什么需要-context">一、为什么需要 Context</h2>
<h3 id="11-没有-context-的痛点">1.1 没有 Context 的痛点</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 假设一个 HTTP 请求启动了 3 个 goroutine 处理子任务</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">handler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">fetchFromDB</span><span class="p">()</span><span class="w">      </span><span class="c1">// 用户断开连接，它还在跑</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">callExternalAPI</span><span class="p">()</span><span class="w">  </span><span class="c1">// 超时了，它还在跑</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">writeToCache</span><span class="p">()</span><span class="w">     </span><span class="c1">// 没人需要结果了，它还在跑</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>问题：</p>
<ul>
<li>用户取消请求，goroutine 收不到通知，继续浪费资源</li>
<li>无法设置统一超时</li>
<li>无法在调用链中传递请求 ID、用户身份等数据</li>
<li>goroutine 泄漏</li>
</ul>
<h3 id="12-context-的设计哲学">1.2 Context 的设计哲学</h3>
<blockquote>
<p><strong>&ldquo;一个请求，一棵 goroutine 树，一个 Context。&rdquo;</strong></p>
</blockquote>
<ul>
<li>Context 是<strong>树形结构</strong>，根节点是 <code>context.Background()</code> 或 <code>context.TODO()</code></li>
<li>父 Context 取消 → <strong>所有子 Context 全部取消</strong>（级联）</li>
<li>Context 是<strong>并发安全</strong>的，可被多个 goroutine 同时使用</li>
<li>Context 应该作为<strong>函数的第一个参数</strong>，命名为 <code>ctx</code></li>
</ul>
<hr>
<h2 id="二context-接口">二、Context 接口</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Context</span><span class="w"> </span><span class="kd">interface</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 返回 deadline 和是否设置了 deadline</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">Deadline</span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="nx">deadline</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 返回一个 channel，在 Context 被取消时关闭</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">Done</span><span class="p">()</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 返回取消原因（在 Done 关闭后才有意义）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">Err</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 获取 key 对应的值</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">Value</span><span class="p">(</span><span class="nx">key</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>关键点：</strong></p>
<ul>
<li><code>Done()</code> 返回的 channel 在取消/超时时<strong>关闭</strong></li>
<li><code>Err()</code> 返回 <code>context.Canceled</code> 或 <code>context.DeadlineExceeded</code></li>
<li>没有取消时，<code>Done()</code> 返回 <code>nil</code>（会永久阻塞）</li>
</ul>
<hr>
<h2 id="三四种创建方式">三、四种创建方式</h2>
<h3 id="31-根-context">3.1 根 Context</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">()</span><span class="w"> </span><span class="c1">// 通常用于 main、初始化、测试</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">()</span><span class="w">       </span><span class="c1">// 不确定用哪个时，占位</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>两者都是空的、永不取消的 Context。<strong>生产代码中只用 Background，TODO 只是标记待办。</strong></p>
<h3 id="32-withcancel手动取消">3.2 WithCancel：手动取消</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">parentCtx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w"> </span><span class="c1">// 必须调用，否则泄漏</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="33-withtimeout超时取消">3.3 WithTimeout：超时取消</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">parentCtx</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="34-withdeadline到某时刻取消">3.4 WithDeadline：到某时刻取消</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithDeadline</span><span class="p">(</span><span class="nx">parentCtx</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="35-withvalue携带数据">3.5 WithValue：携带数据</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">parentCtx</span><span class="p">,</span><span class="w"> </span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="nx">value</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="四基础示例">四、基础示例</h2>
<h3 id="41-withcancel-基本用法">4.1 WithCancel 基本用法</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;worker %d 收到取消信号: %v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;worker %d 工作中...\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;main: 发送取消信号&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">cancel</span><span class="p">()</span><span class="w"> </span><span class="c1">// 通知所有子 goroutine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;main: 退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>输出：</strong></p>
<pre tabindex="0"><code>worker 1 工作中...
worker 2 工作中...
worker 1 工作中...
worker 2 工作中...
main: 发送取消信号
worker 1 收到取消信号: context canceled
worker 2 收到取消信号: context canceled
main: 退出
</code></pre><hr>
<h3 id="42-withtimeout-超时控制">4.2 WithTimeout 超时控制</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">slowOperation</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">3</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;操作完成&#34;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="mi">2</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">slowOperation</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;失败:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"> </span><span class="c1">// 失败: context deadline exceeded</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;成功:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">result</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h3 id="43-级联取消树形结构">4.3 级联取消（树形结构）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">root</span><span class="p">,</span><span class="w"> </span><span class="nx">cancelRoot</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancelRoot</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">child1</span><span class="p">,</span><span class="w"> </span><span class="nx">cancelChild1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">root</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancelChild1</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">child2</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">root</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">&lt;-</span><span class="nx">child1</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;child1 被取消:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">child1</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">&lt;-</span><span class="nx">child2</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;child2 被取消:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">child2</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;取消 root&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">cancelRoot</span><span class="p">()</span><span class="w"> </span><span class="c1">// 级联取消 child1 和 child2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">200</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>输出：</strong></p>
<pre tabindex="0"><code>取消 root
child1 被取消: context canceled
child2 被取消: context canceled
</code></pre><blockquote>
<p><strong>规则</strong>：取消父节点 → 所有子节点自动取消；取消子节点 → 不影响父节点。</p>
</blockquote>
<hr>
<h2 id="五withvalue-传值">五、WithValue 传值</h2>
<h3 id="51-基本用法">5.1 基本用法</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// key 必须是不导出的自定义类型，避免冲突</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">contextKey</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">userIDKey</span><span class="w">    </span><span class="nx">contextKey</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;userID&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">requestIDKey</span><span class="w"> </span><span class="nx">contextKey</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;requestID&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">handleRequest</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">userID</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Value</span><span class="p">(</span><span class="nx">userIDKey</span><span class="p">).(</span><span class="kt">string</span><span class="p">);</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;用户:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">userID</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">reqID</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Value</span><span class="p">(</span><span class="nx">requestIDKey</span><span class="p">).(</span><span class="kt">string</span><span class="p">);</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;请求 ID:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">reqID</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">userIDKey</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;user-123&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">requestIDKey</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;req-456&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">handleRequest</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>输出：</strong></p>
<pre tabindex="0"><code>用户: user-123
请求 ID: req-456
</code></pre><h3 id="52-为什么-key-不能用-string">5.2 为什么 key 不能用 string？</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ 错误：不同包可能用相同 string 键冲突</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;userID&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;123&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ✅ 正确：用自定义类型</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">ctxKey</span><span class="w"> </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">userIDKey</span><span class="w"> </span><span class="nx">ctxKey</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">userIDKey</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;123&#34;</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="53-withvalue-的适用场景">5.3 WithValue 的适用场景</h3>
<p><strong>✅ 适合：</strong></p>
<ul>
<li>请求 ID、trace ID（链路追踪）</li>
<li>用户身份、租户 ID</li>
<li>认证 token</li>
<li>跨 API 边界的请求范围元数据</li>
</ul>
<p><strong>❌ 不适合：</strong></p>
<ul>
<li>传递函数参数（应该显式传参）</li>
<li>传递可选参数</li>
<li>传递可变状态</li>
</ul>
<blockquote>
<p>官方原话：<em>&ldquo;Use context values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.&rdquo;</em></p>
</blockquote>
<hr>
<h2 id="六在-goroutine-中正确使用">六、在 goroutine 中正确使用</h2>
<h3 id="61-模式一worker-监听-ctx">6.1 模式一：worker 监听 ctx</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">jobs</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="nx">Job</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="nx">job</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nf">process</span><span class="p">(</span><span class="nx">job</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="62-模式二启动多个-goroutine-并统一取消">6.2 模式二：启动多个 goroutine 并统一取消</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;worker %d 退出\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">300</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}(</span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;所有 worker 已退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="63-模式三errgroup推荐">6.3 模式三：ErrGroup（推荐）</h3>
<p><code>golang.org/x/sync/errgroup</code> 结合了 WaitGroup 和 Context，任何 goroutine 出错会取消其他 goroutine：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;golang.org/x/sync/errgroup&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">g</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">errgroup</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">g</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">3</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">g</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;出错了&#34;</span><span class="p">)</span><span class="w"> </span><span class="c1">// 会取消其他 goroutine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">g</span><span class="p">.</span><span class="nf">Wait</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;errgroup 结束:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="七实战场景">七、实战场景</h2>
<h3 id="71-http-请求超时">7.1 HTTP 请求超时</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fetchURL</span><span class="p">(</span><span class="nx">url</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="mi">3</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewRequestWithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;GET&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">DefaultClient</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">body</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nf">ReadAll</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nb">string</span><span class="p">(</span><span class="nx">body</span><span class="p">),</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="72-http-server-中传递-context">7.2 HTTP Server 中传递 Context</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">handler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nf">Context</span><span class="p">()</span><span class="w"> </span><span class="c1">// HTTP 请求自带的 Context</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 用户断开连接时 ctx 会自动取消</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">doSomething</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">Is</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Canceled</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// 客户端断开</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">(),</span><span class="w"> </span><span class="mi">500</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">w</span><span class="p">.</span><span class="nf">Write</span><span class="p">([]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">result</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">doSomething</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">5</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;done&#34;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="73-数据库查询超时">7.3 数据库查询超时</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">queryDB</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">rows</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">db</span><span class="p">.</span><span class="nf">QueryContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;SELECT * FROM users&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">rows</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="74-链式调用取消">7.4 链式调用取消</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">serviceA</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nf">serviceB</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w"> </span><span class="c1">// 传递 ctx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">serviceB</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nf">serviceC</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">serviceC</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w"> </span><span class="c1">// 任意一层取消，全部返回</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="75-后台任务--优雅关闭">7.5 后台任务 + 优雅关闭</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;os/signal&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;syscall&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 监听系统信号</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sigCh</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Signal</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">signal</span><span class="p">.</span><span class="nf">Notify</span><span class="p">(</span><span class="nx">sigCh</span><span class="p">,</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">SIGINT</span><span class="p">,</span><span class="w"> </span><span class="nx">syscall</span><span class="p">.</span><span class="nx">SIGTERM</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">&lt;-</span><span class="nx">sigCh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;\n收到退出信号&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;服务已启动，Ctrl+C 退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;正在清理...&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;已退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="八常见陷阱">八、常见陷阱</h2>
<h3 id="81-忘记调用-cancelgoroutine-泄漏">8.1 忘记调用 cancel（goroutine 泄漏）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ 即使操作提前结束，Context 也不会释放</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">bad</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Hour</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">doSomething</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ✅</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">good</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Hour</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">doSomething</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p><strong>规则</strong>：永远 <code>defer cancel()</code>，即使不超时也要调用。</p>
</blockquote>
<h3 id="82-把-context-存到-struct-里">8.2 把 Context 存到 struct 里</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ Context 应该作为函数参数传递</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Service</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ✅</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Service</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="o">*</span><span class="nx">Service</span><span class="p">)</span><span class="w"> </span><span class="nf">Do</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">...</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="83-用-context-传普通参数">8.3 用 Context 传普通参数</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithValue</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;userID&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">123</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ✅ 显式传参</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">getUser</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">userID</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">User</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="84-在-select-中忽略-ctx">8.4 在 select 中忽略 ctx</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ 无法取消</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">job</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">process</span><span class="p">(</span><span class="nx">job</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ✅</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">job</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">process</span><span class="p">(</span><span class="nx">job</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="85-检查-ctxerr-的时机">8.5 检查 ctx.Err() 的时机</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 此时 ctx.Err() 才有值</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w"> </span><span class="c1">// context.Canceled 或 context.DeadlineExceeded</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="86-取消不会杀死-goroutine">8.6 取消不会杀死 goroutine</h3>
<p>Context 只是<strong>信号</strong>，需要 goroutine 主动检查 <code>ctx.Done()</code> 并退出：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="c1">// 必须主动返回</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// 注意：如果这里有阻塞操作，仍需该操作支持 ctx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="九context-底层结构">九、Context 底层结构</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">emptyCtx</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="c1">// Background / TODO</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">cancelCtx</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Context</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">mu</span><span class="w">       </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">done</span><span class="w">     </span><span class="nx">atomic</span><span class="p">.</span><span class="nx">Value</span><span class="w"> </span><span class="c1">// chan struct{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">children</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="nx">canceler</span><span class="p">]</span><span class="kd">struct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">err</span><span class="w">      </span><span class="kt">error</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">timerCtx</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">cancelCtx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">timer</span><span class="w">    </span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Timer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">deadline</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">valueCtx</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Context</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="nx">val</span><span class="w"> </span><span class="kt">any</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>关键机制：</strong></p>
<ul>
<li><code>cancelCtx</code> 维护<strong>子节点列表</strong>，取消时遍历通知所有子节点</li>
<li><code>timerCtx</code> 内嵌 <code>cancelCtx</code>，额外带 <code>timer</code> 和 <code>deadline</code></li>
<li><code>valueCtx</code> 是<strong>链表</strong>结构，<code>Value()</code> 沿链向上查找</li>
<li><code>Done()</code> 返回的 channel <strong>懒加载</strong>，只有在第一次调用时才创建</li>
</ul>
<hr>
<h2 id="十context-使用清单">十、Context 使用清单</h2>
<table>
	<thead>
			<tr>
					<th>规则</th>
					<th>说明</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>第一个参数</td>
					<td><code>func Do(ctx context.Context, ...)</code></td>
			</tr>
			<tr>
					<td>不要存 struct</td>
					<td>Context 作为参数传递</td>
			</tr>
			<tr>
					<td>命名 ctx</td>
					<td>参数统一叫 <code>ctx</code></td>
			</tr>
			<tr>
					<td>必 defer cancel</td>
					<td>即使提前返回也要释放</td>
			</tr>
			<tr>
					<td>nil 用 TODO</td>
					<td>不确定时用 <code>context.TODO()</code></td>
			</tr>
			<tr>
					<td>不放 nil</td>
					<td>不要传 nil Context</td>
			</tr>
			<tr>
					<td>key 用自定义类型</td>
					<td>避免包间冲突</td>
			</tr>
			<tr>
					<td>只放请求范围数据</td>
					<td>不放参数、可选值</td>
			</tr>
			<tr>
					<td>Done 用 select</td>
					<td>配合 select 监听取消</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="十一context-的替代与补充">十一、Context 的替代与补充</h2>
<table>
	<thead>
			<tr>
					<th>场景</th>
					<th>推荐</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>单个 goroutine 取消</td>
					<td><code>context.WithCancel</code></td>
			</tr>
			<tr>
					<td>多个 goroutine 统一取消</td>
					<td><code>errgroup</code> + context</td>
			</tr>
			<tr>
					<td>纯等待完成</td>
					<td><code>sync.WaitGroup</code>（不需要 ctx）</td>
			</tr>
			<tr>
					<td>只有超时</td>
					<td><code>time.After</code>（不需要 ctx）</td>
			</tr>
			<tr>
					<td>需要传值 + 取消</td>
					<td><code>context.WithValue</code> + <code>WithTimeout</code></td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="十二完整实战带超时的并发请求">十二、完整实战：带超时的并发请求</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;golang.org/x/sync/errgroup&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Result</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Source</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Data</span><span class="w">   </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fetch</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">source</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">delay</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="o">*</span><span class="nx">Result</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="nx">delay</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">Result</span><span class="p">{</span><span class="nx">Source</span><span class="p">:</span><span class="w"> </span><span class="nx">source</span><span class="p">,</span><span class="w"> </span><span class="nx">Data</span><span class="p">:</span><span class="w"> </span><span class="nx">source</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s">&#34;-data&#34;</span><span class="p">},</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;%s: %w&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">source</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="mi">1500</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">g</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">errgroup</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sources</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;db&#34;</span><span class="p">:</span><span class="w">      </span><span class="mi">300</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;cache&#34;</span><span class="p">:</span><span class="w">   </span><span class="mi">200</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;api&#34;</span><span class="p">:</span><span class="w">     </span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span><span class="w"> </span><span class="c1">// 会超时</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">results</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">delay</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">sources</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">delay</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">delay</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">g</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">r</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">fetch</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">delay</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">results</span><span class="p">[</span><span class="nx">r</span><span class="p">.</span><span class="nx">Source</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">r</span><span class="p">.</span><span class="nx">Data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">g</span><span class="p">.</span><span class="nf">Wait</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;有任务失败:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;已完成的任务:&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;  %s: %s\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>输出示例：</strong></p>
<pre tabindex="0"><code>有任务失败: api: context deadline exceeded
已完成的任务:
  cache: cache-data
  db: db-data
</code></pre><hr>
<h2 id="十三核心要点总结">十三、核心要点总结</h2>
<ol>
<li><strong>Context 是取消信号传播树</strong>，父取消 → 子全取消</li>
<li><strong>取消 ≠ 杀死 goroutine</strong>，必须 goroutine 主动检查 <code>Done()</code></li>
<li><strong>永远 <code>defer cancel()</code></strong>，防止泄漏</li>
<li><strong>Context 作为第一个参数</strong>，不存 struct</li>
<li><strong>WithValue 只放请求范围数据</strong>（trace ID、用户身份），不传参数</li>
<li><strong>HTTP / DB / RPC 都原生支持 Context</strong>，直接传下去</li>
<li><strong>errgroup</strong> 是 context + goroutine 的最佳实践组合</li>
<li><strong>WithValue 的 key 用自定义类型</strong>，避免冲突</li>
</ol>
<hr>
<p>进一步展开：</p>
<ul>
<li><strong><code>errgroup</code> 源码与高级用法</strong>（SetLimit、TryGo）</li>
<li><strong><code>context</code> 源码分析</strong>（<code>cancelCtx</code> 的传播机制）</li>
<li><strong>分布式链路追踪</strong>（OpenTelemetry + context）</li>
<li><strong>gRPC 中的 Context 传递</strong></li>
</ul>
]]></content:encoded></item><item><title>About me</title><link>https://blog.dart.xin/about/</link><pubDate>Mon, 12 Oct 2026 11:04:50 +0800</pubDate><guid>https://blog.dart.xin/about/</guid><description>&lt;h3 id="hi-there--im-zwdart"&gt;Hi there 👋 I&amp;rsquo;m [zwdart]&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;🔭 I’m currently working on [API]&lt;/li&gt;
&lt;li&gt;🌱 I’m currently learning [Prompt RAG Agent LLM]&lt;/li&gt;
&lt;li&gt;👯 I’m looking to collaborate on [rust]&lt;/li&gt;
&lt;li&gt;💬 Ask me about [Go / 后端 / 云原生]&lt;/li&gt;
&lt;li&gt;📫 How to reach me: [邮箱]&lt;/li&gt;
&lt;li&gt;⚡ Fun fact: [君子曰：‌学不可以已‌。]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;br&gt;
&lt;img alt="Go" loading="lazy" src="https://img.shields.io/badge/-Go-00ADD8?style=flat&amp;logo=go&amp;logoColor=white"&gt;
&lt;img alt="Rust" loading="lazy" src="https://img.shields.io/badge/-rust-DC382D?style=flat&amp;logo=rust&amp;logoColor=white"&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<h3 id="hi-there--im-zwdart">Hi there 👋 I&rsquo;m [zwdart]</h3>
<ul>
<li>🔭 I’m currently working on [API]</li>
<li>🌱 I’m currently learning [Prompt RAG Agent LLM]</li>
<li>👯 I’m looking to collaborate on [rust]</li>
<li>💬 Ask me about [Go / 后端 / 云原生]</li>
<li>📫 How to reach me: [邮箱]</li>
<li>⚡ Fun fact: [君子曰：‌学不可以已‌。]</li>
</ul>
<p><strong>Tech Stack:</strong><br>
<img alt="Go" loading="lazy" src="https://img.shields.io/badge/-Go-00ADD8?style=flat&logo=go&logoColor=white">
<img alt="Rust" loading="lazy" src="https://img.shields.io/badge/-rust-DC382D?style=flat&logo=rust&logoColor=white"></p>
]]></content:encoded></item><item><title>君子曰：学不可以已。</title><link>https://blog.dart.xin/posts/studying/</link><pubDate>Mon, 12 Oct 2026 10:53:42 +0800</pubDate><guid>https://blog.dart.xin/posts/studying/</guid><description>&lt;h2 id="荀子劝学"&gt;《荀子·劝学》&lt;/h2&gt;
&lt;p&gt;君子曰：学不可以已。&lt;/p&gt;
&lt;p&gt;青，取之于蓝，而青于蓝；冰，水为之，而寒于水。木直中绳，輮以为轮，其曲中规。虽有槁暴，不复挺者，輮使之然也。故木受绳则直，金就砺则利，君子博学而日参省乎己，则知明而行无过矣。&lt;/p&gt;
&lt;p&gt;吾尝终日而思矣，不如须臾之所学也；吾尝跂而望矣，不如登高之博见也。登高而招，臂非加长也，而见者远；顺风而呼，声非加疾也，而闻者彰。假舆马者，非利足也，而致千里；假舟楫者，非能水也，而绝江河。君子生非异也，善假于物也。&lt;/p&gt;
&lt;p&gt;南方有鸟焉，名曰蒙鸠，以羽为巢，而编之以发，系之苇苕，风至苕折，卵破子死。巢非不完也，所系者然也。西方有木焉，名曰射干，茎长四寸，生于高山之上，而临百仞之渊，木茎非能长也，所立者然也。蓬生麻中，不扶而直；白沙在涅，与之俱黑。兰槐之根是为芷，其渐之滫，君子不近，庶人不服。其质非不美也，所渐者然也。故君子居必择乡，游必就士，所以防邪辟而近中正也。&lt;/p&gt;
&lt;p&gt;物类之起，必有所始。荣辱之来，必象其德。肉腐出虫，鱼枯生蠹。怠慢忘身，祸灾乃作。强自取柱，柔自取束。邪秽在身，怨之所构。施薪若一，火就燥也；平地若一，水就湿也。草木畴生，禽兽群焉，物各从其类也。是故质的张而弓矢至焉，林木茂而斧斤至焉，树成荫而众鸟息焉，醯酸而蚋聚焉。故言有召祸也，行有招辱也，君子慎其所立乎！&lt;/p&gt;
&lt;p&gt;积土成山，风雨兴焉；积水成渊，蛟龙生焉；积善成德，而神明自得，圣心备焉。故不积跬步，无以至千里；不积小流，无以成江海。骐骥一跃，不能十步；驽马十驾，功在不舍。锲而舍之，朽木不折；锲而不舍，金石可镂。蚓无爪牙之利，筋骨之强，上食埃土，下饮黄泉，用心一也。蟹六跪而二螯，非蛇鳝之穴无可寄托者，用心躁也。是故无冥冥之志者，无昭昭之明；无惛惛之事者，无赫赫之功。行衢道者不至，事两君者不容。目不能两视而明，耳不能两听而聪。螣蛇无足而飞，鼫鼠五技而穷。《诗》曰：“尸鸠在桑，其子七兮。淑人君子，其仪一兮。其仪一兮，心如结兮。”故君子结于一也。&lt;/p&gt;
&lt;p&gt;昔者瓠巴鼓瑟而流鱼出听，伯牙鼓琴而六马仰秣。故声无小而不闻，行无隐而不形。玉在山而草木润，渊生珠而崖不枯。为善不积邪？安有不闻者乎？&lt;/p&gt;
&lt;p&gt;学恶乎始？恶乎终？曰：其数则始乎诵经，终乎读礼；其义则始乎为士，终乎为圣人。真积力久则入，学至乎没而后止也。故学数有终，若其义则不可须臾舍也。为之，人也；舍之，禽兽也。故书者，政事之纪也；诗者，中声之所止也；礼者，法之大分，类之纲纪也。故学至乎礼而止矣。夫是之谓道德之极。礼之敬文也，乐之中和也，诗书之博也，春秋之微也，在天地之间者毕矣。&lt;/p&gt;
&lt;p&gt;君子之学也，入乎耳，箸乎心，布乎四体，形乎动静。端而言，蠕而动，一可以为法则。小人之学也，入乎耳，出乎口；口耳之间，则四寸耳，曷足以美七尺之躯哉！古之学者为己，今之学者为人。君子之学也，以美其身；小人之学也，以为禽犊。故不问而告谓之傲，问一而告二谓之囋。傲、非也，囋、非也；君子如向矣。&lt;/p&gt;
&lt;p&gt;学莫便乎近其人。礼乐法而不说，诗书故而不切，春秋约而不速。方其人之习君子之说，则尊以遍矣，周于世矣。故曰：学莫便乎近其人。学之经莫速乎好其人，隆礼次之。上不能好其人，下不能隆礼，安特将学杂识志，顺诗书而已耳。则末世穷年，不免为陋儒而已。将原先王，本仁义，则礼正其经纬蹊径也。若挈裘领，诎五指而顿之，顺者不可胜数也。不道礼宪，以诗书为之，譬之犹以指测河也，以戈舂黍也，以锥餐壶也，不可以得之矣。故隆礼，虽未明，法士也；不隆礼，虽察辩，散儒也。&lt;/p&gt;
&lt;p&gt;问楛者，勿告也；告楛者，勿问也；说楛者，勿听也。有争气者，勿与辩也。故必由其道至，然后接之；非其道则避之。故礼恭，而后可与言道之方；辞顺，而后可与言道之理；色从而后可与言道之致。故未可与言而言谓之傲，可与言而不言谓之隐，不观气色而言谓之瞽。故君子不傲、不隐、不瞽，谨顺其身。《诗》曰：“匪交匪舒，天子所予。”此之谓也。&lt;/p&gt;
&lt;p&gt;百发失一，不足谓善射；千里蹞步不至，不足谓善御；伦类不通，仁义不一，不足谓善学。学也者，固学一之也。一出焉，一入焉，涂巷之人也；其善者少，不善者多，桀纣盗跖也；全之尽之，然后学者也。&lt;/p&gt;
&lt;p&gt;君子知夫不全不粹之不足以为美也，故诵数以贯之，思索以通之，为其人以处之，除其害者以持养之。使目非是无欲见也，使耳非是无欲闻也，使口非是无欲言也，使心非是无欲虑也。及至其致好之也，目好之五色，耳好之五声，口好之五味，心利之有天下。是故权利不能倾也，群众不能移也，天下不能荡也。生乎由是，死乎由是，夫是之谓德操。德操然后能定，能定然后能应。能定能应，夫是之谓成人。天见其明，地见其光，君子贵其全也。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;唐·颜真卿《劝学》&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;三更灯火五更鸡，正是男儿读书时。&lt;br&gt;
黑发不知勤学早，白首方悔读书迟。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;宋真宗《劝学诗》&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;富家不用买良田，书中自有千钟粟。&lt;br&gt;
安居不用架高堂，书中自有黄金屋。&lt;br&gt;
出门莫恨无人随，书中车马多如簇。&lt;br&gt;
娶妻莫恨无良媒，书中自有颜如玉。&lt;br&gt;
男儿欲遂平生志，五经勤向窗前读。&lt;/p&gt;
&lt;/blockquote&gt;</description><content:encoded><![CDATA[<h2 id="荀子劝学">《荀子·劝学》</h2>
<p>君子曰：学不可以已。</p>
<p>青，取之于蓝，而青于蓝；冰，水为之，而寒于水。木直中绳，輮以为轮，其曲中规。虽有槁暴，不复挺者，輮使之然也。故木受绳则直，金就砺则利，君子博学而日参省乎己，则知明而行无过矣。</p>
<p>吾尝终日而思矣，不如须臾之所学也；吾尝跂而望矣，不如登高之博见也。登高而招，臂非加长也，而见者远；顺风而呼，声非加疾也，而闻者彰。假舆马者，非利足也，而致千里；假舟楫者，非能水也，而绝江河。君子生非异也，善假于物也。</p>
<p>南方有鸟焉，名曰蒙鸠，以羽为巢，而编之以发，系之苇苕，风至苕折，卵破子死。巢非不完也，所系者然也。西方有木焉，名曰射干，茎长四寸，生于高山之上，而临百仞之渊，木茎非能长也，所立者然也。蓬生麻中，不扶而直；白沙在涅，与之俱黑。兰槐之根是为芷，其渐之滫，君子不近，庶人不服。其质非不美也，所渐者然也。故君子居必择乡，游必就士，所以防邪辟而近中正也。</p>
<p>物类之起，必有所始。荣辱之来，必象其德。肉腐出虫，鱼枯生蠹。怠慢忘身，祸灾乃作。强自取柱，柔自取束。邪秽在身，怨之所构。施薪若一，火就燥也；平地若一，水就湿也。草木畴生，禽兽群焉，物各从其类也。是故质的张而弓矢至焉，林木茂而斧斤至焉，树成荫而众鸟息焉，醯酸而蚋聚焉。故言有召祸也，行有招辱也，君子慎其所立乎！</p>
<p>积土成山，风雨兴焉；积水成渊，蛟龙生焉；积善成德，而神明自得，圣心备焉。故不积跬步，无以至千里；不积小流，无以成江海。骐骥一跃，不能十步；驽马十驾，功在不舍。锲而舍之，朽木不折；锲而不舍，金石可镂。蚓无爪牙之利，筋骨之强，上食埃土，下饮黄泉，用心一也。蟹六跪而二螯，非蛇鳝之穴无可寄托者，用心躁也。是故无冥冥之志者，无昭昭之明；无惛惛之事者，无赫赫之功。行衢道者不至，事两君者不容。目不能两视而明，耳不能两听而聪。螣蛇无足而飞，鼫鼠五技而穷。《诗》曰：“尸鸠在桑，其子七兮。淑人君子，其仪一兮。其仪一兮，心如结兮。”故君子结于一也。</p>
<p>昔者瓠巴鼓瑟而流鱼出听，伯牙鼓琴而六马仰秣。故声无小而不闻，行无隐而不形。玉在山而草木润，渊生珠而崖不枯。为善不积邪？安有不闻者乎？</p>
<p>学恶乎始？恶乎终？曰：其数则始乎诵经，终乎读礼；其义则始乎为士，终乎为圣人。真积力久则入，学至乎没而后止也。故学数有终，若其义则不可须臾舍也。为之，人也；舍之，禽兽也。故书者，政事之纪也；诗者，中声之所止也；礼者，法之大分，类之纲纪也。故学至乎礼而止矣。夫是之谓道德之极。礼之敬文也，乐之中和也，诗书之博也，春秋之微也，在天地之间者毕矣。</p>
<p>君子之学也，入乎耳，箸乎心，布乎四体，形乎动静。端而言，蠕而动，一可以为法则。小人之学也，入乎耳，出乎口；口耳之间，则四寸耳，曷足以美七尺之躯哉！古之学者为己，今之学者为人。君子之学也，以美其身；小人之学也，以为禽犊。故不问而告谓之傲，问一而告二谓之囋。傲、非也，囋、非也；君子如向矣。</p>
<p>学莫便乎近其人。礼乐法而不说，诗书故而不切，春秋约而不速。方其人之习君子之说，则尊以遍矣，周于世矣。故曰：学莫便乎近其人。学之经莫速乎好其人，隆礼次之。上不能好其人，下不能隆礼，安特将学杂识志，顺诗书而已耳。则末世穷年，不免为陋儒而已。将原先王，本仁义，则礼正其经纬蹊径也。若挈裘领，诎五指而顿之，顺者不可胜数也。不道礼宪，以诗书为之，譬之犹以指测河也，以戈舂黍也，以锥餐壶也，不可以得之矣。故隆礼，虽未明，法士也；不隆礼，虽察辩，散儒也。</p>
<p>问楛者，勿告也；告楛者，勿问也；说楛者，勿听也。有争气者，勿与辩也。故必由其道至，然后接之；非其道则避之。故礼恭，而后可与言道之方；辞顺，而后可与言道之理；色从而后可与言道之致。故未可与言而言谓之傲，可与言而不言谓之隐，不观气色而言谓之瞽。故君子不傲、不隐、不瞽，谨顺其身。《诗》曰：“匪交匪舒，天子所予。”此之谓也。</p>
<p>百发失一，不足谓善射；千里蹞步不至，不足谓善御；伦类不通，仁义不一，不足谓善学。学也者，固学一之也。一出焉，一入焉，涂巷之人也；其善者少，不善者多，桀纣盗跖也；全之尽之，然后学者也。</p>
<p>君子知夫不全不粹之不足以为美也，故诵数以贯之，思索以通之，为其人以处之，除其害者以持养之。使目非是无欲见也，使耳非是无欲闻也，使口非是无欲言也，使心非是无欲虑也。及至其致好之也，目好之五色，耳好之五声，口好之五味，心利之有天下。是故权利不能倾也，群众不能移也，天下不能荡也。生乎由是，死乎由是，夫是之谓德操。德操然后能定，能定然后能应。能定能应，夫是之谓成人。天见其明，地见其光，君子贵其全也。</p>
<p><strong>唐·颜真卿《劝学》</strong></p>
<blockquote>
<p>三更灯火五更鸡，正是男儿读书时。<br>
黑发不知勤学早，白首方悔读书迟。</p>
</blockquote>
<p><strong>宋真宗《劝学诗》</strong></p>
<blockquote>
<p>富家不用买良田，书中自有千钟粟。<br>
安居不用架高堂，书中自有黄金屋。<br>
出门莫恨无人随，书中车马多如簇。<br>
娶妻莫恨无良媒，书中自有颜如玉。<br>
男儿欲遂平生志，五经勤向窗前读。</p>
</blockquote>
]]></content:encoded></item><item><title>Goroutine Select</title><link>https://blog.dart.xin/golang/go104-select/</link><pubDate>Mon, 12 Oct 2026 09:55:20 +0800</pubDate><guid>https://blog.dart.xin/golang/go104-select/</guid><description>Go 专门为 channel 设计的多路复用语句，</description><content:encoded><![CDATA[<h1 id="go-select-详解与可运行示例">Go <code>select</code> 详解与可运行示例</h1>
<h2 id="一本质select-是什么">一、本质：select 是什么</h2>
<p><code>select</code> 是 Go 里<strong>唯一的多路 channel 操作原语</strong>。它让一个 goroutine 同时等待多个 channel 操作，<strong>哪个先就绪就走哪个</strong>。没有它，你要么串行阻塞在单个 channel 上，要么自己开 goroutine 轮询。</p>
<p><strong>一句话定位</strong>：<code>switch</code> 是对值的分支，<code>select</code> 是对<strong>通道就绪状态</strong>的分支。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">    </span><span class="c1">// ch1 有数据可读</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">handle</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">x</span><span class="p">:</span><span class="w">      </span><span class="c1">// ch2 有接收者就绪</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 发送成功</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">  </span><span class="c1">// 超时</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">            </span><span class="c1">// 所有 case 都没就绪</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 不阻塞，立即走这里</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="二语法与语义规则">二、语法与语义规则</h2>
<h3 id="规则-1case-必须是-channel-操作">规则 1：case 必须是 channel 操作</h3>
<p>合法：<code>&lt;-ch</code>、<code>ch &lt;- v</code>、<code>v, ok := &lt;-ch</code>。
非法：普通表达式、函数调用（<code>case f():</code> 不行）、常量比较。</p>
<h3 id="规则-2多个-case-同时就绪时伪随机选一个">规则 2：多个 case 同时就绪时，<strong>伪随机</strong>选一个</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>两个都就绪时，runtime 用 <code>cheaprandn</code> 随机选。<strong>这是有意设计</strong>，避免固定顺序导致某个 case 永远饿死。所以<strong>不要依赖 select 的分支顺序</strong>写业务逻辑。</p>
<h3 id="规则-3没有-default-时全部不就绪则阻塞当前-goroutine">规则 3：没有 <code>default</code> 时，全部不就绪则<strong>阻塞</strong>当前 goroutine</h3>
<p>阻塞期间 goroutine 进 <code>_Gwaiting</code>，会让出 P，M 去跑别的 G。<strong>不占 CPU</strong>。</p>
<h3 id="规则-4有-default-时全部不就绪则立即执行-default">规则 4：有 <code>default</code> 时，全部不就绪则<strong>立即执行 default</strong></h3>
<p><code>default</code> 让 select 变成<strong>非阻塞</strong>的。这是&quot;尝试一次，不行就算了&quot;的标准写法。</p>
<h3 id="规则-5select--永久阻塞">规则 5：<code>select {}</code> 永久阻塞</h3>
<p>空 select <strong>永远阻塞</strong>，用于让 main goroutine 永不退出（配合其他 goroutine 干活）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{}</span><span class="w">   </span><span class="c1">// 永久阻塞，不会死锁 panic（因为有 worker 在跑）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>但<strong>如果没有任何其他 goroutine</strong>，<code>select {}</code> 会触发 <code>fatal error: all goroutines are asleep - deadlock</code>。</p>
<h3 id="规则-6nil-channel-上的-case-永远不就绪">规则 6：<strong>nil channel 上的 case 永远不就绪</strong></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w">   </span><span class="c1">// nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">        </span><span class="c1">// 这个 case 永远不会被选中</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这是<strong>禁用某个分支</strong>的利器（后文详述）。</p>
<h3 id="规则-7break-跳出的是-select不是外层-for">规则 7：<code>break</code> 跳出的是 <code>select</code>，不是外层 <code>for</code></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">break</span><span class="w">      </span><span class="c1">// ⚠️ 只跳出 select，for 继续转</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>要用 <code>return</code>、<code>goto</code>、或者带标签的 <code>break loop</code> 才能跳出循环。</p>
<hr>
<h2 id="三运行时机制selectgo">三、运行时机制：<code>selectgo</code></h2>
<p>编译器把 <code>select</code> 编译成对 <code>runtime.selectgo</code> 的调用。核心流程：</p>
<pre tabindex="0"><code>selectgo(cases):
  ① 生成 pollorder（随机打乱的 case 顺序）
  ② 生成 lockorder（按 channel 地址排序，防死锁）
  ③ 第一轮：按 pollorder 遍历，逐个尝试非阻塞收发
      ├─ 有就绪的 → 直接执行该 case，返回
      └─ 没有 → 继续
  ④ 有 default → 执行 default，返回
  ⑤ 没有 default → 把所有 case 的 sudog 挂到各 channel 的等待队列上
      → gopark() 阻塞
  ⑥ 被某个 channel 的就绪事件唤醒 → 从队列摘除所有 sudog → 返回被选中的 case
</code></pre><p><strong>两个关键设计</strong>：</p>
<ol>
<li><strong>pollorder 随机</strong>：保证公平，避免固定顺序饥饿</li>
<li><strong>lockorder 按地址排序</strong>：多个 case 涉及多个 channel 的锁时，<strong>统一按地址顺序加锁</strong>，避免两个 goroutine 以相反顺序加锁导致死锁</li>
</ol>
<p><strong>case 数量上限</strong>：<code>select</code> 最多 <strong>65536</strong> 个 case（<code>uint16</code> 索引），实际中远用不到，但要知道有这个上限。</p>
<p><strong>性能</strong>：无就绪 case 且需要阻塞时，每个 case 都要分配 sudog、挂队列，开销明显高于单个 channel 操作。<strong>select 适合&quot;少数几个 channel&quot;，不适合&quot;海量 channel 轮询&quot;</strong>。</p>
<hr>
<h2 id="四经典模式每个都可直接-go-run">四、经典模式（每个都可直接 <code>go run</code>）</h2>
<h3 id="示例-1基础多路复用">示例 1：基础多路复用</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ch1</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s">&#34;来自 ch1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">200</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s">&#34;来自 ch2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>：</p>
<pre tabindex="0"><code>收到: 来自 ch1
收到: 来自 ch2
</code></pre><p><strong>要点</strong>：<code>select</code> 同时等两个 channel，谁先到处理谁。<code>ch1</code> 先发（100ms），所以先走 <code>ch1</code> 分支。</p>
<hr>
<h3 id="示例-2超时控制最常用">示例 2：超时控制（最常用）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">slowOp</span><span class="p">()</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">   </span><span class="c1">// 模拟慢操作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s">&#34;结果&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">ch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">start</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nf">slowOp</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;成功:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;耗时:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Since</span><span class="p">(</span><span class="nx">start</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;超时! 耗时:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Since</span><span class="p">(</span><span class="nx">start</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>：</p>
<pre tabindex="0"><code>超时! 耗时: 500.xxxms
</code></pre><p><strong>要点</strong>：</p>
<ul>
<li><code>time.After(d)</code> 返回一个 channel，d 之后收到一个值</li>
<li>慢操作 2 秒，超时 500ms，select 走超时分支</li>
<li><strong>注意</strong>：<code>time.After</code> 底层 timer 在触发前不会释放，<strong>高频循环里用 <code>time.NewTimer</code> + <code>Reset</code> 更省资源</strong>（后文陷阱详述）</li>
</ul>
<hr>
<h3 id="示例-3非阻塞收发default">示例 3：非阻塞收发（<code>default</code>）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 第一次：channel 空，发送成功</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;发送 1 成功&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;发送 1 失败（通道满）&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 第二次：channel 已满（cap=1），发送走 default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;发送 2 成功&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;发送 2 失败（通道满）&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 接收：有数据，成功</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;接收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;通道空，无数据&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 再次接收：空了，走 default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;接收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;通道空，无数据&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>：</p>
<pre tabindex="0"><code>发送 1 成功
发送 2 失败（通道满）
接收到: 1
通道空，无数据
</code></pre><p><strong>要点</strong>：<code>default</code> 让操作变成&quot;尝试一次&quot;——这就是&quot;非阻塞发送/接收&quot;的标准写法。</p>
<hr>
<h3 id="示例-4优雅退出context--信号">示例 4：优雅退出（context + 信号）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ticker</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">NewTicker</span><span class="p">(</span><span class="mi">200</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">ticker</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;worker %d 退出: %v\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ticker</span><span class="p">.</span><span class="nx">C</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;worker %d 干活中...\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">700</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;&gt;&gt;&gt; 发出取消信号&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;&gt;&gt;&gt; main 退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>（时序大致如此）：</p>
<pre tabindex="0"><code>worker 1 干活中...
worker 2 干活中...
worker 3 干活中...
worker 1 干活中...
worker 2 干活中...
worker 3 干活中...
worker 1 干活中...
worker 2 干活中...
worker 3 干活中...
&gt;&gt;&gt; 发出取消信号
worker 1 退出: context canceled
worker 2 退出: context canceled
worker 3 退出: context canceled
&gt;&gt;&gt; main 退出
</code></pre><p><strong>要点</strong>：</p>
<ul>
<li><code>ctx.Done()</code> 是<strong>广播</strong>：<code>cancel()</code> 后所有 select 在这个 case 上的 goroutine 同时收到</li>
<li>这是 Go 服务里<strong>标准退出模式</strong>：每个长任务都 <code>select</code> 监听 <code>ctx.Done()</code></li>
<li><code>defer ticker.Stop()</code> 防止资源泄漏</li>
</ul>
<hr>
<h3 id="示例-5用-nil-channel-动态禁用分支">示例 5：用 nil channel 动态禁用分支</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">ch1</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s">&#34;ch1 数据&#34;</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s">&#34;ch2 数据&#34;</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 先启用两个通道</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c1</span><span class="p">,</span><span class="w"> </span><span class="nx">c2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">ch1</span><span class="p">,</span><span class="w"> </span><span class="nx">ch2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">c1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">c1</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w">   </span><span class="c1">// 禁用 ch1 分支，后续 select 忽略它</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">c2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">c2</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;两个通道都已处理完毕&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>：</p>
<pre tabindex="0"><code>处理: ch1 数据
处理: ch2 数据
两个通道都已处理完毕
</code></pre><p><strong>要点</strong>：<code>c1 = nil</code> 之后，<code>case v := &lt;-c1</code> 在 select 里<strong>永远不就绪</strong>——这是<strong>动态开关分支</strong>的标准技巧。常见于&quot;关闭某个数据源后只从剩下的源读&quot;的场景。</p>
<hr>
<h3 id="示例-6优先级-select防止低优先级饿死">示例 6：优先级 select（防止低优先级饿死）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">high</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">low</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 塞入数据</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">high</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;高优先级-%d&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">low</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;低优先级-%d&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nb">close</span><span class="p">(</span><span class="nx">high</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// 第一层 select：优先尝试 high</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">high</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">continue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="c1">// high 已关闭，走下面</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="c1">// high 没就绪，落到第二层</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// 第二层 select：处理 low 或等待 high</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">high</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">low</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">10</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>（前 3 条必为高优先级）：</p>
<pre tabindex="0"><code>处理: 高优先级-0
处理: 高优先级-1
处理: 高优先级-2
处理: 低优先级-0
处理: 低优先级-1
处理: 低优先级-2
处理: 低优先级-3
处理: 低优先级-4
</code></pre><p><strong>要点</strong>：select <strong>本身不支持优先级</strong>，但可以<strong>用两层 select 模拟</strong>：第一层带 <code>default</code> 尝试高优先级，没就绪再进第二层等。<strong>代价是代码复杂，且默认分支有 CPU 空转风险</strong>——第二层里的 <code>time.Sleep</code> 就是为了缓解空转（生产代码里更推荐用 <code>time.Ticker</code> 或合理设置 default 的退出条件）。</p>
<hr>
<h3 id="示例-7for-select-循环最常见的结构">示例 7：for-select 循环（最常见的结构）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">jobs</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">done</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 消费者</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">case</span><span class="w"> </span><span class="nx">j</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">					</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;jobs 已关闭，消费者退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">					</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理任务:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">j</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到退出信号，消费者退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 生产 3 个任务</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">jobs</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nb">close</span><span class="p">(</span><span class="nx">done</span><span class="p">)</span><span class="w">   </span><span class="c1">// 触发消费者退出</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;main 退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>：</p>
<pre tabindex="0"><code>处理任务: 1
处理任务: 2
处理任务: 3
收到退出信号，消费者退出
main 退出
</code></pre><p><strong>要点</strong>：</p>
<ul>
<li><code>for { select { ... } }</code> 是 Go 并发代码的<strong>基本骨架</strong></li>
<li>每个 case 都要有<strong>明确的退出路径</strong>（<code>return</code> 或 <code>break loop</code>）</li>
<li>用 <code>v, ok := &lt;-ch</code> 感知关闭</li>
<li><strong>不要用 <code>break</code> 跳出 for</strong>——<code>break</code> 只跳 select（前文规则 7）</li>
</ul>
<hr>
<h3 id="示例-8select-随机性演示">示例 8：select 随机性演示</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch1</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">counts</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="kt">int</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">counts</span><span class="p">[</span><span class="nx">v</span><span class="p">]</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">ch1</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">counts</span><span class="p">[</span><span class="nx">v</span><span class="p">]</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">&#34;ch1 被选中 %d 次, ch2 被选中 %d 次\n&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">counts</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="w"> </span><span class="nx">counts</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">10</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>（数字接近 500/500）：</p>
<pre tabindex="0"><code>ch1 被选中 约500 次, ch2 被选中 约500 次
</code></pre><p><strong>要点</strong>：两个 channel 始终有数据，select <strong>随机</strong>选。跑多次会发现比例接近 1:1，但<strong>不是严格交替</strong>。<strong>不要依赖 select 的顺序写逻辑</strong>。</p>
<hr>
<h3 id="示例-9合并多个-channelfan-in">示例 9：合并多个 channel（fan-in）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">name</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">defer</span><span class="w"> </span><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nx">n</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;%s-%d&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">50</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">ch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">merge</span><span class="p">(</span><span class="nx">chs</span><span class="w"> </span><span class="o">...&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">out</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">chs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">out</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">v</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">out</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">a</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="s">&#34;A&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">b</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="s">&#34;B&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nf">merge</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="w"> </span><span class="nx">b</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;全部完成&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>（A/B 交错，顺序不定）：</p>
<pre tabindex="0"><code>收到: A-0
收到: B-0
收到: A-1
收到: B-1
收到: A-2
收到: B-2
全部完成
</code></pre><p><strong>要点</strong>：这是<strong>不依赖 select 的 fan-in</strong>——每个源一个 goroutine 往同一个 out 写。<code>sync.WaitGroup + close</code> 是上一轮讨论的&quot;多生产者关闭&quot;标准模式。<strong>merge 内部每个 goroutine 的 <code>for range</code> 是阻塞读，天然实现了&quot;谁有数据谁先写&quot;</strong>，效果等价于 select 多路复用，但<strong>代码更清晰、可扩展</strong>（N 个源不用写 N 个 case）。</p>
<hr>
<h3 id="示例-10带缓冲-channel--select-的批处理">示例 10：带缓冲 channel + select 的批处理</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">events</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">100</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 生产事件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">20</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">events</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">20</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">events</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ticker</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">NewTicker</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">ticker</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">batch</span><span class="w"> </span><span class="p">[]</span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">events</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">batch</span><span class="p">)</span><span class="w"> </span><span class="p">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">					</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;最后一批:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">batch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;全部完成&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">batch</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nb">append</span><span class="p">(</span><span class="nx">batch</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">batch</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;批量处理:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">batch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">batch</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">batch</span><span class="p">[:</span><span class="mi">0</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ticker</span><span class="p">.</span><span class="nx">C</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">batch</span><span class="p">)</span><span class="w"> </span><span class="p">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;定时刷新:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">batch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">batch</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">batch</span><span class="p">[:</span><span class="mi">0</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>预期输出</strong>（大致）：</p>
<pre tabindex="0"><code>定时刷新: [1 2 3 4]
批量处理: [5 6 7 8 9]
批量处理: [10 11 12 13 14]
批量处理: [15 16 17 18 19]
最后一批: [20]
全部完成
</code></pre><p><strong>要点</strong>：<code>select</code> 同时监听&quot;数据到达&quot;和&quot;定时器触发&quot;，实现<strong>双条件批处理</strong>——要么凑够 5 个，要么 100ms 到了就刷。<strong>这是生产级批量处理的标准骨架</strong>（日志聚合、数据库批量写都用这个模式）。</p>
<hr>
<h2 id="五陷阱清单">五、陷阱清单</h2>
<h3 id="-陷阱-1timeafter-在循环里的资源泄漏">⚠️ 陷阱 1：<code>time.After</code> 在循环里的资源泄漏</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ 每秒泄漏一个 timer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">handle</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">   </span><span class="c1">// 每次循环都创建新 timer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">timeout</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>time.After</code> 创建的 timer 在触发前<strong>不会被 GC</strong>，即使 select 走了别的分支。高频循环里会累积大量 timer，内存持续上涨。</p>
<p><strong>修复</strong>：用 <code>time.NewTimer</code> + 手动 <code>Reset</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ✅ 复用 timer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">timer</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">NewTimer</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nx">timer</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">timer</span><span class="p">.</span><span class="nf">Stop</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">&lt;-</span><span class="nx">timer</span><span class="p">.</span><span class="nx">C</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">timer</span><span class="p">.</span><span class="nf">Reset</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">handle</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">timer</span><span class="p">.</span><span class="nx">C</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">timeout</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="-陷阱-2default-导致-cpu-空转">⚠️ 陷阱 2：<code>default</code> 导致 CPU 空转</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ 死循环空转，CPU 100%</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">handle</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 什么都不做，立即进入下一轮</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>default</code> 让 select 不阻塞，循环转得飞快，<strong>CPU 打满</strong>。</p>
<p><strong>修复</strong>：要么去掉 <code>default</code>（阻塞等），要么在 default 里加 <code>runtime.Gosched()</code> 或 <code>time.Sleep</code>，要么用 <code>time.Ticker</code> 节流。</p>
<h3 id="-陷阱-3break-跳出的是-select不是-for">⚠️ 陷阱 3：<code>break</code> 跳出的是 select，不是 for</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ break 只跳出 select，for 继续转</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">break</span><span class="w">          </span><span class="c1">// 以为跳出 for，其实只跳出 select</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ✅ 用 return 或标签</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">loop</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">done</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">break</span><span class="w"> </span><span class="nx">loop</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="-陷阱-4关闭的-channel-在-select-里一直就绪">⚠️ 陷阱 4：关闭的 channel 在 select 里<strong>一直就绪</strong></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">   </span><span class="c1">// 必须判断 ok，否则 select 每次都选这个 case</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">other</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">handle</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>关闭的 channel <strong>永远可以立即读到零值</strong>，所以 <code>case &lt;-ch</code> 在 select 里<strong>永远就绪</strong>，会<strong>疯狂抢走</strong>其他 case 的机会。<strong>必须用 <code>v, ok :=</code> 判断，ok 为 false 时把 <code>ch</code> 设为 nil 或退出</strong>。</p>
<h3 id="-陷阱-5nil-channel-用于禁用但别拿它做空转">⚠️ 陷阱 5：nil channel 用于<strong>禁用</strong>，但别拿它做&quot;空转&quot;</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w">   </span><span class="c1">// nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">        </span><span class="c1">// 永远不就绪 ✅ 用来禁分支</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">other</span><span class="p">:</span><span class="w">     </span><span class="c1">// 正常工作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这是<strong>特性</strong>不是 bug。但如果所有 case 都是 nil channel 且没有 default，就<strong>永久阻塞</strong>（和 <code>select {}</code> 一样）。</p>
<h3 id="-陷阱-6select-里对同一-channel-多个-case">⚠️ 陷阱 6：select 里对同一 channel 多个 case</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ❌ 两个 case 操作同一 channel，行为混乱</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>虽然语法合法，但<strong>实际语义很难推理</strong>（一读一写同时竞争），生产代码应避免。<strong>每个 channel 在 select 里最多出现一次</strong>。</p>
<h3 id="-陷阱-7select-的-case-太多">⚠️ 陷阱 7：select 的 case 太多</h3>
<p>超过 65536 个 case <strong>编译期报错</strong>。即使没到上限，case 越多，<code>selectgo</code> 的轮询和锁开销越大。<strong>&ldquo;等很多个 channel&rdquo; 应该用 fan-in 模式聚合成一个 channel，而不是写几百个 case</strong>。</p>
<hr>
<h2 id="六性能与最佳实践速查">六、性能与最佳实践速查</h2>
<table>
	<thead>
			<tr>
					<th>场景</th>
					<th>推荐写法</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>等少数几个 channel</td>
					<td><code>select</code> 直接多路复用</td>
			</tr>
			<tr>
					<td>等很多个 channel</td>
					<td>fan-in 聚合（示例 9），<strong>别写 N 个 case</strong></td>
			</tr>
			<tr>
					<td>超时</td>
					<td><code>time.After</code>（低频）/ <code>time.NewTimer</code>（循环内）</td>
			</tr>
			<tr>
					<td>非阻塞尝试</td>
					<td><code>select + default</code></td>
			</tr>
			<tr>
					<td>优先级</td>
					<td>两层 select（示例 6），注意 default 空转</td>
			</tr>
			<tr>
					<td>动态启用/禁用分支</td>
					<td>把变量设为 <code>nil</code></td>
			</tr>
			<tr>
					<td>循环等待 + 退出</td>
					<td><code>for { select { case &lt;-ctx.Done(): return; ... } }</code></td>
			</tr>
			<tr>
					<td>关闭的 channel</td>
					<td><code>v, ok := &lt;-ch</code> 判断 ok，ok=false 时退出或置 nil</td>
			</tr>
			<tr>
					<td>广播退出</td>
					<td><code>close(done)</code> 或 <code>cancel()</code>，不要发 N 个值</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="七总结">七、总结</h2>
<p><strong>心智模型三句话</strong>：</p>
<ol>
<li><strong>select 是 channel 的 switch</strong>——对&quot;哪个 channel 就绪&quot;做分支，不是对值做分支</li>
<li><strong>就绪随机选</strong>——这是公平性保证，<strong>不要依赖分支顺序</strong></li>
<li><strong><code>default</code> 改阻塞语义</strong>——有 default 就是&quot;尝试一次&quot;，没 default 就是&quot;阻塞等待&quot;</li>
</ol>
<p><strong>四条铁律</strong>：</p>
<ul>
<li><strong>每个 case 都要有退出路径</strong>（<code>return</code> / <code>break label</code> / 置 nil），否则就是泄漏或死循环</li>
<li><strong>关闭的 channel 永远就绪</strong>，必须 <code>v, ok :=</code> 判断</li>
<li><strong><code>default</code> 要慎用</strong>，空转 CPU 是生产事故的常见源头</li>
<li><strong>循环里的 <code>time.After</code> 会泄漏 timer</strong>，用 <code>NewTimer + Reset</code></li>
</ul>
<p><strong>一句话</strong>：</p>
<blockquote>
<p><strong><code>select</code> 让你一个 goroutine 同时监听多个通道，是 Go 写&quot;响应式&quot;逻辑的基石——超时、取消、多路复用、非阻塞尝试、优先级调度，全都靠它。但它的随机性和&quot;关闭通道永远就绪&quot;两个特性，决定了你必须在每个 case 里显式处理&quot;这个分支是否还该活着&quot;，否则不是死锁就是 CPU 打满。</strong></p>
</blockquote>
<h2 id="十二select-底层原理简要">十二、select 底层原理（简要）</h2>
<p>编译器会把 <code>select</code> 转换为运行时调用：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 伪代码</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">selectgo</span><span class="p">(</span><span class="nx">cas0</span><span class="w"> </span><span class="o">*</span><span class="nx">scase</span><span class="p">,</span><span class="w"> </span><span class="nx">order0</span><span class="w"> </span><span class="o">*</span><span class="kt">uint16</span><span class="p">,</span><span class="w"> </span><span class="nx">ncases</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>流程：</strong></p>
<ol>
<li>生成 <code>scase</code> 数组，每个 case 描述一个 channel 操作</li>
<li>加锁所有涉及 channel（按地址排序避免死锁）</li>
<li>遍历所有 case，找出就绪的</li>
<li>若多个就绪 → 用 <code>fastrand</code> 随机选一个</li>
<li>若都未就绪 → 当前 goroutine 挂起到所有 channel 的等待队列</li>
<li>有 <code>default</code> 则直接走 default</li>
</ol>
<p><strong>要点：</strong></p>
<ul>
<li>有 <code>default</code>：不挂起，直接返回</li>
<li>无 <code>default</code>：将当前 goroutine 加入所有相关 channel 的 <code>sendq</code>/<code>recvq</code></li>
<li>被唤醒后从所有队列移除自己</li>
</ul>
<hr>
<h2 id="十三实战完整示例---带超时取消重试的任务调度">十三、实战：完整示例 - 带超时、取消、重试的任务调度</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;context&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;math/rand&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fetch</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">result</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 模拟网络请求</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nf">Duration</span><span class="p">(</span><span class="nx">rand</span><span class="p">.</span><span class="nf">Intn</span><span class="p">(</span><span class="mi">500</span><span class="p">))</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">result</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="s">&#34;数据-%d&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">id</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">result</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">r</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span><span class="w"> </span><span class="mi">2</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;整体超时，退出&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">data</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">fetch</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;任务&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;失败:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">continue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;任务&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;成功:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;全部完成&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="十四select-使用清单">十四、select 使用清单</h2>
<table>
	<thead>
			<tr>
					<th>场景</th>
					<th>写法</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>等待多个 channel</td>
					<td><code>select { case &lt;-a: case &lt;-b: }</code></td>
			</tr>
			<tr>
					<td>非阻塞读取</td>
					<td>加 <code>default</code></td>
			</tr>
			<tr>
					<td>超时控制</td>
					<td><code>case &lt;-time.After(d):</code></td>
			</tr>
			<tr>
					<td>循环超时</td>
					<td><code>time.NewTimer</code> + <code>Reset</code></td>
			</tr>
			<tr>
					<td>退出信号</td>
					<td><code>case &lt;-done:</code> 配合 <code>close(done)</code></td>
			</tr>
			<tr>
					<td>取消</td>
					<td><code>case &lt;-ctx.Done():</code></td>
			</tr>
			<tr>
					<td>禁用某 case</td>
					<td>把 channel 置 nil</td>
			</tr>
			<tr>
					<td>合并多 channel</td>
					<td>for + select + <code>ch = nil</code></td>
			</tr>
			<tr>
					<td>高优先级</td>
					<td>嵌套 select</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="十五最佳实践总结">十五、最佳实践总结</h2>
<ol>
<li>✅ 循环内用 <code>select</code> + <code>for</code> 持续监听</li>
<li>✅ 超时优先用 <code>context.Context</code>，<code>time.After</code> 仅用于单次</li>
<li>✅ 退出用 <code>close(done)</code> 广播，不要发送多个值</li>
<li>✅ <code>ch = nil</code> 是禁用 case 的惯用技巧</li>
<li>✅ 多 case 随机是特性，不是 bug；要优先级用嵌套 select</li>
<li>❌ 循环里别用 <code>time.After</code>，会泄漏定时器</li>
<li>❌ 别在 select 中 close 已经关闭的 channel</li>
<li>❌ 无限循环 + 无 default + 所有 channel 阻塞 → deadlock</li>
</ol>
<hr>
<p>进一步展开：</p>
<ul>
<li><strong>select 源码解析</strong>（<code>runtime.selectgo</code> 的具体实现）</li>
<li><strong>context 与 select 配合</strong>的深入用法</li>
<li><strong>高并发场景</strong>下 select 的性能优化与替代方案</li>
</ul>
]]></content:encoded></item><item><title>Goroutine Channel</title><link>https://blog.dart.xin/golang/go103-channel/</link><pubDate>Mon, 12 Oct 2026 09:31:48 +0800</pubDate><guid>https://blog.dart.xin/golang/go103-channel/</guid><description>Go Channel 详细讲解</description><content:encoded><![CDATA[<h1 id="go-channel-详细讲解">Go Channel 详细讲解</h1>
<p>Channel 是 Go 并发编程的核心，它是 goroutine 之间通信的管道，遵循 <strong>&ldquo;不要通过共享内存来通信，而要通过通信来共享内存&rdquo;</strong> 的设计哲学。</p>
<hr>
<h2 id="一channel-基础概念">一、Channel 基础概念</h2>
<h3 id="11-什么是-channel">1.1 什么是 Channel</h3>
<p>Channel 是一个<strong>类型化</strong>的管道，可以通过它发送和接收值：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">     </span><span class="c1">// 无缓冲 channel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">42</span><span class="w">                 </span><span class="c1">// 发送</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">x</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">                </span><span class="c1">// 接收</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="12-创建方式">1.2 创建方式</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 无缓冲 channel（同步）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch1</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 有缓冲 channel（异步，容量为 3）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch2</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 声明（nil channel，无法使用，会永久阻塞）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">ch3</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="13-三种操作">1.3 三种操作</h3>
<table>
	<thead>
			<tr>
					<th>操作</th>
					<th>语法</th>
					<th>说明</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>发送</td>
					<td><code>ch &lt;- v</code></td>
					<td>向 channel 写入</td>
			</tr>
			<tr>
					<td>接收</td>
					<td><code>v := &lt;-ch</code></td>
					<td>从 channel 读出</td>
			</tr>
			<tr>
					<td>关闭</td>
					<td><code>close(ch)</code></td>
					<td>关闭 channel</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="二无缓冲-vs-有缓冲">二、无缓冲 vs 有缓冲</h2>
<h3 id="21-无缓冲-channel同步">2.1 无缓冲 Channel（同步）</h3>
<p>无缓冲 channel 的发送和接收<strong>必须同时准备好</strong>，否则阻塞。这叫&quot;同步通信&quot;。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;准备发送&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="c1">// 阻塞，直到有人接收</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;发送完成&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;准备接收&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;接收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>输出：</strong></p>
<pre tabindex="0"><code>准备发送
准备接收
接收到: 100
发送完成
</code></pre><h3 id="22-有缓冲-channel异步">2.2 有缓冲 Channel（异步）</h3>
<p>缓冲区未满时可发送，未空时可接收。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;缓冲区:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">ch</span><span class="p">),</span><span class="w"> </span><span class="s">&#34;容量:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nb">cap</span><span class="p">(</span><span class="nx">ch</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ch &lt;- 4  // 会阻塞！缓冲区已满</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">)</span><span class="w"> </span><span class="c1">// 1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">)</span><span class="w"> </span><span class="c1">// 2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">)</span><span class="w"> </span><span class="c1">// 3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>关键 API：</strong></p>
<ul>
<li><code>len(ch)</code>：当前缓冲的元素个数</li>
<li><code>cap(ch)</code>：缓冲区容量</li>
</ul>
<hr>
<h2 id="三关闭-channel-与-range-遍历">三、关闭 Channel 与 range 遍历</h2>
<h3 id="31-关闭-channel">3.1 关闭 Channel</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>规则：</strong></p>
<ul>
<li>只能由<strong>发送方</strong>关闭</li>
<li>关闭后不能再发送（panic）</li>
<li>关闭后仍可接收剩余数据</li>
<li>对已关闭 channel 再关闭会 panic</li>
</ul>
<h3 id="32-接收的两个返回值">3.2 接收的两个返回值</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ok == true  : 正常接收</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ok == false : channel 已关闭且无数据</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="33-range-遍历">3.3 range 遍历</h3>
<p><code>range</code> 会自动检测 channel 关闭，收到所有数据后退出循环。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w"> </span><span class="c1">// 必须关闭，否则 range 会死锁</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;channel 已关闭&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="四单向-channel">四、单向 Channel</h2>
<p>用于限制 channel 的使用方向，提高类型安全。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w">    </span><span class="c1">// 双向</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">int</span><span class="w">  </span><span class="c1">// 只写</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w">  </span><span class="c1">// 只读</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 只写</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">send</span><span class="p">(</span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 只读</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">receive</span><span class="p">(</span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="nf">send</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">receive</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>双向 channel 可隐式转换为单向，反之不行。</p>
</blockquote>
<hr>
<h2 id="五select-多路复用">五、select 多路复用</h2>
<p><code>select</code> 让 goroutine 同时等待多个 channel 操作。</p>
<h3 id="51-基础用法">5.1 基础用法</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;来自 ch1:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;来自 ch2:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">ch3</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">10</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;写入 ch3&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;无就绪操作&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="52-超时控制">5.2 超时控制</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;超时了&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="53-非阻塞读取">5.3 非阻塞读取</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;没有数据&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="六常见模式">六、常见模式</h2>
<h3 id="61-生产者-消费者">6.1 生产者-消费者</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="o">*</span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">id</span><span class="o">*</span><span class="mi">10</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 3 个生产者</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">go</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">ch</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">wg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 等待所有生产者后关闭</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 消费者</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;消费:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="62-worker-pool工作池">6.2 Worker Pool（工作池）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">jobs</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="o">*</span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">jobs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">results</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">jobs</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">100</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">results</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">100</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 启动 3 个 worker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">w</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">w</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">jobs</span><span class="p">,</span><span class="w"> </span><span class="nx">results</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">wg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 发送任务</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">9</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">jobs</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">close</span><span class="p">(</span><span class="nx">jobs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 等 worker 结束再关闭结果</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">close</span><span class="p">(</span><span class="nx">results</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;结果:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="63-扇入扇出fan-in--fan-out">6.3 扇入扇出（Fan-in / Fan-out）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Fan-out：多个 goroutine 消费同一个 channel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Fan-in：多个 channel 合并到一个</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fanIn</span><span class="p">(</span><span class="nx">chs</span><span class="w"> </span><span class="o">...&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">out</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">chs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">out</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">v</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">close</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">out</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="64-用-channel-实现信号量">6.4 用 channel 实现信号量</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">sem</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{},</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="c1">// 最多 3 个并发</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sem</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}{}</span><span class="w">  </span><span class="c1">// 获取</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">i</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">sem</span><span class="w"> </span><span class="p">}()</span><span class="w"> </span><span class="c1">// 释放</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ... 业务逻辑</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}(</span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="七常见陷阱">七、常见陷阱</h2>
<h3 id="71-死锁">7.1 死锁</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">  </span><span class="c1">// 死锁！没人接收</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>报错：</strong> <code>fatal error: all goroutines are asleep - deadlock!</code></p>
<h3 id="72-向已关闭-channel-发送">7.2 向已关闭 channel 发送</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">  </span><span class="c1">// panic: send on closed channel</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="73-重复关闭">7.3 重复关闭</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">  </span><span class="c1">// panic: close of closed channel</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="74-nil-channel">7.4 nil channel</h3>
<p>对 nil channel 的发送和接收会<strong>永久阻塞</strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">      </span><span class="c1">// 永久阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">   </span><span class="c1">// 永久阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w"> </span><span class="c1">// panic</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>💡 <code>select</code> 中可用 nil channel 来&quot;禁用&quot;某个 case。</p>
</blockquote>
<h3 id="75-goroutine-泄漏">7.5 goroutine 泄漏</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">bad</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="c1">// 如果没人接收，goroutine 永久阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 忘了接收</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>解决：</strong> 使用缓冲、使用 context 取消、确保有接收方。</p>
<hr>
<h2 id="八channel-底层原理简要">八、Channel 底层原理（简要）</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">hchan</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">qcount</span><span class="w">   </span><span class="kt">uint</span><span class="w">           </span><span class="c1">// 当前元素数量</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">dataqsiz</span><span class="w"> </span><span class="kt">uint</span><span class="w">           </span><span class="c1">// 缓冲区大小</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">buf</span><span class="w">      </span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="w"> </span><span class="c1">// 环形缓冲区</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">elemsize</span><span class="w"> </span><span class="kt">uint16</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">closed</span><span class="w">   </span><span class="kt">uint32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sendx</span><span class="w">    </span><span class="kt">uint</span><span class="w">           </span><span class="c1">// 发送索引</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">recvx</span><span class="w">    </span><span class="kt">uint</span><span class="w">           </span><span class="c1">// 接收索引</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">recvq</span><span class="w">    </span><span class="nx">waitq</span><span class="w">          </span><span class="c1">// 等待接收的 goroutine 队列</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sendq</span><span class="w">    </span><span class="nx">waitq</span><span class="w">          </span><span class="c1">// 等待发送的 goroutine 队列</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">lock</span><span class="w">     </span><span class="nx">mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>要点：</strong></p>
<ol>
<li>有缓冲 channel 底层是<strong>环形队列</strong></li>
<li>等待的 goroutine 被挂到 <code>recvq</code> / <code>sendq</code></li>
<li>无缓冲发送时，直接把数据从 sender 拷贝到 receiver（&ldquo;接力&rdquo;）</li>
<li>所有操作都需要<strong>加锁</strong>，因此高频通信可考虑其他方式</li>
</ol>
<hr>
<h2 id="九channel-vs-其他同步机制">九、Channel vs 其他同步机制</h2>
<table>
	<thead>
			<tr>
					<th>场景</th>
					<th>推荐</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>goroutine 间传数据</td>
					<td>Channel</td>
			</tr>
			<tr>
					<td>只做互斥保护</td>
					<td><code>sync.Mutex</code></td>
			</tr>
			<tr>
					<td>等待多个任务完成</td>
					<td><code>sync.WaitGroup</code></td>
			</tr>
			<tr>
					<td>一次性通知</td>
					<td><code>close(ch)</code> 或 <code>sync.Once</code></td>
			</tr>
			<tr>
					<td>状态共享、缓存</td>
					<td><code>sync.Map</code> / 原子操作</td>
			</tr>
	</tbody>
</table>
<p><strong>性能提示：</strong></p>
<ul>
<li>无缓冲 channel ≈ 一次 goroutine 切换成本</li>
<li>有缓冲 channel 更快（减少阻塞）</li>
<li>极高吞吐场景可考虑 <code>sync/atomic</code> 或分片锁</li>
</ul>
<hr>
<h2 id="十最佳实践总结">十、最佳实践总结</h2>
<ol>
<li>✅ <strong>谁发送，谁关闭</strong>（谁负责 close）</li>
<li>✅ 用 <code>range</code> 或 <code>v, ok := &lt;-ch</code> 安全接收</li>
<li>✅ 使用 <code>select</code> + <code>time.After</code> 做超时</li>
<li>✅ 用 <code>context.Context</code> 处理取消信号</li>
<li>✅ Worker Pool 用 <code>WaitGroup</code> + <code>close</code> 组合</li>
<li>❌ 不要向已关闭的 channel 发送</li>
<li>❌ 不要依赖 channel 做广播（用 <code>close</code> 或 context）</li>
<li>❌ 不要用 channel 代替简单的互斥锁</li>
</ol>
<hr>
]]></content:encoded></item><item><title>基础知识 答题要点</title><link>https://blog.dart.xin/softexam/s102-basic/</link><pubDate>Mon, 12 Oct 2026 08:16:28 +0800</pubDate><guid>https://blog.dart.xin/softexam/s102-basic/</guid><description>&lt;p&gt;针对基础知识科目（上午选择题），以下按模块列出&lt;strong&gt;高频考点&lt;/strong&gt;，每条都标注了常考的具体形式和结论，方便直接背诵熟悉。&lt;/p&gt;
&lt;h3 id="一计算机组成与体系结构约6分"&gt;一、计算机组成与体系结构（约6分）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 码制与运算（必考）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;补码的核心作用&lt;/strong&gt;：将减法转化为加法，简化运算器设计；符号位与数值位统一处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;浮点数&lt;/strong&gt;：阶码决定&lt;strong&gt;范围&lt;/strong&gt;，尾数决定&lt;strong&gt;精度&lt;/strong&gt;。阶码越长范围越大，尾数越长精度越高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 流水线技术（必考计算）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;周期&lt;/strong&gt;：取指令、分析、执行三段中&lt;strong&gt;最长的那段&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行时间公式&lt;/strong&gt;：建立时间 +（指令数 − 1）× 流水线周期。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 校验码（反复出现）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;奇偶校验&lt;/strong&gt;：只能检错，不能纠错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRC&lt;/strong&gt;：可查错，不可纠错，用模2除法计算。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;海明码&lt;/strong&gt;：可查错也可纠错，校验位公式 2^r ≥ r + m + 1（(m)为数据位，(r)为校验位）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Cache与存储&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache作用&lt;/strong&gt;：缓解CPU与主存速度不匹配，依据&lt;strong&gt;局部性原理&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;层次化存储&lt;/strong&gt;：速度从高到低为 寄存器 → Cache → 主存 → 磁盘，容量相反。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="二操作系统约6分"&gt;二、操作系统（约6分）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 进程状态与PV操作（高频）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;三态转换&lt;/strong&gt;：运行→等待（等事件发生），等待→就绪（事件发生），就绪→运行（被调度）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PV操作经典结论&lt;/strong&gt;：互斥信号量初值为1；同步信号量初值通常为0。前趋图中，前驱节点执行V操作，后继节点执行P操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 死锁与银行家算法&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;死锁必要条件&lt;/strong&gt;：互斥、请求与保持、不剥夺、环路等待。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;银行家算法&lt;/strong&gt;：判断系统是否处于&lt;strong&gt;安全状态&lt;/strong&gt;，若存在安全序列则不会死锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 存储管理（必考计算）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;页式存储地址转换&lt;/strong&gt;：逻辑地址 = 页号 + 页内地址；通过页表将页号映射为物理块号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页面置换&lt;/strong&gt;：淘汰原则是“最近最久未使用”（LRU）或“先进先出”（FIFO），需判断是否发生&lt;strong&gt;缺页&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位示图&lt;/strong&gt;：用二进制位表示磁盘块占用情况，考查字号、位号的计算。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 文件系统&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;绝对路径与相对路径&lt;/strong&gt;：从根目录开始是绝对路径；从当前目录开始是相对路径。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;索引文件&lt;/strong&gt;：多级索引的地址项计算，常考直接地址、一级间接、二级间接的寻址范围。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="三数据结构与算法约9分"&gt;三、数据结构与算法（约9分）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 树与二叉树（必考）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;遍历&lt;/strong&gt;：前序（根左右）、中序（左根右）、后序（左右根）。已知中序 + 前序（或后序）可唯一确定二叉树。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;哈夫曼树&lt;/strong&gt;：带权路径长度最小的二叉树，用于哈夫曼编码（频率高的编码短）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二叉排序树&lt;/strong&gt;：左子树 &amp;lt; 根 &amp;lt; 右子树，中序遍历得到有序序列。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 图（高频）&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>针对基础知识科目（上午选择题），以下按模块列出<strong>高频考点</strong>，每条都标注了常考的具体形式和结论，方便直接背诵熟悉。</p>
<h3 id="一计算机组成与体系结构约6分">一、计算机组成与体系结构（约6分）</h3>
<p><strong>1. 码制与运算（必考）</strong></p>
<ul>
<li><strong>补码的核心作用</strong>：将减法转化为加法，简化运算器设计；符号位与数值位统一处理。</li>
<li><strong>浮点数</strong>：阶码决定<strong>范围</strong>，尾数决定<strong>精度</strong>。阶码越长范围越大，尾数越长精度越高。</li>
</ul>
<p><strong>2. 流水线技术（必考计算）</strong></p>
<ul>
<li><strong>周期</strong>：取指令、分析、执行三段中<strong>最长的那段</strong>。</li>
<li><strong>执行时间公式</strong>：建立时间 +（指令数 − 1）× 流水线周期。</li>
</ul>
<p><strong>3. 校验码（反复出现）</strong></p>
<ul>
<li><strong>奇偶校验</strong>：只能检错，不能纠错。</li>
<li><strong>CRC</strong>：可查错，不可纠错，用模2除法计算。</li>
<li><strong>海明码</strong>：可查错也可纠错，校验位公式 2^r ≥ r + m + 1（(m)为数据位，(r)为校验位）。</li>
</ul>
<p><strong>4. Cache与存储</strong></p>
<ul>
<li><strong>Cache作用</strong>：缓解CPU与主存速度不匹配，依据<strong>局部性原理</strong>。</li>
<li><strong>层次化存储</strong>：速度从高到低为 寄存器 → Cache → 主存 → 磁盘，容量相反。</li>
</ul>
<h3 id="二操作系统约6分">二、操作系统（约6分）</h3>
<p><strong>1. 进程状态与PV操作（高频）</strong></p>
<ul>
<li><strong>三态转换</strong>：运行→等待（等事件发生），等待→就绪（事件发生），就绪→运行（被调度）。</li>
<li><strong>PV操作经典结论</strong>：互斥信号量初值为1；同步信号量初值通常为0。前趋图中，前驱节点执行V操作，后继节点执行P操作。</li>
</ul>
<p><strong>2. 死锁与银行家算法</strong></p>
<ul>
<li><strong>死锁必要条件</strong>：互斥、请求与保持、不剥夺、环路等待。</li>
<li><strong>银行家算法</strong>：判断系统是否处于<strong>安全状态</strong>，若存在安全序列则不会死锁。</li>
</ul>
<p><strong>3. 存储管理（必考计算）</strong></p>
<ul>
<li><strong>页式存储地址转换</strong>：逻辑地址 = 页号 + 页内地址；通过页表将页号映射为物理块号。</li>
<li><strong>页面置换</strong>：淘汰原则是“最近最久未使用”（LRU）或“先进先出”（FIFO），需判断是否发生<strong>缺页</strong>。</li>
<li><strong>位示图</strong>：用二进制位表示磁盘块占用情况，考查字号、位号的计算。</li>
</ul>
<p><strong>4. 文件系统</strong></p>
<ul>
<li><strong>绝对路径与相对路径</strong>：从根目录开始是绝对路径；从当前目录开始是相对路径。</li>
<li><strong>索引文件</strong>：多级索引的地址项计算，常考直接地址、一级间接、二级间接的寻址范围。</li>
</ul>
<h3 id="三数据结构与算法约9分">三、数据结构与算法（约9分）</h3>
<p><strong>1. 树与二叉树（必考）</strong></p>
<ul>
<li><strong>遍历</strong>：前序（根左右）、中序（左根右）、后序（左右根）。已知中序 + 前序（或后序）可唯一确定二叉树。</li>
<li><strong>哈夫曼树</strong>：带权路径长度最小的二叉树，用于哈夫曼编码（频率高的编码短）。</li>
<li><strong>二叉排序树</strong>：左子树 &lt; 根 &lt; 右子树，中序遍历得到有序序列。</li>
</ul>
<p><strong>2. 图（高频）</strong></p>
<ul>
<li><strong>拓扑排序</strong>：用于AOV网，输出入度为0的顶点，检测是否有环。</li>
<li><strong>关键路径</strong>：从源点到汇点的最长路径，决定工程最短工期。</li>
</ul>
<p><strong>3. 查找与排序（必考复杂度与稳定性）</strong></p>
<ul>
<li><strong>二分查找</strong>：要求有序，时间复杂度 (O(\log n))。</li>
<li><strong>哈希冲突处理</strong>：开放定址法（线性探测）、链地址法。</li>
<li><strong>排序稳定性</strong>：稳定 → 冒泡、插入、归并、基数；不稳定 → 快排、堆排、选择、希尔。</li>
<li><strong>时间复杂度</strong>：快排平均 (O(n\log n))，最坏 (O(n^2))；堆排和归并恒为 (O(n\log n))。</li>
</ul>
<h3 id="四程序设计语言基础约6分">四、程序设计语言基础（约6分）</h3>
<p><strong>1. 编译与解释（高频）</strong></p>
<ul>
<li><strong>编译过程</strong>：词法分析 → 语法分析 → 语义分析 → 中间代码 → 优化 → 目标代码。</li>
<li><strong>编译 vs 解释</strong>：编译效率高但可移植性差；解释可移植性好但效率低。</li>
</ul>
<p><strong>2. 参数传递（必考）</strong></p>
<ul>
<li><strong>传值调用</strong>：形参是实参的副本，修改形参不影响实参。</li>
<li><strong>传址/引用调用</strong>：形参是实参的地址或别名，修改形参会改变实参。</li>
</ul>
<p><strong>3. 有限自动机与正规式</strong></p>
<ul>
<li><strong>DFA与NFA</strong>：DFA的每个状态对同一输入只有唯一后继；NFA可有多个后继。正规式与有限自动机等价。</li>
</ul>
<h3 id="五软件工程约11-13分分值最高模块">五、软件工程（约11-13分，分值最高模块）</h3>
<p><strong>1. 开发模型（必考对比）</strong></p>
<ul>
<li><strong>瀑布模型</strong>：阶段严格顺序，适合需求明确且稳定。</li>
<li><strong>敏捷开发</strong>：迭代、快速响应变化，如Scrum（每日站会）、XP（结对编程）。</li>
<li><strong>螺旋模型</strong>：强调风险分析，适合大型复杂项目。</li>
</ul>
<p><strong>2. 内聚与耦合（必考判定）</strong></p>
<ul>
<li><strong>内聚从高到低</strong>：功能内聚 &gt; 顺序内聚 &gt; 通信内聚 &gt; 过程内聚 &gt; 时间内聚 &gt; 逻辑内聚 &gt; 偶然内聚。</li>
<li><strong>耦合从低到高</strong>：非直接耦合 &gt; 数据耦合 &gt; 标记耦合 &gt; 控制耦合 &gt; 外部耦合 &gt; 公共耦合 &gt; 内容耦合。<strong>设计目标：高内聚，低耦合</strong>。</li>
</ul>
<p><strong>3. 测试与维护</strong></p>
<ul>
<li><strong>黑盒测试</strong>：等价类划分、边界值分析（不关心内部逻辑）。</li>
<li><strong>白盒测试</strong>：逻辑覆盖（语句、判定、条件、路径覆盖），<strong>McCabe环路复杂度</strong> = 判定节点数 + 1。</li>
<li><strong>维护类型</strong>：纠错性（修bug）、适应性（环境变化）、完善性（加功能）、预防性（提高可维护性）。</li>
</ul>
<h3 id="六面向对象约11分">六、面向对象（约11分）</h3>
<p><strong>1. UML图（必考识别）</strong></p>
<ul>
<li><strong>用例图</strong>：参与者与用例的关系（包含、扩展、泛化）。</li>
<li><strong>类图</strong>：类之间的关系——依赖、关联、聚合（空心菱形）、组合（实心菱形）、泛化（继承）。</li>
<li><strong>时序图</strong>：对象之间按时间顺序的消息交互，关注<strong>同步/异步消息</strong>。</li>
</ul>
<p><strong>2. 设计模式（必考分类与意图）</strong></p>
<ul>
<li><strong>创建型</strong>：单例（全局唯一实例）、工厂方法（延迟到子类创建）。</li>
<li><strong>结构型</strong>：适配器（转换接口）、装饰器（动态加职责）。</li>
<li><strong>行为型</strong>：观察者（一对多依赖通知）、策略（封装可互换算法）。</li>
</ul>
<h3 id="七数据库系统约6分">七、数据库系统（约6分）</h3>
<p><strong>1. E-R模型转关系模式（必考）</strong></p>
<ul>
<li><strong>1:1</strong>：任一方主键放入另一方作外键。</li>
<li><strong>1:n</strong>：<strong>n端</strong>的主键放入<strong>1端</strong>作外键。</li>
<li><strong>m:n</strong>：<strong>单独建一张关系表</strong>，主键为双方主键的组合。</li>
</ul>
<p><strong>2. 规范化理论（必考判定）</strong></p>
<ul>
<li><strong>1NF</strong>：属性不可再分。</li>
<li><strong>2NF</strong>：消除非主属性对码的<strong>部分函数依赖</strong>。</li>
<li><strong>3NF</strong>：消除非主属性对码的<strong>传递函数依赖</strong>。</li>
</ul>
<p><strong>3. 事务与并发</strong></p>
<ul>
<li><strong>ACID</strong>：原子性、一致性、隔离性、持久性。</li>
<li><strong>封锁协议</strong>：一级封锁（防丢失修改）、二级封锁（防脏读）、三级封锁（防不可重复读）。</li>
</ul>
<h3 id="八计算机网络与信息安全约5分">八、计算机网络与信息安全（约5分）</h3>
<p><strong>1. 网络协议</strong></p>
<ul>
<li><strong>OSI七层</strong>：物理→数据链路→网络→传输→会话→表示→应用。</li>
<li><strong>TCP/IP核心协议</strong>：IP（网络层）、TCP/UDP（传输层）、HTTP/FTP/SMTP（应用层）。</li>
<li><strong>子网划分</strong>：根据子网掩码计算网络地址、广播地址、可用主机数。</li>
</ul>
<p><strong>2. 加密与认证</strong></p>
<ul>
<li><strong>对称加密</strong>：加密解密同一密钥，速度快，如DES、AES。</li>
<li><strong>非对称加密</strong>：公钥加密、私钥解密（或反之），如RSA。</li>
<li><strong>数字签名</strong>：用<strong>私钥</strong>签名，<strong>公钥</strong>验证，保证不可否认性。</li>
<li><strong>数字证书</strong>：由CA签发，绑定公钥与身份。</li>
</ul>
<h3 id="九知识产权与标准化约3分">九、知识产权与标准化（约3分）</h3>
<p><strong>1. 知识产权（纯记忆，必拿分）</strong></p>
<ul>
<li><strong>著作权保护期</strong>：自然人作品为作者终生 + 死后50年；单位作品为首次发表后50年。</li>
<li><strong>归属判定</strong>：职务作品一般归单位（除非另有约定）；委托开发按合同约定。</li>
<li><strong>侵权认定</strong>：未经许可复制、发行、修改等。</li>
</ul>
<p><strong>2. 标准化</strong></p>
<ul>
<li><strong>标准代号</strong>：GB（国家标准）、GB/T（推荐性国标）、ISO（国际标准）。</li>
</ul>
<h3 id="十专业英语5分">十、专业英语（5分）</h3>
<ul>
<li><strong>题型</strong>：一篇计算机领域英文短文，挖5个空，选词填空。</li>
<li><strong>策略</strong>：不必精读全文，先看选项词性，再根据上下文语境和固定搭配判断。</li>
</ul>
<p>以上是基础知识科目中<strong>重复出现频率最高、可以直接背结论</strong>的考点。建议用真题验证：每做完一套选择题，把错题对应的知识点标记出来，反复回顾这些条目，直到形成条件反射。</p>
]]></content:encoded></item><item><title>应用技术科目 答题要点</title><link>https://blog.dart.xin/softexam/s103-applied/</link><pubDate>Mon, 12 Oct 2026 08:16:28 +0800</pubDate><guid>https://blog.dart.xin/softexam/s103-applied/</guid><description>&lt;p&gt;针对应用技术科目（下午题），以下按题型列出&lt;strong&gt;高频考点&lt;/strong&gt;，每条都标注了常考的&lt;strong&gt;具体形式&lt;/strong&gt;和&lt;strong&gt;答题要点&lt;/strong&gt;，方便直接背诵熟悉。&lt;/p&gt;
&lt;h3 id="一数据流图dfd-15分必拿题"&gt;一、数据流图（DFD）—— 15分必拿题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 四大元素识别&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外部实体&lt;/strong&gt;：题干中的“用户”“管理员”“客户”“银行系统”等角色词，是系统边界之外的人或系统。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据存储&lt;/strong&gt;：题干中的“XX文件”“XX表”“XX库”“XX清单”等，命名以名词加“表/文件”后缀。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加工&lt;/strong&gt;：用“动词+名词”命名（如“检查成绩表”“审定合格者”），不能只有输入没有输出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据流&lt;/strong&gt;：用名词短语命名（如“工资数据”），&lt;strong&gt;不能&lt;/strong&gt;用加工名（如“计算工资”）来命名数据流。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 数据平衡三原则（必考判断）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;父图与子图平衡&lt;/strong&gt;：子图的输入输出必须与父图中对应加工的输入输出&lt;strong&gt;完全一致&lt;/strong&gt;。子图多出来的数据流，说明父图缺了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加工自身平衡&lt;/strong&gt;：每个加工必须有输入有输出。只有入没有出是“数据黑洞”，只有出没有入是“数据奇迹”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;顶层图与0层图平衡&lt;/strong&gt;：顶层图的输入输出数量，0层图必须一一对应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 补充数据流的步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先对比顶层图和0层图，圈出不匹配的地方。&lt;/li&gt;
&lt;li&gt;再逐句阅读题干说明，标注实体和数据流去向。&lt;/li&gt;
&lt;li&gt;检查加工与存储的交互：&lt;strong&gt;读&lt;/strong&gt;数据时从存储&lt;strong&gt;出线&lt;/strong&gt;，&lt;strong&gt;写&lt;/strong&gt;数据时向存储&lt;strong&gt;入线&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 三条铁律（绝对违规）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据存储之间&lt;strong&gt;不能&lt;/strong&gt;直连，必须经过加工。&lt;/li&gt;
&lt;li&gt;外部实体之间&lt;strong&gt;不能&lt;/strong&gt;直连，必须经过加工。&lt;/li&gt;
&lt;li&gt;加工不能只有入或只有出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="二数据库设计--15分必拿题"&gt;二、数据库设计 —— 15分必拿题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. E-R图补全&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;联系类型判断&lt;/strong&gt;：根据题干描述判断是1:1、1:n还是m:n。注意“一位客户可以预订多个客房，一间客房在不同时间也可被多人预订”→ 客户与客房之间是&lt;strong&gt;多对多&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;联系属性补充&lt;/strong&gt;：如果联系本身有属性（如“订单”有“订单内容”“日期”），要标在联系上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. E-R图转关系模式（必考规则）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1:1&lt;/strong&gt;：将一方主键放入另一方作外键，或单独建表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1:n&lt;/strong&gt;：&lt;strong&gt;n端&lt;/strong&gt;的主键放入&lt;strong&gt;1端&lt;/strong&gt;作外键，或单独建表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;m:n&lt;/strong&gt;：&lt;strong&gt;必须单独建一张关系表&lt;/strong&gt;，主键为双方主键的&lt;strong&gt;组合&lt;/strong&gt;，联系自身的属性也放入该表。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 关系模式填空（主键/外键判定）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;找主键：从题干描述中找“唯一标识”的属性（如“员工号”“客房号”“身份证号”）。&lt;/li&gt;
&lt;li&gt;找外键：如果关系A中记录了关系B的主键，该属性就是外键。&lt;/li&gt;
&lt;li&gt;常考填空：(a)处填“供货商编号”或“团购点编号”等外键，(b)处填“客户电话”等参与联系的主键。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 范式判断（送分题）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1NF&lt;/strong&gt;：属性不可再分。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2NF&lt;/strong&gt;：消除非主属性对码的&lt;strong&gt;部分函数依赖&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3NF&lt;/strong&gt;：消除非主属性对码的&lt;strong&gt;传递函数依赖&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="三uml建模--15分必拿题"&gt;三、UML建模 —— 15分必拿题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 类图（重中之重）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关系判定&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;泛化（继承）&lt;/strong&gt;：空心三角实线，表示“is-a”，如 Customer 继承 Person。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聚合&lt;/strong&gt;：空心菱形，表示“has-a”但整体与部分可分离。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合&lt;/strong&gt;：实心菱形，表示“has-a”且部分随整体销毁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;依赖&lt;/strong&gt;：虚线箭头，一个类的变化影响另一个类。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多重度标注&lt;/strong&gt;：1对1填“1”，1对多填“1..&lt;em&gt;”或“0..&lt;/em&gt;”。注意“0”表示可以没有（如刚开业时没有订单）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 用例图&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;包含（include）&lt;/strong&gt;：基础用例&lt;strong&gt;一定&lt;/strong&gt;会执行被包含用例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扩展（extend）&lt;/strong&gt;：基础用例&lt;strong&gt;可能&lt;/strong&gt;执行扩展用例（有条件）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;泛化&lt;/strong&gt;：参与者之间或用例之间的继承关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 时序图&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关注对象之间的&lt;strong&gt;消息传递顺序&lt;/strong&gt;，补全缺失的消息名称或对象生命线。&lt;/li&gt;
&lt;li&gt;消息按时间顺序从上到下排列。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="四c语言算法设计--15分必拿题"&gt;四、C语言算法设计 —— 15分必拿题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. 四大算法策略识别（看题干）&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>针对应用技术科目（下午题），以下按题型列出<strong>高频考点</strong>，每条都标注了常考的<strong>具体形式</strong>和<strong>答题要点</strong>，方便直接背诵熟悉。</p>
<h3 id="一数据流图dfd-15分必拿题">一、数据流图（DFD）—— 15分必拿题</h3>
<p><strong>1. 四大元素识别</strong></p>
<ul>
<li><strong>外部实体</strong>：题干中的“用户”“管理员”“客户”“银行系统”等角色词，是系统边界之外的人或系统。</li>
<li><strong>数据存储</strong>：题干中的“XX文件”“XX表”“XX库”“XX清单”等，命名以名词加“表/文件”后缀。</li>
<li><strong>加工</strong>：用“动词+名词”命名（如“检查成绩表”“审定合格者”），不能只有输入没有输出。</li>
<li><strong>数据流</strong>：用名词短语命名（如“工资数据”），<strong>不能</strong>用加工名（如“计算工资”）来命名数据流。</li>
</ul>
<p><strong>2. 数据平衡三原则（必考判断）</strong></p>
<ul>
<li><strong>父图与子图平衡</strong>：子图的输入输出必须与父图中对应加工的输入输出<strong>完全一致</strong>。子图多出来的数据流，说明父图缺了。</li>
<li><strong>加工自身平衡</strong>：每个加工必须有输入有输出。只有入没有出是“数据黑洞”，只有出没有入是“数据奇迹”。</li>
<li><strong>顶层图与0层图平衡</strong>：顶层图的输入输出数量，0层图必须一一对应。</li>
</ul>
<p><strong>3. 补充数据流的步骤</strong></p>
<ul>
<li>先对比顶层图和0层图，圈出不匹配的地方。</li>
<li>再逐句阅读题干说明，标注实体和数据流去向。</li>
<li>检查加工与存储的交互：<strong>读</strong>数据时从存储<strong>出线</strong>，<strong>写</strong>数据时向存储<strong>入线</strong>。</li>
</ul>
<p><strong>4. 三条铁律（绝对违规）</strong></p>
<ul>
<li>数据存储之间<strong>不能</strong>直连，必须经过加工。</li>
<li>外部实体之间<strong>不能</strong>直连，必须经过加工。</li>
<li>加工不能只有入或只有出。</li>
</ul>
<h3 id="二数据库设计--15分必拿题">二、数据库设计 —— 15分必拿题</h3>
<p><strong>1. E-R图补全</strong></p>
<ul>
<li><strong>联系类型判断</strong>：根据题干描述判断是1:1、1:n还是m:n。注意“一位客户可以预订多个客房，一间客房在不同时间也可被多人预订”→ 客户与客房之间是<strong>多对多</strong>。</li>
<li><strong>联系属性补充</strong>：如果联系本身有属性（如“订单”有“订单内容”“日期”），要标在联系上。</li>
</ul>
<p><strong>2. E-R图转关系模式（必考规则）</strong></p>
<ul>
<li><strong>1:1</strong>：将一方主键放入另一方作外键，或单独建表。</li>
<li><strong>1:n</strong>：<strong>n端</strong>的主键放入<strong>1端</strong>作外键，或单独建表。</li>
<li><strong>m:n</strong>：<strong>必须单独建一张关系表</strong>，主键为双方主键的<strong>组合</strong>，联系自身的属性也放入该表。</li>
</ul>
<p><strong>3. 关系模式填空（主键/外键判定）</strong></p>
<ul>
<li>找主键：从题干描述中找“唯一标识”的属性（如“员工号”“客房号”“身份证号”）。</li>
<li>找外键：如果关系A中记录了关系B的主键，该属性就是外键。</li>
<li>常考填空：(a)处填“供货商编号”或“团购点编号”等外键，(b)处填“客户电话”等参与联系的主键。</li>
</ul>
<p><strong>4. 范式判断（送分题）</strong></p>
<ul>
<li><strong>1NF</strong>：属性不可再分。</li>
<li><strong>2NF</strong>：消除非主属性对码的<strong>部分函数依赖</strong>。</li>
<li><strong>3NF</strong>：消除非主属性对码的<strong>传递函数依赖</strong>。</li>
</ul>
<h3 id="三uml建模--15分必拿题">三、UML建模 —— 15分必拿题</h3>
<p><strong>1. 类图（重中之重）</strong></p>
<ul>
<li><strong>关系判定</strong>：
<ul>
<li><strong>泛化（继承）</strong>：空心三角实线，表示“is-a”，如 Customer 继承 Person。</li>
<li><strong>聚合</strong>：空心菱形，表示“has-a”但整体与部分可分离。</li>
<li><strong>组合</strong>：实心菱形，表示“has-a”且部分随整体销毁。</li>
<li><strong>依赖</strong>：虚线箭头，一个类的变化影响另一个类。</li>
</ul>
</li>
<li><strong>多重度标注</strong>：1对1填“1”，1对多填“1..<em>”或“0..</em>”。注意“0”表示可以没有（如刚开业时没有订单）。</li>
</ul>
<p><strong>2. 用例图</strong></p>
<ul>
<li><strong>包含（include）</strong>：基础用例<strong>一定</strong>会执行被包含用例。</li>
<li><strong>扩展（extend）</strong>：基础用例<strong>可能</strong>执行扩展用例（有条件）。</li>
<li><strong>泛化</strong>：参与者之间或用例之间的继承关系。</li>
</ul>
<p><strong>3. 时序图</strong></p>
<ul>
<li>关注对象之间的<strong>消息传递顺序</strong>，补全缺失的消息名称或对象生命线。</li>
<li>消息按时间顺序从上到下排列。</li>
</ul>
<h3 id="四c语言算法设计--15分必拿题">四、C语言算法设计 —— 15分必拿题</h3>
<p><strong>1. 四大算法策略识别（看题干）</strong></p>
<ul>
<li><strong>分治法</strong>：将问题分解为子问题，递归求解后合并（如二分查找、归并排序）。</li>
<li><strong>动态规划</strong>：题干中出现<strong>递归式</strong>（状态转移方程），用查表方式避免重复计算，通常自底向上。</li>
<li><strong>贪心法</strong>：每一步都选当前最优，不考虑全局，通常用于背包问题、活动安排。</li>
<li><strong>回溯法</strong>：递归+剪枝，常用于排列组合、八皇后、任务分配。代码中有“试错→递归→撤销”的模式。</li>
</ul>
<p><strong>2. 代码填空技巧</strong></p>
<ul>
<li><strong>第一个空</strong>通常是递归出口或初始化条件（如 <code>k == N</code>、<code>m &lt; k</code>）。</li>
<li><strong>循环边界</strong>：观察递归式中的参数范围，通常与子问题规模相关。</li>
<li><strong>递归调用</strong>：填 <code>k+1</code> 或 <code>k-1</code> 等推进参数。</li>
<li><strong>回溯操作</strong>：通常是将标志位<strong>恢复</strong>（如 <code>worker[i] = 0</code>、<code>task[k] = 0</code>）。</li>
<li><strong>注意</strong>：比较时不要漏掉等号（<code>&gt;=</code> 不能写成 <code>&gt;</code>），<code>==</code> 不能写成 <code>=</code>。</li>
</ul>
<p><strong>3. 常考算法类型</strong></p>
<ul>
<li>二分查找、快速排序、归并排序</li>
<li>最长公共子序列、背包问题、最短路径（Dijkstra/Floyd）</li>
<li>分治法日程安排（递归填充矩阵）</li>
</ul>
<h3 id="五设计模式选答题cjava-15分选做">五、设计模式选答题（C++/Java）—— 15分选做</h3>
<p><strong>1. 高频模式（背5-6种即可）</strong></p>
<ul>
<li><strong>创建型</strong>：单例（私有构造+静态实例）、工厂方法（延迟到子类创建）。</li>
<li><strong>结构型</strong>：适配器（转换接口）、装饰器（动态加职责）。</li>
<li><strong>行为型</strong>：观察者（注册+通知）、策略（封装可互换算法）、模板方法（父类定骨架，子类实现步骤）。</li>
</ul>
<p><strong>2. 代码填空技巧（不要通读全部代码）</strong></p>
<ul>
<li>看<strong>类名和方法名</strong>判断模式：类名含“Singleton”→找私有构造；含“Observer”→找 <code>register</code> 和 <code>notify</code>。</li>
<li>关注<strong>抽象类/接口</strong>的定义，填空通常涉及子类对父类方法的具体实现。</li>
<li>策略模式填空：通常是<strong>具体策略类</strong>的方法实现，以及<strong>上下文类</strong>中持有策略对象的成员变量。</li>
</ul>
<p><strong>3. 答题策略</strong></p>
<ul>
<li>选择自己<strong>最擅长的语言</strong>（Java或C++），考前集中练习真题的代码填空。</li>
<li>设计模式题能拿<strong>一半分</strong>就足以通过考试。</li>
</ul>
]]></content:encoded></item><item><title>软考考点梳理</title><link>https://blog.dart.xin/softexam/s101-outline/</link><pubDate>Mon, 12 Oct 2026 08:09:20 +0800</pubDate><guid>https://blog.dart.xin/softexam/s101-outline/</guid><description>总结软考或技术认证中的基础知识核心考点与答题技巧。</description><content:encoded><![CDATA[<p>软考中级软件设计师的考点可以分成两大科目来梳理：<strong>基础知识</strong>（上午选择题）侧重知识广度，<strong>应用技术</strong>（下午主观题）侧重动手设计能力。以下按模块分类列出，并附上简要解释。</p>
<h3 id="一基础知识科目考点">一、基础知识科目考点</h3>
<p>这科涵盖面广，但单点深度适中，重在概念准确和知识面完整。</p>
<p><strong>1. 计算机组成与体系结构</strong></p>
<ul>
<li><strong>数据表示</strong>：原码、反码、补码、移码的转换与运算，浮点数的阶码与尾数含义（阶码决定范围，尾数决定精度）。</li>
<li><strong>CPU与体系结构</strong>：CPU组成、指令系统、CISC与RISC的区别、流水线技术（吞吐率、加速比计算）。</li>
<li><strong>存储系统</strong>：Cache的作用与命中率、层次化存储体系、主存编址计算。</li>
<li><strong>校验码</strong>：奇偶校验、海明校验、CRC循环冗余校验的原理与检错纠错能力。</li>
</ul>
<p><strong>2. 操作系统</strong></p>
<ul>
<li><strong>进程管理</strong>：进程状态转换、信号量与PV操作（常结合前趋图考查）、死锁的产生条件与银行家算法。</li>
<li><strong>存储管理</strong>：页式存储、段页式存储的地址转换，页面置换算法（如LRU、FIFO），页面淘汰时的缺页判断。</li>
<li><strong>文件管理</strong>：树形目录结构、索引文件结构、位示图的计算。</li>
<li><strong>设备管理</strong>：磁盘调度算法（如SSTF、SCAN），I/O控制方式。</li>
</ul>
<p><strong>3. 数据结构与算法</strong>
这是软件的“立身之本”，既直接考选择题，也是下午算法题的基础。</p>
<ul>
<li><strong>线性结构</strong>：顺序表与链表的区别与操作，栈和队列的特性与应用场景。</li>
<li><strong>树与二叉树</strong>：二叉树的性质与遍历（前/中/后序），二叉排序树、最优二叉树（哈夫曼树）、平衡二叉树的概念。</li>
<li><strong>图</strong>：图的存储（邻接矩阵、邻接表），图的遍历（DFS/BFS），拓扑排序与关键路径。</li>
<li><strong>查找与排序</strong>：二分查找、哈希查找（冲突处理），各类排序算法（快排、堆排、归并等）的时间/空间复杂度与稳定性。</li>
<li><strong>算法策略</strong>：分治法、动态规划、贪心法、回溯法的基本思想与典型问题。</li>
</ul>
<p><strong>4. 程序设计语言基础</strong></p>
<ul>
<li><strong>编译与解释</strong>：编译过程（词法、语法、语义分析等阶段），编译型与解释型语言的区别（效率与可移植性的权衡）。</li>
<li><strong>语言机制</strong>：函数调用的参数传递方式（传值与传址），有限自动机与正规式（词法分析基础），表达式求值（后缀表达式）。</li>
</ul>
<p><strong>5. 软件工程</strong></p>
<ul>
<li><strong>开发模型</strong>：瀑布模型、敏捷开发、螺旋模型等的特点与适用场景，常作为高频考点。</li>
<li><strong>设计原则</strong>：模块的内聚与耦合（高内聚低耦合的具体类型），系统设计的基本原理。</li>
<li><strong>测试与维护</strong>：黑盒测试（等价类、边界值）与白盒测试（逻辑覆盖），McCabe环路复杂度计算，软件维护的类型（纠错性、完善性等）。</li>
</ul>
<p><strong>6. 面向对象</strong></p>
<ul>
<li><strong>核心概念</strong>：封装、继承、多态的含义与作用。</li>
<li><strong>UML</strong>：类图、用例图、活动图、状态图等各类图的识别与语义。</li>
<li><strong>设计模式</strong>：创建型、结构型、行为型模式的分类、意图与典型应用场景（如单例、工厂、观察者）。</li>
</ul>
<p><strong>7. 数据库系统</strong></p>
<ul>
<li><strong>数据模型</strong>：E-R模型（实体、属性、联系）向关系模式的转换。</li>
<li><strong>关系理论</strong>：函数依赖、键（主键/外键）、范式判定（1NF至3NF）、模式分解。</li>
<li><strong>SQL与并发</strong>：基础SQL查询语句，事务的ACID特性，并发控制与封锁协议。</li>
</ul>
<p><strong>8. 计算机网络与信息安全</strong></p>
<ul>
<li><strong>网络协议</strong>：OSI七层模型与TCP/IP协议族各层的功能与协议，子网划分与IP地址计算。</li>
<li><strong>安全基础</strong>：对称与非对称加密、数字签名、信息摘要、数字证书，常见网络威胁与防范措施。</li>
</ul>
<p><strong>9. 知识产权与标准化</strong></p>
<ul>
<li><strong>知识产权</strong>：软件著作权的保护对象、保护期限、归属判定与侵权认定。</li>
<li><strong>标准化</strong>：标准的分类与代号，常见信息技术标准的基本知识。</li>
</ul>
<p><strong>10. 专业英语</strong></p>
<ul>
<li><strong>英文阅读</strong>：正确阅读理解计算机领域的英文资料，通常为完形填空形式，考查专业术语理解与语境把握。</li>
</ul>
<hr>
<h3 id="二应用技术科目考点">二、应用技术科目考点</h3>
<p>这科是主观题，题型相对固定，重在把设计方法练成“肌肉记忆”。</p>
<p><strong>1. 数据流图（必答）</strong></p>
<ul>
<li><strong>绘制与补全</strong>：补充外部实体、数据存储、加工的名称，补全缺失的数据流。</li>
<li><strong>数据平衡</strong>：确保顶层图与下层图的数据流守恒，父图与子图的输入输出对应。</li>
</ul>
<p><strong>2. 数据库设计（必答）</strong></p>
<ul>
<li><strong>E-R建模</strong>：根据需求描述补充实体、属性、联系，标出联系类型（1:1、1:n、m:n）。</li>
<li><strong>关系模式转换</strong>：将E-R图转换为关系模式，指定主键与外键，判断范式级别。</li>
</ul>
<p><strong>3. UML建模（必答）</strong></p>
<ul>
<li><strong>图例补全</strong>：根据场景描述补充用例图、类图、时序图、活动图或状态图中的缺失元素（如类名、关系、消息）。</li>
</ul>
<p><strong>4. C语言算法设计（必答）</strong></p>
<ul>
<li><strong>代码填空</strong>：给定一段完整算法代码，挖去关键行，补全代码以符合算法逻辑。</li>
<li><strong>算法策略</strong>：通常考查分治法、动态规划、贪心法、回溯法的实现细节，注重循环边界、变量初始化与状态转移方程。</li>
</ul>
<p><strong>5. 程序设计选答（Java / C++ 二选一）</strong></p>
<ul>
<li><strong>语言特性</strong>：Java或C++的基本语法、面向对象特性（继承、多态）。</li>
<li><strong>设计模式实现</strong>：结合设计模式（如观察者、策略、工厂）进行代码填空或补全，考查模式在具体语言中的落地能力。</li>
</ul>
<p>备考时建议优先攻克“数据结构与算法、软件工程、数据库”这三条主线，它们既在上午题中占分高，又是下午设计题的底层工具。下午题中，数据流图、UML和算法设计几乎每年都出现，针对性练习真题的回报最确定。</p>
]]></content:encoded></item><item><title>Goroutine详细：sync.WaitGroup</title><link>https://blog.dart.xin/golang/go102-sync.waitgroup/</link><pubDate>Sun, 11 Oct 2026 22:51:47 +0800</pubDate><guid>https://blog.dart.xin/golang/go102-sync.waitgroup/</guid><description>&lt;h1 id="syncwaitgroup-是什么"&gt;sync.WaitGroup 是什么.&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt; 是 Go 标准库中用于&lt;strong&gt;等待一组 goroutine 完成&lt;/strong&gt;的同步原语。它解决的核心问题是：主 goroutine 需要知道其他多个 goroutine 何时全部执行完毕。&lt;/p&gt;
&lt;h2 id="基本用法"&gt;基本用法&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitGroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 计数 +1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 计数 -1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 做一些工作&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 阻塞，直到计数归零&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="为什么需要-add-和-done"&gt;为什么需要 Add 和 Done&lt;/h2&gt;
&lt;p&gt;WaitGroup 内部维护一个&lt;strong&gt;计数器&lt;/strong&gt;，它的语义是&amp;quot;还有多少个任务没完成&amp;quot;：&lt;/p&gt;</description><content:encoded><![CDATA[<h1 id="syncwaitgroup-是什么">sync.WaitGroup 是什么.</h1>
<p><code>sync.WaitGroup</code> 是 Go 标准库中用于<strong>等待一组 goroutine 完成</strong>的同步原语。它解决的核心问题是：主 goroutine 需要知道其他多个 goroutine 何时全部执行完毕。</p>
<h2 id="基本用法">基本用法</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">          </span><span class="c1">// 计数 +1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">id</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w"> </span><span class="c1">// 计数 -1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// 做一些工作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}(</span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w"> </span><span class="c1">// 阻塞，直到计数归零</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="为什么需要-add-和-done">为什么需要 Add 和 Done</h2>
<p>WaitGroup 内部维护一个<strong>计数器</strong>，它的语义是&quot;还有多少个任务没完成&quot;：</p>
<ul>
<li><strong><code>Add(delta)</code></strong>：告诉 WaitGroup &ldquo;我要开始 <code>delta</code> 个任务了&rdquo;，计数器增加。</li>
<li><strong><code>Done()</code></strong>：等价于 <code>Add(-1)</code>，表示&quot;我完成了 1 个任务&quot;，计数器减少。</li>
<li><strong><code>Wait()</code></strong>：阻塞当前 goroutine，直到计数器归零。</li>
</ul>
<p>拆成两个方法而不是一个&quot;注册+运行&quot;的 API，是因为<strong>任务的启动和完成在时间上是分离的</strong>：</p>
<ol>
<li>你需要在<strong>启动 goroutine 之前</strong>就 <code>Add</code>，否则可能出现 goroutine 已经跑完并 <code>Done</code> 了、但 <code>Add</code> 还没执行的竞态，导致计数器错乱甚至 <code>Wait</code> 永远不返回。</li>
<li><code>Done</code> 必须在<strong>任务真正结束时</strong>调用，通常配合 <code>defer</code>，保证即使 panic 或提前 return 也能正确减计数。</li>
</ol>
<h2 id="关键细节">关键细节</h2>
<p><strong>Add 必须在 Wait 之前、且在 goroutine 启动前调用</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 错误：可能在 Wait 已返回后才 Add</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">  </span><span class="c1">// 竞态！</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>正确写法是在 <code>go</code> 语句之前 <code>wg.Add(1)</code>。</p>
<p><strong>计数器不能变成负数</strong>：<code>Done</code> 调用次数多于 <code>Add</code> 会 panic。</p>
<p><strong>WaitGroup 不可复制</strong>：它内部含 <code>noCopy</code> 标记，传参要用指针 <code>*sync.WaitGroup</code>。</p>
<p><strong>复用</strong>：等 <code>Wait</code> 返回、计数器归零后，可以再次 <code>Add</code> 复用同一个 WaitGroup（但要注意此时不能有并发的 Wait）。</p>
<h2 id="和其他原语的区别">和其他原语的区别</h2>
<table>
	<thead>
			<tr>
					<th>原语</th>
					<th>用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>sync.WaitGroup</code></td>
					<td>等待一组任务全部完成</td>
			</tr>
			<tr>
					<td><code>sync.Mutex</code></td>
					<td>互斥访问共享资源</td>
			</tr>
			<tr>
					<td>channel</td>
					<td>更通用的通信/同步，可实现类似 WaitGroup 的效果但更灵活</td>
			</tr>
	</tbody>
</table>
<p>简单说，Add/Done 的设计是为了把&quot;声明任务数量&quot;和&quot;标记任务结束&quot;这两个时机分开，既保证计数正确，又让完成通知可以自然地用 <code>defer</code> 表达。</p>
]]></content:encoded></item><item><title>Goroutine详细：并发编程详细讲解</title><link>https://blog.dart.xin/golang/go101-goroutine/</link><pubDate>Sat, 10 Oct 2026 10:02:47 +0800</pubDate><guid>https://blog.dart.xin/golang/go101-goroutine/</guid><description>&lt;h1 id="go-并发编程详细讲解"&gt;Go 并发编程详细讲解&lt;/h1&gt;
&lt;p&gt;Go 的并发模型基于 &lt;strong&gt;CSP（Communicating Sequential Processes）&lt;/strong&gt;，核心思想是：&lt;strong&gt;不要通过共享内存来通信，而要通过通信来共享内存。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面从基础到进阶，系统讲解 Go 并发的所有核心概念，每个概念都配上可运行的示例。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一goroutine轻量级线程"&gt;一、Goroutine：轻量级线程&lt;/h2&gt;
&lt;h3 id="11-基础用法"&gt;1.1 基础用法&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;world&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 启动一个 goroutine&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 主 goroutine 继续执行&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 等 goroutine 跑完&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键点&lt;/strong&gt;：&lt;/p&gt;</description><content:encoded><![CDATA[<h1 id="go-并发编程详细讲解">Go 并发编程详细讲解</h1>
<p>Go 的并发模型基于 <strong>CSP（Communicating Sequential Processes）</strong>，核心思想是：<strong>不要通过共享内存来通信，而要通过通信来共享内存。</strong></p>
<p>下面从基础到进阶，系统讲解 Go 并发的所有核心概念，每个概念都配上可运行的示例。</p>
<hr>
<h2 id="一goroutine轻量级线程">一、Goroutine：轻量级线程</h2>
<h3 id="11-基础用法">1.1 基础用法</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">say</span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="nf">say</span><span class="p">(</span><span class="s">&#34;world&#34;</span><span class="p">)</span><span class="w">   </span><span class="c1">// 启动一个 goroutine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">say</span><span class="p">(</span><span class="s">&#34;hello&#34;</span><span class="p">)</span><span class="w">      </span><span class="c1">// 主 goroutine 继续执行</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w"> </span><span class="c1">// 等 goroutine 跑完</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>关键点</strong>：</p>
<ul>
<li><code>go</code> 关键字启动一个 goroutine，<strong>立即返回</strong>，不阻塞。</li>
<li>主 goroutine 结束时，<strong>所有其他 goroutine 都会被强制终止</strong>。所以 <code>main</code> 里需要 <code>Sleep</code> 或同步机制等待。</li>
<li>Goroutine 初始栈只有 <strong>2KB</strong>，可动态增长，所以可以轻松启动数十万个。</li>
</ul>
<h3 id="12-为什么-goroutine-比线程轻">1.2 为什么 goroutine 比线程轻</h3>
<table>
	<thead>
			<tr>
					<th>对比项</th>
					<th>线程</th>
					<th>Goroutine</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>初始栈大小</td>
					<td>1-8 MB</td>
					<td>2 KB</td>
			</tr>
			<tr>
					<td>切换成本</td>
					<td>内核态切换，微秒级</td>
					<td>用户态切换，纳秒级</td>
			</tr>
			<tr>
					<td>数量级</td>
					<td>几千</td>
					<td>百万级</td>
			</tr>
			<tr>
					<td>调度</td>
					<td>操作系统</td>
					<td>Go runtime（GMP 模型）</td>
			</tr>
	</tbody>
</table>
<h3 id="13-用-waitgroup-等待">1.3 用 WaitGroup 等待</h3>
<p><code>time.Sleep</code> 不靠谱，生产代码用 <code>sync.WaitGroup</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="s">&#34;sync&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">          </span><span class="c1">// 计数 +1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w"> </span><span class="c1">// 计数 -1（defer 保证一定执行）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;worker&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}(</span><span class="nx">i</span><span class="p">)</span><span class="w">               </span><span class="c1">// 注意：i 作为参数传入，避免闭包捕获问题</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">              </span><span class="c1">// 阻塞直到计数归零</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;全部完成&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>易错点</strong>：不要写成 <code>go func() { ... }(i)</code> 里的 <code>i</code> 直接捕获。Go 1.22 之前循环变量是共享的，需要用参数传入或 <code>i := i</code> 复制。Go 1.22 起每次迭代都是新变量，但显式传参更清晰。</p>
<hr>
<h2 id="二channelgoroutine-之间的通信管道">二、Channel：goroutine 之间的通信管道</h2>
<p>Channel 是 Go 并发的核心。可以把它想象成一条<strong>类型安全的队列</strong>。</p>
<h3 id="21-无缓冲-channel">2.1 无缓冲 Channel</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">   </span><span class="c1">// 无缓冲</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">42</span><span class="w">           </span><span class="c1">// 发送：阻塞直到有人接收</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">              </span><span class="c1">// 接收：阻塞直到有人发送</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">         </span><span class="c1">// 42</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>特点</strong>：发送和接收<strong>必须同时就绪</strong>，否则阻塞。这叫<strong>同步通信</strong>。</p>
<h3 id="22-有缓冲-channel">2.2 有缓冲 Channel</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">  </span><span class="c1">// 缓冲大小 3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">                  </span><span class="c1">// 不阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">2</span><span class="w">                  </span><span class="c1">// 不阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">3</span><span class="w">                  </span><span class="c1">// 不阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// ch &lt;- 4               // 会阻塞，缓冲满了</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">)</span><span class="w">        </span><span class="c1">// 1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">)</span><span class="w">        </span><span class="c1">// 2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">)</span><span class="w">        </span><span class="c1">// 3</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>特点</strong>：缓冲未满时发送不阻塞，缓冲非空时接收不阻塞。缓冲满了发送阻塞，缓冲空了接收阻塞。</p>
<h3 id="23-关闭-channel">2.3 关闭 Channel</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 方式一：range 自动结束</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">   </span><span class="c1">// 1, 2，然后循环自动结束</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 方式二：逗号 ok 判断</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ok</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;channel 已关闭&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>规则</strong>：</p>
<ul>
<li>只有<strong>发送方</strong>应该关闭 channel，接收方关闭会 panic。</li>
<li>关闭后<strong>不能发送</strong>，但<strong>可以继续接收剩余数据</strong>。</li>
<li>重复关闭会 panic。</li>
<li>向 nil channel 发送/接收会永久阻塞。</li>
</ul>
<h3 id="24-channel-作为同步信号">2.4 Channel 作为同步信号</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">done</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{})</span><span class="w">   </span><span class="c1">// struct{} 不占内存</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 做一些工作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;工作完成&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nb">close</span><span class="p">(</span><span class="nx">done</span><span class="p">)</span><span class="w">                </span><span class="c1">// 或 done &lt;- struct{}{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&lt;-</span><span class="nx">done</span><span class="w">                         </span><span class="c1">// 等待完成</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>struct{}</code> 是零大小类型，作为纯信号使用时比 <code>bool</code> 更省内存。</p>
<h3 id="25-channel-方向">2.5 Channel 方向</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">out</span><span class="w"> </span><span class="kd">chan</span><span class="o">&lt;-</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">   </span><span class="c1">// 只发送</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">out</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nb">close</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">consumer</span><span class="p">(</span><span class="nx">in</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">    </span><span class="c1">// 只接收</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">in</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="nf">producer</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">consumer</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>方向约束让编译器帮你检查错误，是良好的 API 设计习惯。</p>
<hr>
<h2 id="三select多路复用">三、Select：多路复用</h2>
<p><code>select</code> 让一个 goroutine 同时等待多个 channel 操作。</p>
<h3 id="31-基础用法">3.1 基础用法</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到 ch1:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">ch2</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">42</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;发送到 ch2&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;超时&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;都没有就绪，立即执行&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>规则</strong>：</p>
<ul>
<li>多个 case 同时就绪时，<strong>随机选一个</strong>（防止饥饿）。</li>
<li>有 <code>default</code> 时<strong>不阻塞</strong>；没有 <code>default</code> 时<strong>阻塞</strong>直到某个 case 就绪。</li>
</ul>
<h3 id="32-超时控制">3.2 超时控制</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fetchData</span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">  </span><span class="c1">// 模拟慢操作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="s">&#34;data&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="s">&#34;&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;超时&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="33-非阻塞接收">3.3 非阻塞接收</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">case</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;没有数据&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="34-退出信号">3.4 退出信号</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">stop</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">stop</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;收到退出信号&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="c1">// 正常工作</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">100</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="四context取消与超时">四、Context：取消与超时</h2>
<p><code>context</code> 是 Go 官方推荐的<strong>跨 goroutine 传递取消信号和超时</strong>的机制。</p>
<h3 id="41-四种-context">4.1 四种 Context</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">()</span><span class="w">                          </span><span class="c1">// 根 context，永不取消</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">parent</span><span class="p">)</span><span class="w">            </span><span class="c1">// 手动取消</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">parent</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">  </span><span class="c1">// 超时取消</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithDeadline</span><span class="p">(</span><span class="nx">parent</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">().</span><span class="nf">Add</span><span class="p">(</span><span class="mi">3</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">))</span><span class="w"> </span><span class="c1">// 截止时间</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">                                        </span><span class="c1">// 必须调用，防止资源泄漏</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="42-取消传播">4.2 取消传播</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithCancel</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;A&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;B&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">cancel</span><span class="p">()</span><span class="w">   </span><span class="c1">// 取消信号传播到所有子 goroutine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">worker</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;退出:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;工作中&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">500</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Millisecond</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="43-配合-http-请求">4.3 配合 HTTP 请求</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">callAPI</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">req</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nf">NewRequestWithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;GET&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;https://api.example.com&#34;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">resp</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">DefaultClient</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">  </span><span class="c1">// 如果 ctx 被取消，err 会是 context.Canceled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">.</span><span class="nf">Close</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="44-context-使用原则">4.4 Context 使用原则</h3>
<table>
	<thead>
			<tr>
					<th>原则</th>
					<th>说明</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>作为函数<strong>第一个参数</strong></td>
					<td><code>func Do(ctx context.Context, ...)</code></td>
			</tr>
			<tr>
					<td>不放进 struct</td>
					<td>Context 应该随调用链传递，不是对象状态</td>
			</tr>
			<tr>
					<td>只用于<strong>取消、超时、传值</strong></td>
					<td>不要用它传业务参数</td>
			</tr>
			<tr>
					<td><code>cancel()</code> 必须调用</td>
					<td><code>defer cancel()</code> 防止 goroutine 泄漏</td>
			</tr>
			<tr>
					<td><code>context.Value</code> 少用</td>
					<td>只用于 request-scoped 的元数据（如 traceID、用户身份）</td>
			</tr>
	</tbody>
</table>
<hr>
<h2 id="五sync-包共享内存同步">五、sync 包：共享内存同步</h2>
<p>虽然 Go 提倡用 channel 通信，但有些场景用共享内存更自然。</p>
<h3 id="51-mutex互斥锁">5.1 Mutex：互斥锁</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Counter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="w">  </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Counter</span><span class="p">)</span><span class="w"> </span><span class="nf">Inc</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Counter</span><span class="p">)</span><span class="w"> </span><span class="nf">Value</span><span class="p">()</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="52-rwmutex读写锁">5.2 RWMutex：读写锁</h3>
<p>读多写少的场景，允许多个读者并发：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Cache</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">RWMutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">m</span><span class="w">  </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Cache</span><span class="p">)</span><span class="w"> </span><span class="nf">Get</span><span class="p">(</span><span class="nx">k</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">bool</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RLock</span><span class="p">()</span><span class="w">          </span><span class="c1">// 读锁，可多个 goroutine 同时持有</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">RUnlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="p">[</span><span class="nx">k</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Cache</span><span class="p">)</span><span class="w"> </span><span class="nf">Set</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">           </span><span class="c1">// 写锁，独占</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">m</span><span class="p">[</span><span class="nx">k</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">v</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="53-once只执行一次">5.3 Once：只执行一次</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">instance</span><span class="w"> </span><span class="o">*</span><span class="nx">DB</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">once</span><span class="w">     </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Once</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">GetDB</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="nx">DB</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">once</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">instance</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">DB</span><span class="p">{}</span><span class="w">   </span><span class="c1">// 只执行一次，其他 goroutine 阻塞等待</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">instance</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>单例模式的标准实现，比手动加锁更安全。</p>
<h3 id="54-syncmap并发安全的-map">5.4 sync.Map：并发安全的 Map</h3>
<p>普通 map 并发读写会 panic。<code>sync.Map</code> 适合<strong>读多写少、key 相对固定</strong>的场景：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Map</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">m</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="s">&#34;key&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;value&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">v</span><span class="p">,</span><span class="w"> </span><span class="nx">ok</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nf">Load</span><span class="p">(</span><span class="s">&#34;key&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">m</span><span class="p">.</span><span class="nf">Delete</span><span class="p">(</span><span class="s">&#34;key&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">m</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>注意</strong>：<code>sync.Map</code> 不是万能替代。大部分场景用 <code>sync.RWMutex + map</code> 性能更好。<code>sync.Map</code> 只在特定场景（如缓存、注册表）有优势。</p>
<h3 id="55-syncpool对象复用">5.5 sync.Pool：对象复用</h3>
<p>减少 GC 压力，适合频繁创建销毁的对象：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">bufPool</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Pool</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">New</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">any</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">process</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">buf</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bufPool</span><span class="p">.</span><span class="nf">Get</span><span class="p">().(</span><span class="o">*</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">buf</span><span class="p">.</span><span class="nf">Reset</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">bufPool</span><span class="p">.</span><span class="nf">Put</span><span class="p">(</span><span class="nx">buf</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">buf</span><span class="p">.</span><span class="nf">WriteString</span><span class="p">(</span><span class="s">&#34;data&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>注意</strong>：<code>sync.Pool</code> 里的对象可能在任意 GC 时被回收，不能用于持久化状态。</p>
<hr>
<h2 id="六经典并发模式">六、经典并发模式</h2>
<h3 id="61-worker-pool工作池">6.1 Worker Pool（工作池）</h3>
<p>控制并发数量，避免资源耗尽：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">workerPool</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">jobs</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">100</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">results</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">100</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 启动 3 个 worker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">w</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">w</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">for</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">jobs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">results</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">j</span><span class="w">   </span><span class="c1">// 处理任务</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 发送任务</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">jobs</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">jobs</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 等待 worker 完成后关闭 results</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">results</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 收集结果</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">results</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">r</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="62-fan-out--fan-in扇出扇入">6.2 Fan-out / Fan-in（扇出扇入）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Fan-out：一个输入分发给多个 worker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// Fan-in：多个 worker 输出汇聚成一个 channel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fanIn</span><span class="p">(</span><span class="nx">chs</span><span class="w"> </span><span class="o">...&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">out</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">chs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">c</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="nx">out</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">v</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">out</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="63-pipeline流水线">6.3 Pipeline（流水线）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">gen</span><span class="p">(</span><span class="nx">nums</span><span class="w"> </span><span class="o">...</span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">out</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">nums</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">out</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">out</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">square</span><span class="p">(</span><span class="nx">in</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">&lt;-</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">out</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">for</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">in</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">out</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">n</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nb">close</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">out</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// gen → square → 输出</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nf">square</span><span class="p">(</span><span class="nf">gen</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">)</span><span class="w">   </span><span class="c1">// 1, 4, 9, 16, 25</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="64-限流semaphore">6.4 限流（Semaphore）</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">limited</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sem</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{},</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">   </span><span class="c1">// 最多 3 个并发</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">sem</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}{}</span><span class="w">        </span><span class="c1">// 获取令牌</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">sem</span><span class="w"> </span><span class="p">}()</span><span class="w"> </span><span class="c1">// 释放令牌</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;处理&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">n</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}(</span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>用 <code>golang.org/x/sync/semaphore</code> 可以支持带权重的限流。</p>
<h3 id="65-errgroup带错误传播的并发">6.5 errgroup：带错误传播的并发</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="s">&#34;golang.org/x/sync/errgroup&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">fetchAll</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">urls</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">g</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">errgroup</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">urls</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">url</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">url</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">g</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nf">fetch</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">url</span><span class="p">)</span><span class="w">   </span><span class="c1">// 任一失败会取消 ctx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">g</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">   </span><span class="c1">// 返回第一个错误</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>errgroup</code> 是并发任务编排的利器，比手写 WaitGroup + channel 简洁得多。</p>
<hr>
<h2 id="七数据竞争与检测">七、数据竞争与检测</h2>
<h3 id="71-竞争示例">7.1 竞争示例</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="kd">var</span><span class="w"> </span><span class="nx">wg</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">WaitGroup</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">defer</span><span class="w"> </span><span class="nx">wg</span><span class="p">.</span><span class="nf">Done</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">n</span><span class="o">++</span><span class="w">   </span><span class="c1">// 数据竞争！</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">wg</span><span class="p">.</span><span class="nf">Wait</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="w">   </span><span class="c1">// 通常小于 1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="72-用--race-检测">7.2 用 -race 检测</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">go run -race main.go
</span></span><span class="line"><span class="cl">go <span class="nb">test</span> -race ./...
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>-race</code> 会在运行时检测数据竞争，输出冲突的读写位置。<strong>生产构建不要开 -race</strong>（性能损失约 10 倍），但开发和 CI 一定要开。</p>
<h3 id="73-修复方式">7.3 修复方式</h3>
<p><strong>方式一：加锁</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">mu</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Lock</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">n</span><span class="o">++</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">mu</span><span class="p">.</span><span class="nf">Unlock</span><span class="p">()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>方式二：用 atomic</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="kt">int64</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">atomic</span><span class="p">.</span><span class="nf">AddInt64</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">n</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>方式三：用 channel</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">n</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ch</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="八gmp-调度模型">八、GMP 调度模型</h2>
<p>理解 Go 调度的核心：</p>
<table>
	<thead>
			<tr>
					<th>组件</th>
					<th>含义</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>G</strong> (Goroutine)</td>
					<td>用户态协程，包含栈、指令指针等</td>
			</tr>
			<tr>
					<td><strong>M</strong> (Machine)</td>
					<td>操作系统线程</td>
			</tr>
			<tr>
					<td><strong>P</strong> (Processor)</td>
					<td>逻辑处理器，持有可运行 G 的本地队列</td>
			</tr>
	</tbody>
</table>
<p><strong>调度流程</strong>：</p>
<ol>
<li>每个 P 有一个本地运行队列，M 绑定 P 后从中取 G 执行。</li>
<li>本地队列空时，M 会从全局队列或其他 P 的队列<strong>窃取</strong> G（work stealing）。</li>
<li>G 遇到阻塞系统调用时，M 会释放 P，让其他 M 绑定 P 继续执行。</li>
<li>网络 I/O 由 netpoller 处理，不阻塞 M。</li>
</ol>
<p><strong>关键参数</strong>：</p>
<ul>
<li><code>GOMAXPROCS</code>：P 的数量，默认等于 CPU 核数。</li>
<li><code>runtime.NumGoroutine()</code>：当前 goroutine 数量，用于排查泄漏。</li>
</ul>
<hr>
<h2 id="九常见坑与最佳实践">九、常见坑与最佳实践</h2>
<h3 id="91-goroutine-泄漏">9.1 Goroutine 泄漏</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 错误：goroutine 永远阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">bad</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">42</span><span class="w">   </span><span class="c1">// 没人接收，永久阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// 函数返回，goroutine 泄漏</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 正确：用 context 或 buffered channel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">good</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">   </span><span class="c1">// 缓冲 1，发送不阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">42</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="92-循环变量捕获">9.2 循环变量捕获</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Go 1.22 之前：所有 goroutine 打印 5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 修复方式一：传参</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">n</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="w"> </span><span class="p">}(</span><span class="nx">i</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 修复方式二：局部变量</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">i</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Go 1.22 起循环变量每次迭代都是新的，不再有此问题。</p>
<h3 id="93-关闭已关闭的-channel">9.3 关闭已关闭的 Channel</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 错误：重复关闭 panic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">   </span><span class="c1">// panic: close of closed channel</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>用 <code>sync.Once</code> 保证只关一次：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">once</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Once</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">once</span><span class="p">.</span><span class="nf">Do</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w"> </span><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="94-向已关闭的-channel-发送">9.4 向已关闭的 Channel 发送</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nb">close</span><span class="p">(</span><span class="nx">ch</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">1</span><span class="w">   </span><span class="c1">// panic: send on closed channel</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>规则</strong>：只有发送方关闭，且关闭后不再发送。</p>
<h3 id="95-用-buffered-channel-避免泄漏">9.5 用 Buffered Channel 避免泄漏</h3>
<p>如果 goroutine 发送后可能没人接收，用缓冲 channel：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">ch</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">   </span><span class="c1">// 缓冲 1，发送方不会阻塞</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">go</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">ch</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="mi">42</span><span class="w"> </span><span class="p">}()</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="96-超时与重试">9.6 超时与重试</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">retry</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">fn</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">fn</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">time</span><span class="p">.</span><span class="nf">After</span><span class="p">(</span><span class="nx">time</span><span class="p">.</span><span class="nf">Duration</span><span class="p">(</span><span class="nx">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">):</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;重试 3 次仍失败&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="十决策指南用-channel-还是-mutex">十、决策指南：用 Channel 还是 Mutex</h2>
<table>
	<thead>
			<tr>
					<th>场景</th>
					<th>推荐</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>传递数据所有权</td>
					<td>Channel</td>
			</tr>
			<tr>
					<td>分发任务给多个 worker</td>
					<td>Channel</td>
			</tr>
			<tr>
					<td>通知事件（signal）</td>
					<td>Channel</td>
			</tr>
			<tr>
					<td>保护共享状态（计数器、缓存）</td>
					<td>Mutex / RWMutex</td>
			</tr>
			<tr>
					<td>单例初始化</td>
					<td>sync.Once</td>
			</tr>
			<tr>
					<td>对象池</td>
					<td>sync.Pool</td>
			</tr>
			<tr>
					<td>并发 map</td>
					<td>sync.Map 或 RWMutex + map</td>
			</tr>
	</tbody>
</table>
<p><strong>简单判断</strong>：如果你在问&quot;谁拥有这份数据&quot;，用 Channel；如果你在问&quot;谁能同时访问这份数据&quot;，用 Mutex。</p>
<hr>
<h2 id="十一ai-应用中的并发模式">十一、AI 应用中的并发模式</h2>
<p>结合你之前的项目，几个实际场景：</p>
<h3 id="111-流式响应--超时">11.1 流式响应 + 超时</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">streamWithTimeout</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">client</span><span class="w"> </span><span class="o">*</span><span class="nx">llm</span><span class="p">.</span><span class="nx">Client</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">cancel</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">defer</span><span class="w"> </span><span class="nf">cancel</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nf">ChatStream</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">msgs</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">tok</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Print</span><span class="p">(</span><span class="nx">tok</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="112-并发调用多个模型对比">11.2 并发调用多个模型对比</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">compareModels</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">prompt</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">models</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">g</span><span class="p">,</span><span class="w"> </span><span class="nx">ctx</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">errgroup</span><span class="p">.</span><span class="nf">WithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">results</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">sync</span><span class="p">.</span><span class="nx">Map</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">models</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">g</span><span class="p">.</span><span class="nf">Go</span><span class="p">(</span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">client</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">llm</span><span class="p">.</span><span class="nf">NewClient</span><span class="p">(</span><span class="nx">baseURL</span><span class="p">,</span><span class="w"> </span><span class="nx">apiKey</span><span class="p">,</span><span class="w"> </span><span class="nx">m</span><span class="p">,</span><span class="w"> </span><span class="nx">httpClient</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">reply</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">client</span><span class="p">.</span><span class="nf">Chat</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="p">[]</span><span class="nx">llm</span><span class="p">.</span><span class="nx">Message</span><span class="p">{{</span><span class="nx">Role</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;user&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Content</span><span class="p">:</span><span class="w"> </span><span class="nx">prompt</span><span class="p">}})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">				</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">results</span><span class="p">.</span><span class="nf">Store</span><span class="p">(</span><span class="nx">m</span><span class="p">,</span><span class="w"> </span><span class="nx">reply</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">g</span><span class="p">.</span><span class="nf">Wait</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">out</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">results</span><span class="p">.</span><span class="nf">Range</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">k</span><span class="p">,</span><span class="w"> </span><span class="nx">v</span><span class="w"> </span><span class="kt">any</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">out</span><span class="p">[</span><span class="nx">k</span><span class="p">.(</span><span class="kt">string</span><span class="p">)]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">v</span><span class="p">.(</span><span class="kt">string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">out</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="113-token-计数限流">11.3 Token 计数限流</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">TokenLimiter</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">sem</span><span class="w"> </span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">NewTokenLimiter</span><span class="p">(</span><span class="nx">maxConcurrent</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="nx">TokenLimiter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">TokenLimiter</span><span class="p">{</span><span class="nx">sem</span><span class="p">:</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">chan</span><span class="w"> </span><span class="kd">struct</span><span class="p">{},</span><span class="w"> </span><span class="nx">maxConcurrent</span><span class="p">)}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">l</span><span class="w"> </span><span class="o">*</span><span class="nx">TokenLimiter</span><span class="p">)</span><span class="w"> </span><span class="nf">Acquire</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">select</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="nx">l</span><span class="p">.</span><span class="nx">sem</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="kd">struct</span><span class="p">{}{}:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">case</span><span class="w"> </span><span class="o">&lt;-</span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Done</span><span class="p">():</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">ctx</span><span class="p">.</span><span class="nf">Err</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">l</span><span class="w"> </span><span class="o">*</span><span class="nx">TokenLimiter</span><span class="p">)</span><span class="w"> </span><span class="nf">Release</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">&lt;-</span><span class="nx">l</span><span class="p">.</span><span class="nx">sem</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><hr>
<h2 id="十二总结">十二、总结</h2>
<table>
	<thead>
			<tr>
					<th>主题</th>
					<th>核心要点</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Goroutine</strong></td>
					<td>轻量、go 关键字、主 goroutine 退出会杀死所有</td>
			</tr>
			<tr>
					<td><strong>Channel</strong></td>
					<td>无缓冲同步、有缓冲异步、关闭规则、方向约束</td>
			</tr>
			<tr>
					<td><strong>Select</strong></td>
					<td>多路复用、超时、default 非阻塞</td>
			</tr>
			<tr>
					<td><strong>Context</strong></td>
					<td>取消传播、超时、作为第一个参数</td>
			</tr>
			<tr>
					<td><strong>sync 包</strong></td>
					<td>Mutex/RWMutex/Once/Pool/Map</td>
			</tr>
			<tr>
					<td><strong>经典模式</strong></td>
					<td>Worker Pool、Fan-in/out、Pipeline、Semaphore、errgroup</td>
			</tr>
			<tr>
					<td><strong>数据竞争</strong></td>
					<td>用 -race 检测，加锁或 atomic 修复</td>
			</tr>
			<tr>
					<td><strong>GMP</strong></td>
					<td>G=协程、M=线程、P=逻辑处理器，work stealing</td>
			</tr>
			<tr>
					<td><strong>常见坑</strong></td>
					<td>goroutine 泄漏、循环变量、重复关闭、死锁</td>
			</tr>
			<tr>
					<td><strong>决策</strong></td>
					<td>传数据用 channel，护状态用 mutex</td>
			</tr>
	</tbody>
</table>
<p><strong>一句话</strong>：Go 并发的精髓是 <strong>&ldquo;用 channel 传递数据，用 context 传递取消，用 sync 保护状态&rdquo;</strong>。理解这三者的适用场景，就能写出健壮的并发程序。</p>
]]></content:encoded></item></channel></rss>