<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>咕咕咕</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://cooooing.github.io/</id>
  <link href="https://cooooing.github.io/" rel="alternate"/>
  <link href="https://cooooing.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, 咕咕咕</rights>
  <title>咕咕咕的小破站</title>
  <updated>2026-03-06T14:08:00.000Z</updated>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="读书笔记" scheme="https://cooooing.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="《hello agents》" scheme="https://cooooing.github.io/tags/%E3%80%8Ahello-agents%E3%80%8B/"/>
    <category term="AI" scheme="https://cooooing.github.io/tags/AI/"/>
    <category term="Agent" scheme="https://cooooing.github.io/tags/Agent/"/>
    <content>
      <![CDATA[<h2 id="智能体经典范式构建"><a href="#智能体经典范式构建" class="headerlink" title="智能体经典范式构建"></a>智能体经典范式构建</h2><p>大语言模型（LLM）具备强大的推理与语言生成能力，但将其转化为能够与物理或数字环境交互的“智能体（Agent）”，需要一套严密的架构范式来组织其“感知-思考-行动”的控制流。<br>本章聚焦于三种最具代表性的智能体构建范式：ReAct、Plan-and-Solve 与 Reflection。通过从零手写这些架构的运行时逻辑，旨在剥离高层框架（如 LangChain）的抽象，深入理解智能体决策回路的底层机制。</p><h3 id="基础环境与工具抽象机制"><a href="#基础环境与工具抽象机制" class="headerlink" title="基础环境与工具抽象机制"></a>基础环境与工具抽象机制</h3><p>在构建复杂智能体范式之前，必须首先建立标准化的模型通信接口与外部工具注册中心。这是实现业务逻辑与底层基础设施解耦的关键工程步骤。</p><h4 id="LLM-客户端封装"><a href="#LLM-客户端封装" class="headerlink" title="LLM 客户端封装"></a>LLM 客户端封装</h4><p>为了保证核心调度逻辑的纯粹性，需将 API 密钥管理、网络超时处理、请求重试策略以及流式响应（Streaming）的拼接逻辑封装在一个独立的 LLM 客户端类（如 <code>HelloAgentsLLM</code>）中。<br>这使得智能体的主循环仅需关注 <code>messages</code> 的构造与返回文本的解析。</p><h4 id="工具执行器（ToolExecutor）的注册机制"><a href="#工具执行器（ToolExecutor）的注册机制" class="headerlink" title="工具执行器（ToolExecutor）的注册机制"></a>工具执行器（ToolExecutor）的注册机制</h4><p>工具是智能体干预外部世界的接口。一个标准化的工具执行系统必须具备以下设计：</p><ul><li><strong>工具的元数据定义：</strong> 包含唯一的 <code>Name</code>、功能实现 <code>func</code>，以及极其关键的 <code>Description</code>。大模型缺乏对函数的硬编码认知，完全依赖 <code>Description</code> 中的自然语言描述来触发调用决策（Tool Routing）。</li><li><strong>注册与调度引擎：</strong> 统一的 <code>ToolExecutor</code> 类用于将多个孤立的工具函数注册进一个哈希表，暴露统一的 <code>getAvailableTools</code> 方法供 Prompt 动态渲染，并暴露 <code>getTool(name)</code> 供执行引擎动态调用。</li></ul><h3 id="范式一：ReAct（思考与行动的动态交替）"><a href="#范式一：ReAct（思考与行动的动态交替）" class="headerlink" title="范式一：ReAct（思考与行动的动态交替）"></a>范式一：ReAct（思考与行动的动态交替）</h3><p><strong>ReAct (Reasoning and Acting)</strong> 是由 Shunyu Yao 等人于 2022 年提出的经典范式。它打破了传统模型“纯推理（易产生幻觉）”或“纯行动（缺乏长远规划）”的二元对立，将逻辑推理与环境探测深度耦合。</p><h4 id="ReAct-的状态机与控制流"><a href="#ReAct-的状态机与控制流" class="headerlink" title="ReAct 的状态机与控制流"></a>ReAct 的状态机与控制流</h4><p>ReAct 的核心是一个“走一步，看一步”的马尔可夫决策过程（MDP）。其运行轨迹被严格约束为交替出现的三个节点：</p><ul><li><strong>Thought（思考）：</strong> 智能体的内部认知过程。用于分析当前观察到的状态，反思上一步的结果，并决策下一步该调用什么工具。</li><li><strong>Action（行动）：</strong> 智能体输出的一段结构化指令（如 <code>Search[keyword]</code>），该指令被外部解析器截获并映射为真实的 API 调用。</li><li><strong>Observation（观察）：</strong> 外部 API 执行后返回的真实世界反馈。该反馈将作为新的上下文注入，触发智能体的下一轮 Thought。</li></ul><p><strong>数学形式化：</strong><br>在时间步 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container>，智能体策略 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="1.29ex" height="1ex" role="img" focusable="false" viewBox="0 -431 570 442"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></g></g></g></svg></mjx-container> 依据初始问题 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.439ex;" xmlns="http://www.w3.org/2000/svg" width="1.041ex" height="1.439ex" role="img" focusable="false" viewBox="0 -442 460 636"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D45E" d="M33 157Q33 258 109 349T280 441Q340 441 372 389Q373 390 377 395T388 406T404 418Q438 442 450 442Q454 442 457 439T460 434Q460 425 391 149Q320 -135 320 -139Q320 -147 365 -148H390Q396 -156 396 -157T393 -175Q389 -188 383 -194H370Q339 -192 262 -192Q234 -192 211 -192T174 -192T157 -193Q143 -193 143 -185Q143 -182 145 -170Q149 -154 152 -151T172 -148Q220 -148 230 -141Q238 -136 258 -53T279 32Q279 33 272 29Q224 -10 172 -10Q117 -10 75 30T33 157ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g></g></g></svg></mjx-container> 和历史轨迹 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="20.996ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 9280.3 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(389,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mn" transform="translate(562,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(1354.6,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(1799.2,0)"><g data-mml-node="mi"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mn" transform="translate(518,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(2720.8,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(3165.4,0)"><path data-c="2026" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60ZM525 60Q525 84 542 102T585 120Q609 120 627 104T646 61Q646 36 629 18T586 0T543 17T525 60ZM972 60Q972 84 989 102T1032 120Q1056 120 1074 104T1093 61Q1093 36 1076 18T1033 0T990 17T972 60Z"></path></g><g data-mml-node="mo" transform="translate(4504.1,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(4948.8,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="TeXAtom" transform="translate(562,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(361,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1139,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(6719.7,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(7164.4,0)"><g data-mml-node="mi"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="TeXAtom" transform="translate(518,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(361,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1139,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(8891.3,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container> 生成当前的思考和行动：<br><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="38.484ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 17009.8 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(389,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="msub" transform="translate(750,0)"><g data-mml-node="mi"><path data-c="210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></g><g data-mml-node="mi" transform="translate(609,-150) scale(0.707)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g><g data-mml-node="mo" transform="translate(1664.3,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(2108.9,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(562,-150) scale(0.707)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g><g data-mml-node="mo" transform="translate(2976.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(3643,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(4698.8,0)"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></g><g data-mml-node="mo" transform="translate(5268.8,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(5657.8,0)"><path data-c="1D45E" d="M33 157Q33 258 109 349T280 441Q340 441 372 389Q373 390 377 395T388 406T404 418Q438 442 450 442Q454 442 457 439T460 434Q460 425 391 149Q320 -135 320 -139Q320 -147 365 -148H390Q396 -156 396 -157T393 -175Q389 -188 383 -194H370Q339 -192 262 -192Q234 -192 211 -192T174 -192T157 -193Q143 -193 143 -185Q143 -182 145 -170Q149 -154 152 -151T172 -148Q220 -148 230 -141Q238 -136 258 -53T279 32Q279 33 272 29Q224 -10 172 -10Q117 -10 75 30T33 157ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g><g data-mml-node="mo" transform="translate(6117.8,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(6562.4,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(6951.4,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mn" transform="translate(562,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(7917,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(8361.6,0)"><g data-mml-node="mi"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mn" transform="translate(518,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(9283.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(9672.2,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(10116.9,0)"><path data-c="2026" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60ZM525 60Q525 84 542 102T585 120Q609 120 627 104T646 61Q646 36 629 18T586 0T543 17T525 60ZM972 60Q972 84 989 102T1032 120Q1056 120 1074 104T1093 61Q1093 36 1076 18T1033 0T990 17T972 60Z"></path></g><g data-mml-node="mo" transform="translate(11455.5,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(11900.2,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(12289.2,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="TeXAtom" transform="translate(562,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(361,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1139,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(14060.1,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(14504.8,0)"><g data-mml-node="mi"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="TeXAtom" transform="translate(518,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(361,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1139,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(16231.8,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(16620.8,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container><br>随后环境 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="1.593ex" height="1.532ex" role="img" focusable="false" viewBox="0 -677 704 677"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D447" d="M40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40Z"></path></g></g></g></svg></mjx-container> 返回观察：<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="10.195ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 4506.1 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(518,-150) scale(0.707)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g><g data-mml-node="mo" transform="translate(1101,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(2156.8,0)"><path data-c="1D447" d="M40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40Z"></path></g><g data-mml-node="mo" transform="translate(2860.8,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(3249.8,0)"><g data-mml-node="mi"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(562,-150) scale(0.707)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g><g data-mml-node="mo" transform="translate(4117.1,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container>。该循环持续进行，直至模型输出表示任务完成的 <code>Finish</code> 指令。</p><h4 id="ReAct-的-Prompt-工程与解析器设计"><a href="#ReAct-的-Prompt-工程与解析器设计" class="headerlink" title="ReAct 的 Prompt 工程与解析器设计"></a>ReAct 的 Prompt 工程与解析器设计</h4><p>实现 ReAct 的难点在于强制 LLM 遵循严格的格式输出，以便代码解析。</p><ul><li><strong>Prompt 模板结构：</strong> 必须包含系统角色声明、可用工具列表的动态注入区、严格格式约束（如 <code>Action: ToolName[Input]</code>）、以及不断累积的 <code>History</code> 记录区。</li><li><strong>正则表达式解析器：</strong> 运行时引擎通过正则匹配（如 <code>r"Thought:\s*(.*?)(?=\nAction:|$)"</code>）剥离文本，切分出控制流（Thought）与执行流（Action）。</li></ul><h4 id="范式特性评估"><a href="#范式特性评估" class="headerlink" title="范式特性评估"></a>范式特性评估</h4><ul><li><strong>优势：</strong> 极高的可解释性（思维链可见）；强大的动态纠错能力（若某次搜索无果，模型可基于 Observation 自主变更搜索词重新尝试）；天然缓解了模型基于参数化知识的“事实性幻觉”。</li><li><strong>局限：</strong> 陷入局部最优（缺乏全局视野）；极高的网络请求开销（由于串行依赖，完成一个任务需经历多次 LLM 轮询）；解析脆弱性（模型格式稍有偏离即导致整个循环崩溃）。</li></ul><h3 id="范式二：Plan-and-Solve（先谋后动的两阶段架构）"><a href="#范式二：Plan-and-Solve（先谋后动的两阶段架构）" class="headerlink" title="范式二：Plan-and-Solve（先谋后动的两阶段架构）"></a>范式二：Plan-and-Solve（先谋后动的两阶段架构）</h3><p>针对 ReAct 在长链路复杂任务中容易“迷失方向”的缺陷，<strong>Plan-and-Solve（规划与求解）</strong> 引入了人类软件工程中的“瀑布流”思想，将任务解耦为明确的静态规划与依序执行两个独立阶段。</p><h4 id="两阶段工作流解构"><a href="#两阶段工作流解构" class="headerlink" title="两阶段工作流解构"></a>两阶段工作流解构</h4><ul><li><strong>阶段一：静态全局规划（Planner）</strong><br>由独立的 Planner 模块（或独立 Prompt）接管。输入原始问题，不进行任何工具调用，仅输出一个高度结构化的子任务队列（如 JSON 或 Python List）。此阶段强制模型进行宏观的、上帝视角的思维链拆解。<br><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.65ex;" xmlns="http://www.w3.org/2000/svg" width="29.443ex" height="2.347ex" role="img" focusable="false" viewBox="0 -750 13013.9 1037.2"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(1028.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(2084.6,0)"><g data-mml-node="mi"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></g><g data-mml-node="TeXAtom" transform="translate(603,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(503,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path></g><g data-mml-node="mi" transform="translate(801,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1330,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g></g></g><g data-mml-node="mo" transform="translate(4102.3,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(4491.3,0)"><path data-c="1D45E" d="M33 157Q33 258 109 349T280 441Q340 441 372 389Q373 390 377 395T388 406T404 418Q438 442 450 442Q454 442 457 439T460 434Q460 425 391 149Q320 -135 320 -139Q320 -147 365 -148H390Q396 -156 396 -157T393 -175Q389 -188 383 -194H370Q339 -192 262 -192Q234 -192 211 -192T174 -192T157 -193Q143 -193 143 -185Q143 -182 145 -170Q149 -154 152 -151T172 -148Q220 -148 230 -141Q238 -136 258 -53T279 32Q279 33 272 29Q224 -10 172 -10Q117 -10 75 30T33 157ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g><g data-mml-node="mo" transform="translate(4951.3,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(5618,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mo" transform="translate(6673.8,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(7062.8,0)"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mn" transform="translate(536,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(8002.4,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(8447,0)"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mn" transform="translate(536,-150) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g></g><g data-mml-node="mo" transform="translate(9386.6,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(9831.3,0)"><path data-c="2026" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60ZM525 60Q525 84 542 102T585 120Q609 120 627 104T646 61Q646 36 629 18T586 0T543 17T525 60ZM972 60Q972 84 989 102T1032 120Q1056 120 1074 104T1093 61Q1093 36 1076 18T1033 0T990 17T972 60Z"></path></g><g data-mml-node="mo" transform="translate(11169.9,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(11614.6,0)"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(536,-150) scale(0.707)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g></g><g data-mml-node="mo" transform="translate(12624.9,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container></li><li><strong>阶段二：状态机递进执行（Executor）</strong><br>由 Executor 模块接管。引擎遍历任务队列 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="1.699ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 751 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g></g></g></svg></mjx-container>。在执行子任务 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.439ex;" xmlns="http://www.w3.org/2000/svg" width="1.878ex" height="1.439ex" role="img" focusable="false" viewBox="0 -442 830 636"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(536,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g></g></g></svg></mjx-container> 时，Prompt 必须同时注入全局计划 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="1.699ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 751 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g></g></g></svg></mjx-container>（以防止偏离主旨）以及历史执行结果集合 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="12.695ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 5611.2 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mo"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(389,0)"><g data-mml-node="mi"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mn" transform="translate(502,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(1294.6,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(1739.2,0)"><path data-c="2026" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60ZM525 60Q525 84 542 102T585 120Q609 120 627 104T646 61Q646 36 629 18T586 0T543 17T525 60ZM972 60Q972 84 989 102T1032 120Q1056 120 1074 104T1093 61Q1093 36 1076 18T1033 0T990 17T972 60Z"></path></g><g data-mml-node="mo" transform="translate(3077.9,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(3522.6,0)"><g data-mml-node="mi"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="TeXAtom" transform="translate(502,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mo" transform="translate(345,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1123,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(5222.2,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container>（提供前置依赖数据）。<br><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="29.027ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 12829.8 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mi" transform="translate(502,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g><g data-mml-node="mo" transform="translate(1073.7,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(2129.5,0)"><g data-mml-node="mi"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></g><g data-mml-node="TeXAtom" transform="translate(603,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mi" transform="translate(469,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(954,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path></g><g data-mml-node="mi" transform="translate(1252,0)"><path data-c="1D463" d="M173 380Q173 405 154 405Q130 405 104 376T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Q21 294 29 316T53 368T97 419T160 441Q202 441 225 417T249 361Q249 344 246 335Q246 329 231 291T200 202T182 113Q182 86 187 69Q200 26 250 26Q287 26 319 60T369 139T398 222T409 277Q409 300 401 317T383 343T365 361T357 383Q357 405 376 424T417 443Q436 443 451 425T467 367Q467 340 455 284T418 159T347 40T241 -11Q177 -11 139 22Q102 54 102 117Q102 148 110 181T151 298Q173 362 173 380Z"></path></g><g data-mml-node="mi" transform="translate(1737,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(4340.3,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(4729.3,0)"><path data-c="1D45E" d="M33 157Q33 258 109 349T280 441Q340 441 372 389Q373 390 377 395T388 406T404 418Q438 442 450 442Q454 442 457 439T460 434Q460 425 391 149Q320 -135 320 -139Q320 -147 365 -148H390Q396 -156 396 -157T393 -175Q389 -188 383 -194H370Q339 -192 262 -192Q234 -192 211 -192T174 -192T157 -193Q143 -193 143 -185Q143 -182 145 -170Q149 -154 152 -151T172 -148Q220 -148 230 -141Q238 -136 258 -53T279 32Q279 33 272 29Q224 -10 172 -10Q117 -10 75 30T33 157ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g><g data-mml-node="mo" transform="translate(5189.3,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mi" transform="translate(5633.9,0)"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(6384.9,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(6829.6,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msub" transform="translate(7218.6,0)"><g data-mml-node="mi"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mn" transform="translate(502,-150) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g><g data-mml-node="mo" transform="translate(8124.2,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="mo" transform="translate(8568.8,0)"><path data-c="2026" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60ZM525 60Q525 84 542 102T585 120Q609 120 627 104T646 61Q646 36 629 18T586 0T543 17T525 60ZM972 60Q972 84 989 102T1032 120Q1056 120 1074 104T1093 61Q1093 36 1076 18T1033 0T990 17T972 60Z"></path></g><g data-mml-node="mo" transform="translate(9907.5,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(10352.2,0)"><g data-mml-node="mi"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="TeXAtom" transform="translate(502,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mo" transform="translate(345,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1123,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(12051.8,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(12440.8,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container></li></ul><h4 id="范式特性评估-1"><a href="#范式特性评估-1" class="headerlink" title="范式特性评估"></a>范式特性评估</h4><ul><li><strong>优势：</strong> 目标一致性极强，有效遏制了复杂推理链的中途断裂；模块化程度高，规划器与执行器可分别挂载不同参数规模的 LLM 以优化成本。</li><li><strong>局限：</strong> 缺乏鲁棒性。一旦阶段一生成的计划存在根本性谬误，或者执行阶段某个前置任务失败，后续流程将产生灾难性的错误累积（Error Propagation），且标准架构下缺乏“重规划（Re-planning）”的异常中断机制。</li></ul><h3 id="范式三：Reflection（自我批判与迭代进化）"><a href="#范式三：Reflection（自我批判与迭代进化）" class="headerlink" title="范式三：Reflection（自我批判与迭代进化）"></a>范式三：Reflection（自我批判与迭代进化）</h3><p>前两种范式本质上是“单次通过（One-pass）”的执行流。<strong>Reflection（反思机制）</strong> 则引入了后验（Post-hoc）的自我校正回路，使智能体具备元认知（Meta-cognition）能力，通过试错与自我批判逼近最优解。</p><h4 id="反思机制的核心循环（Execution-Reflection-Refinement）"><a href="#反思机制的核心循环（Execution-Reflection-Refinement）" class="headerlink" title="反思机制的核心循环（Execution-Reflection-Refinement）"></a>反思机制的核心循环（Execution-Reflection-Refinement）</h4><p>Reflection 范式依赖于多角色的提示词切换与短期记忆模块的管理。</p><ol><li><strong>初始执行（Execution）：</strong> 生成 Baseline 方案（如初版低效代码）。</li><li><strong>自我批判（Reflection）：</strong> 切换系统提示词（如赋予“极其严格的资深评审员”角色）。传入原始任务与初版结果，强制 LLM 进行高维度的缺陷诊断（如时间复杂度分析、逻辑漏洞盘点），输出结构化的优化建议（Feedback）。<br><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.667ex;" xmlns="http://www.w3.org/2000/svg" width="21.79ex" height="2.364ex" role="img" focusable="false" viewBox="0 -750 9631.1 1045"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D439" d="M48 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H742Q749 676 749 669Q749 664 736 557T722 447Q720 440 702 440H690Q683 445 683 453Q683 454 686 477T689 530Q689 560 682 579T663 610T626 626T575 633T503 634H480Q398 633 393 631Q388 629 386 623Q385 622 352 492L320 363H375Q378 363 398 363T426 364T448 367T472 374T489 386Q502 398 511 419T524 457T529 475Q532 480 548 480H560Q567 475 567 470Q567 467 536 339T502 207Q500 200 482 200H470Q463 206 463 212Q463 215 468 234T473 274Q473 303 453 310T364 317H309L277 190Q245 66 245 60Q245 46 334 46H359Q365 40 365 39T363 19Q359 6 353 0H336Q295 2 185 2Q120 2 86 2T48 1Z"></path></g><g data-mml-node="mi" transform="translate(676,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g><g data-mml-node="mo" transform="translate(1247.7,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(2303.5,0)"><g data-mml-node="mi"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></g><g data-mml-node="TeXAtom" transform="translate(603,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(451,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(917,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"></path></g><g data-mml-node="mi" transform="translate(1467,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path></g><g data-mml-node="mi" transform="translate(1765,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(2231,0)"><path data-c="1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path></g><g data-mml-node="mi" transform="translate(2664,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g><g data-mml-node="mo" transform="translate(5095.5,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(5484.5,0)"><path data-c="1D447" d="M40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40Z"></path></g><g data-mml-node="mi" transform="translate(6188.5,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(6717.5,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mi" transform="translate(7186.5,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g><g data-mml-node="mo" transform="translate(7707.5,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(8152.2,0)"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mi" transform="translate(796,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g><g data-mml-node="mo" transform="translate(9242.1,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container></li><li><strong>对齐优化（Refinement）：</strong> 再次切换提示词。输入包含原始任务、上一版输出 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.357ex;" xmlns="http://www.w3.org/2000/svg" width="2.466ex" height="1.95ex" role="img" focusable="false" viewBox="0 -704 1090 861.8"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mi" transform="translate(796,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g></g></g></svg></mjx-container> 及评审员的负反馈 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.357ex;" xmlns="http://www.w3.org/2000/svg" width="2.194ex" height="1.895ex" role="img" focusable="false" viewBox="0 -680 970 837.8"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D439" d="M48 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H742Q749 676 749 669Q749 664 736 557T722 447Q720 440 702 440H690Q683 445 683 453Q683 454 686 477T689 530Q689 560 682 579T663 610T626 626T575 633T503 634H480Q398 633 393 631Q388 629 386 623Q385 622 352 492L320 363H375Q378 363 398 363T426 364T448 367T472 374T489 386Q502 398 511 419T524 457T529 475Q532 480 548 480H560Q567 475 567 470Q567 467 536 339T502 207Q500 200 482 200H470Q463 206 463 212Q463 215 468 234T473 274Q473 303 453 310T364 317H309L277 190Q245 66 245 60Q245 46 334 46H359Q365 40 365 39T363 19Q359 6 353 0H336Q295 2 185 2Q120 2 86 2T48 1Z"></path></g><g data-mml-node="mi" transform="translate(676,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g></g></g></svg></mjx-container>，强制模型依据反馈生成补丁或重构结果 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.471ex;" xmlns="http://www.w3.org/2000/svg" width="4.51ex" height="2.063ex" role="img" focusable="false" viewBox="0 -704 1993.6 912"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="TeXAtom" transform="translate(796,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mo" transform="translate(345,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z"></path></g><g data-mml-node="mn" transform="translate(1123,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g></g></g></svg></mjx-container>。<br><mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.667ex;" xmlns="http://www.w3.org/2000/svg" width="27.071ex" height="2.364ex" role="img" focusable="false" viewBox="0 -750 11965.5 1045"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="TeXAtom" transform="translate(796,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mo" transform="translate(345,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z"></path></g><g data-mml-node="mn" transform="translate(1123,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g><g data-mml-node="mo" transform="translate(2271.4,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="msub" transform="translate(3327.2,0)"><g data-mml-node="mi"><path data-c="1D70B" d="M132 -11Q98 -11 98 22V33L111 61Q186 219 220 334L228 358H196Q158 358 142 355T103 336Q92 329 81 318T62 297T53 285Q51 284 38 284Q19 284 19 294Q19 300 38 329T93 391T164 429Q171 431 389 431Q549 431 553 430Q573 423 573 402Q573 371 541 360Q535 358 472 358H408L405 341Q393 269 393 222Q393 170 402 129T421 65T431 37Q431 20 417 5T381 -10Q370 -10 363 -7T347 17T331 77Q330 86 330 121Q330 170 339 226T357 318T367 358H269L268 354Q268 351 249 275T206 114T175 17Q164 -11 132 -11Z"></path></g><g data-mml-node="TeXAtom" transform="translate(603,-150) scale(0.707)" data-mjx-texclass="ORD"><g data-mml-node="mi"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(451,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(917,0)"><path data-c="1D453" d="M118 -162Q120 -162 124 -164T135 -167T147 -168Q160 -168 171 -155T187 -126Q197 -99 221 27T267 267T289 382V385H242Q195 385 192 387Q188 390 188 397L195 425Q197 430 203 430T250 431Q298 431 298 432Q298 434 307 482T319 540Q356 705 465 705Q502 703 526 683T550 630Q550 594 529 578T487 561Q443 561 443 603Q443 622 454 636T478 657L487 662Q471 668 457 668Q445 668 434 658T419 630Q412 601 403 552T387 469T380 433Q380 431 435 431Q480 431 487 430T498 424Q499 420 496 407T491 391Q489 386 482 386T428 385H372L349 263Q301 15 282 -47Q255 -132 212 -173Q175 -205 139 -205Q107 -205 81 -186T55 -132Q55 -95 76 -78T118 -61Q162 -61 162 -103Q162 -122 151 -136T127 -157L118 -162Z"></path></g><g data-mml-node="mi" transform="translate(1467,0)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1812,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(2412,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g></g></g><g data-mml-node="mo" transform="translate(6015.2,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(6404.2,0)"><path data-c="1D447" d="M40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40Z"></path></g><g data-mml-node="mi" transform="translate(7108.2,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(7637.2,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mi" transform="translate(8106.2,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g><g data-mml-node="mo" transform="translate(8627.2,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(9071.9,0)"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mi" transform="translate(796,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g><g data-mml-node="mo" transform="translate(10161.9,0)"><path data-c="2C" d="M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z"></path></g><g data-mml-node="msub" transform="translate(10606.5,0)"><g data-mml-node="mi"><path data-c="1D439" d="M48 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q146 66 215 342T285 622Q285 629 281 629Q273 632 228 634H197Q191 640 191 642T193 659Q197 676 203 680H742Q749 676 749 669Q749 664 736 557T722 447Q720 440 702 440H690Q683 445 683 453Q683 454 686 477T689 530Q689 560 682 579T663 610T626 626T575 633T503 634H480Q398 633 393 631Q388 629 386 623Q385 622 352 492L320 363H375Q378 363 398 363T426 364T448 367T472 374T489 386Q502 398 511 419T524 457T529 475Q532 480 548 480H560Q567 475 567 470Q567 467 536 339T502 207Q500 200 482 200H470Q463 206 463 212Q463 215 468 234T473 274Q473 303 453 310T364 317H309L277 190Q245 66 245 60Q245 46 334 46H359Q365 40 365 39T363 19Q359 6 353 0H336Q295 2 185 2Q120 2 86 2T48 1Z"></path></g><g data-mml-node="mi" transform="translate(676,-150) scale(0.707)"><path data-c="1D456" d="M184 600Q184 624 203 642T247 661Q265 661 277 649T290 619Q290 596 270 577T226 557Q211 557 198 567T184 600ZM21 287Q21 295 30 318T54 369T98 420T158 442Q197 442 223 419T250 357Q250 340 236 301T196 196T154 83Q149 61 149 51Q149 26 166 26Q175 26 185 29T208 43T235 78T260 137Q263 149 265 151T282 153Q302 153 302 143Q302 135 293 112T268 61T223 11T161 -11Q129 -11 102 10T74 74Q74 91 79 106T122 220Q160 321 166 341T173 380Q173 404 156 404H154Q124 404 99 371T61 287Q60 286 59 284T58 281T56 279T53 278T49 278T41 278H27Q21 284 21 287Z"></path></g></g><g data-mml-node="mo" transform="translate(11576.5,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container></li></ol><h4 id="短期记忆（Memory）的生命周期管理"><a href="#短期记忆（Memory）的生命周期管理" class="headerlink" title="短期记忆（Memory）的生命周期管理"></a>短期记忆（Memory）的生命周期管理</h4><p>Reflection 强依赖上下文的完整回溯。需设计独立的 <code>Memory</code> 组件，严格按时序序列化存储 <code>Execution_Record</code> 与 <code>Reflection_Record</code>。这使得模型在第 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="2.009ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 888 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g></g></g></svg></mjx-container> 轮优化时，不仅能看到上一轮的结果，还能“记住”自己之前犯过的错误，避免陷入相同的逻辑死胡同。</p><h4 id="范式特性评估-2"><a href="#范式特性评估-2" class="headerlink" title="范式特性评估"></a>范式特性评估</h4><ul><li><strong>优势：</strong> 突破单次生成的质量天花板；将“一次性预测失败”转化为“持续优化的台阶”；极大提升了复杂任务（如数学推理、工业级代码生成）的最终可靠性。</li><li><strong>局限：</strong> <strong>时间复杂度与成本双高</strong>（串行等待，Token 消耗呈倍数级增加）；存在 <strong>过度优化（Over-thinking）</strong> 或在两个错误版本间 <strong>震荡（Oscillation）</strong> 的风险，对终止条件（Stopping Criteria）的设定要求极高。</li></ul><h3 id="课后习题解析"><a href="#课后习题解析" class="headerlink" title="课后习题解析"></a>课后习题解析</h3><h4 id="智能体基础范式的组织架构与场景选型"><a href="#智能体基础范式的组织架构与场景选型" class="headerlink" title="智能体基础范式的组织架构与场景选型"></a>智能体基础范式的组织架构与场景选型</h4><p><strong>【原问题】</strong><br>现代智能体架构主要包含 ReAct、Plan-and-Solve 和 Reflection 三种经典范式。</p><ol><li>请从底层控制流的角度分析，这三种范式在处理“思考（Reasoning）”与“行动（Acting）”的组织维度上有什么本质的区别？</li><li>假设需要设计一个“智能家居控制助手”（需统筹控制灯光、空调、窗帘等多个设备，并应对突发状态），作为架构师，你会选择哪种范式作为基础架构？为什么？</li><li>在现实的复杂工程中，单一范式往往存在短板。请尝试设计一种将上述范式组合使用的“混合智能体架构”，并阐述其流转机制及适用场景。</li></ol><p><strong>【解析】</strong></p><ul><li><strong>控制流组织方式的区别：</strong><ul><li><strong>ReAct（交错试探型）：</strong> 思考与行动在极细的粒度上交替（Micro-level Interleaving）。思考的唯一目标是决策当前的单一行动，高度依赖外部环境的即时观察（Observation）来修正下一步推理。属于<strong>启发式搜索闭环</strong>。</li><li><strong>Plan-and-Solve（宏观解耦型）：</strong> 思考与行动在时间轴上被强制物理隔离。思考阶段垄断了全局的规划与解构，行动阶段退化为基于给定列表的机械执行引擎。属于<strong>确定性有限状态机</strong>。</li><li><strong>Reflection（后验嵌套型）：</strong> 并不改变前向的执行逻辑，而是在整个“思考-行动”链路外部嵌套了一层后验的“再思考（批判）”循环。属于<strong>强化优化与反馈控制</strong>。</li></ul></li><li><strong>智能家居管家的架构选型：</strong><br><strong>应采用 ReAct 架构为核心。</strong> 智能家居属于高度动态且部分可观察的物理环境。设备状态极易发生突变（如“拉窗帘”指令发出后，电机可能因卡顿报错）。Plan-and-Solve 无法预见未来的执行异常，会导致后续的依赖计划全盘崩溃；而 ReAct 的“行动 -&gt; 观察异常 -&gt; 重新思考策略”闭环天然具备对物理环境扰动的快速响应与动态纠错能力。</li><li><strong>混合架构设计方案（Plan-and-ReAct 融合框架）：</strong><ul><li><strong>机制设计：</strong> 引入全局规划器（Planner），首先生成宏观步骤树（如 <code>[提取航班, 预订机票, 预订酒店]</code>）。但在执行节点（Executor）层面，不再使用死板的执行脚本，而是为每一个宏观子任务实例化一个<strong>局部的 ReAct Agent</strong>。局部的 Agent 自主调用 API 并处理偶发错误。若局部 Agent 彻底失败，则向上层抛出异常，触发全局 Planner 进行重新规划。</li><li><strong>适用场景：</strong> 处理时间跨度大、涉及多个异构环境且极易发生中断的复合任务（如自动化的软件集成测试、长周期的自动化旅行日程编排）。</li></ul></li></ul><h4 id="ReAct-引擎的鲁棒性优化与工具库扩容"><a href="#ReAct-引擎的鲁棒性优化与工具库扩容" class="headerlink" title="ReAct 引擎的鲁棒性优化与工具库扩容"></a>ReAct 引擎的鲁棒性优化与工具库扩容</h4><p><strong>【原问题】</strong><br>在构建 ReAct 智能体时，常规做法是依赖正则表达式来解析大语言模型的自然语言输出（如提取 <code>Thought</code> 和 <code>Action</code>），并通过简单的字典哈希表注册工具。</p><ol><li>依赖正则表达式提取输出流的做法在工程上存在哪些潜在的脆弱性？</li><li>除了正则表达式，现代工程框架中有什么更鲁棒的输出解析替代方案？试对比两者的优缺点。</li><li>若需为该系统添加一个“计算器”工具，并处理智能体多次调用错误参数引发的崩溃，系统应如何设计“工具选择失败”的引导纠错机制？</li><li>当业务膨胀，智能体的可用工具库从 5 个暴增至 100 个时，将所有工具描述硬编码进 Prompt 会导致什么工程灾难？应如何优化工具的组织和检索机制？</li></ol><p><strong>【解析】</strong></p><ul><li><strong>正则解析的脆弱性与替代方案：</strong><ul><li><strong>脆弱性分析：</strong> LLM 的自回归生成存在概率波动，极易在输出格式上产生微小的偏离（例如输出 <code>Action :Search...</code> 多了一个空格，或在闭合括号前增加了换行）。这会导致正则匹配直接返回 <code>None</code>，引发系统死锁或主循环崩溃。</li><li><strong>鲁棒替代方案：</strong> 采用 <strong>Function Calling（原生工具调用 API）</strong> 或 <strong>JSON Schema 结构化输出（Structured Outputs）</strong>。</li><li><strong>优缺点对比：</strong> 正则解析兼容性极强，适用于任何开源的基础模型，但容错率极低；JSON Schema / Function Calling 直接在模型推理的底层算子或解码阶段强制约束词表概率，实现了 100% 的格式对齐，但强依赖于模型服务商底层的接口支持。</li></ul></li><li><strong>计算器工具接入与异常引导纠偏机制：</strong><ul><li><strong>工具实现：</strong> 封装一个安全的数学表达式求值引擎（如使用 Python 的 <code>ast.literal_eval</code> 配合预设的安全算符库），禁止直接使用原生的 <code>eval()</code> 以防注入攻击。</li><li><strong>失败纠偏机制设计：</strong> 在工具执行的 <code>try-except</code> 捕获块中，<strong>绝不能向外层直接抛出代码异常</strong>。应将异常堆栈信息封装为人类可读的提示语（如 <code>Observation: 错误 - 计算公式格式不合法，请检查括号是否闭合。</code>），并将其作为普通的观测结果送回给 LLM。LLM 会在下一轮的 <code>Thought</code> 中读取该错误反馈，并触发自我认知：“我上一步参数写错了，我需要修正表达式并重试”。</li></ul></li><li><strong>超大规模工具库的路由重构（Tool Retrieval）：</strong><br>面对海量工具，早期的静态绑定与简单检索方案在工业级场景下已不再适用。其工程灾难与现代重构方案分析如下：<ol><li>传统工具组织方案的致命缺陷<ul><li><strong>Prompt 静态硬编码的灾难：</strong> 将上百个工具 Schema 塞入上下文，不仅带来极其高昂的 Token 计费，更会引发大模型的“注意力弥散（Lost in the Middle）”，导致模型频繁调用无关工具或产生严重幻觉。</li><li><strong>Top-K 向量检索的逻辑断裂：</strong> 仅按用户 Query 语义检索 3-5 个工具，会破坏<strong>工具链的隐式依赖</strong>。例如复杂任务需按序调用 <code>获取用户ID -&gt; 查询订单 -&gt; 取消订单</code>，语义检索往往只召回最后一个工具，导致前置参数缺失，任务直接崩溃；此外，Top-K 极易被字面相似的同质化工具占满名额。</li></ul></li><li>现代工业级工具调度机制重构<br>现代架构不再把所有底层 API 扁平化暴露给单一 Agent，而是通过协议解耦、领域路由与主动检索来实现高维管控：<ul><li><strong>标准化接入与动态发现（基于 MCP 协议）：</strong><br>引入 MCP（Model Context Protocol）协议，将工具库从 Prompt 中剥离，封装至独立的 MCP Servers 中。<br>LLM 作为 Client，通过标准化接口（如 <code>list_tools()</code>）进行<strong>动态发现与按需加载</strong>。这种 Client-Server 架构彻底打破了框架层面对工具数量的静态绑定。</li><li><strong>分层路由与技能簇抽象（Skills Routing）：</strong><br>摒弃“全能单体 Agent”，引入多智能体分层路由机制。<ul><li><strong>语义网关（Semantic Router）：</strong> 前置极速分类模型，判断用户意图所属领域。</li><li><strong>领域技能分发：</strong> 将 1000 个工具按业务抽象为多个高内聚的“技能簇（Skill Pods）”，分发给对应的专家智能体（如 <code>Finance_Agent</code> 只需挂载 15 个财务工具）。在垂直领域内，模型能够完美进行注意力分配，彻底消灭漏召回问题。</li></ul></li><li><strong>元工具寻址（Meta-Tool 机制）：</strong><br>将工具检索的主动权交还给大模型。系统初始仅向智能体注入 1~2 个元工具（如 <code>search_skills(keyword, category)</code>）。<br>智能体在推理（Thought）时，若发现缺乏相关能力，需主动调用元工具查询 API 文档并拼装工具链。这使得智能体具备了在开放环境下的微观自治能力。</li></ul></li></ol></li></ul><h4 id="Plan-and-Solve-的状态流转与分层动态重规划"><a href="#Plan-and-Solve-的状态流转与分层动态重规划" class="headerlink" title="Plan-and-Solve 的状态流转与分层动态重规划"></a>Plan-and-Solve 的状态流转与分层动态重规划</h4><p><strong>【原问题】</strong><br>Plan-and-Solve 范式通过预先规划和机械执行解决了复杂任务的逻辑连贯性问题。</p><ol><li>在标准实现中，生成的计划是一次性且静态的。如果在执行中途发现某个前置步骤无法完成，整个系统将陷入停滞。应该如何为该系统引入“动态重规划（Dynamic Re-planning）”机制？</li><li>在处理“预订一次从北京到上海的商务旅行（包含机票、酒店、租车）”任务时，Plan-and-Solve 与 ReAct 哪种范式更合适？为什么？</li><li>针对极度宏大的任务，设计一种“分层规划（Hierarchical Planning）”系统，先生成高层次抽象计划，再拆解详细子计划。这种设计相较于扁平的一维计划有何压倒性优势？</li></ol><p><strong>【解析】</strong></p><ul><li><strong>动态重规划（Re-planning）机制设计：</strong><br>需要将单向的瀑布流重构为带反馈回路的状态机。<ul><li><strong>状态标定：</strong> Executor 在执行子任务时，强制 LLM 返回执行状态的枚举值（如 <code>SUCCESS</code>, <code>FAILED_DUE_TO_ENV</code>）。</li><li><strong>异常回退：</strong> 若捕获到 <code>FAILED</code> 状态，立即中断顺序执行链。系统将“原始总计划”、“失败的步骤坐标”以及“外部 API 报错原因”打包，重新拉起 Planner 模块。</li><li><strong>增量修正：</strong> 触发特殊的重规划提示词：“原计划的步骤 2 执行失败。请保持步骤 1 的既定成果不变，对步骤 2 及其后续依赖步骤进行重新编排”。</li></ul></li><li><strong>商务旅行预订任务的范式选型：</strong><br><strong>两者均有缺陷，但强行对比下 ReAct 的生存率更高。</strong> 预订场景高度依赖外部数据库的实时状态（例如原计划预订上午 9 点的航班，但查询后发现已售罄）。<br>Plan-and-Solve 会因为死板地执行“预订 9 点航班”而导致整个任务流产；ReAct 能够在遭遇“售罄”的 Observation 后，即时转而搜索上午 10 点的替代航班。<br>然而，最完美的方案是两者的结合（宏观采用 P&amp;S 确保机酒车不遗漏，微观调用 ReAct 处理票务变动）。</li><li><strong>分层规划（Hierarchical Planning）系统的优势：</strong><ul><li><strong>突破上下文注意力瓶颈：</strong> 将任务从 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="5.495ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 2429 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mo" transform="translate(763,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1152,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mo" transform="translate(2040,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container> 扁平复杂度转化为 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="8.764ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 3873.7 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mo" transform="translate(763,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1152,0)"><path data-c="6C" d="M42 46H56Q95 46 103 60V68Q103 77 103 91T103 124T104 167T104 217T104 272T104 329Q104 366 104 407T104 482T104 542T103 586T103 603Q100 622 89 628T44 637H26V660Q26 683 28 683L38 684Q48 685 67 686T104 688Q121 689 141 690T171 693T182 694H185V379Q185 62 186 60Q190 52 198 49Q219 46 247 46H263V0H255L232 1Q209 2 183 2T145 3T107 3T57 1L34 0H26V46H42Z"></path><path data-c="6F" d="M28 214Q28 309 93 378T250 448Q340 448 405 380T471 215Q471 120 407 55T250 -10Q153 -10 91 57T28 214ZM250 30Q372 30 372 193V225V250Q372 272 371 288T364 326T348 362T317 390T268 410Q263 411 252 411Q222 411 195 399Q152 377 139 338T126 246V226Q126 130 145 91Q177 30 250 30Z" transform="translate(278,0)"></path><path data-c="67" d="M329 409Q373 453 429 453Q459 453 472 434T485 396Q485 382 476 371T449 360Q416 360 412 390Q410 404 415 411Q415 412 416 414V415Q388 412 363 393Q355 388 355 386Q355 385 359 381T368 369T379 351T388 325T392 292Q392 230 343 187T222 143Q172 143 123 171Q112 153 112 133Q112 98 138 81Q147 75 155 75T227 73Q311 72 335 67Q396 58 431 26Q470 -13 470 -72Q470 -139 392 -175Q332 -206 250 -206Q167 -206 107 -175Q29 -140 29 -75Q29 -39 50 -15T92 18L103 24Q67 55 67 108Q67 155 96 193Q52 237 52 292Q52 355 102 398T223 442Q274 442 318 416L329 409ZM299 343Q294 371 273 387T221 404Q192 404 171 388T145 343Q142 326 142 292Q142 248 149 227T179 192Q196 182 222 182Q244 182 260 189T283 207T294 227T299 242Q302 258 302 292T299 343ZM403 -75Q403 -50 389 -34T348 -11T299 -2T245 0H218Q151 0 138 -6Q118 -15 107 -34T95 -74Q95 -84 101 -97T122 -127T170 -155T250 -167Q319 -167 361 -139T403 -75Z" transform="translate(778,0)"></path></g><g data-mml-node="mo" transform="translate(2430,0)"><path data-c="2061" d=""></path></g><g data-mml-node="mi" transform="translate(2596.7,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mo" transform="translate(3484.7,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container> 的树状结构。每个中层或底层的模型只需要获取其父节点的上下文，有效防止模型在生成长文本时遗忘最初的核心目标。</li><li><strong>高度可并发性：</strong> 一旦高层级的接口契约设计完毕，底层的微观任务（例如分别实现“酒店检索子模块”和“租车询价子模块”）可以被派发给多个 LLM 线程进行并发处理，极大地压缩了工程耗时。</li></ul></li></ul><h4 id="Reflection-的元认知闭环与迭代策略优化"><a href="#Reflection-的元认知闭环与迭代策略优化" class="headerlink" title="Reflection 的元认知闭环与迭代策略优化"></a>Reflection 的元认知闭环与迭代策略优化</h4><p><strong>【原问题】</strong><br>Reflection 机制通过“执行-反思-优化”的后验循环打破了初次生成的质量天花板。</p><ol><li>在反思与执行阶段，如果我们在架构上使用两个不同的模型（例如用极其强大的模型做反思，用速度快的轻量模型做执行），这种非对称设计会带来什么深远影响？</li><li>当前 Reflection 机制的终止条件是“反馈中包含无需改进”或“达到最大迭代次数”。这种硬性设计存在哪些弊端？能否设计一个更智能的终止条件防范模型震荡？</li><li>假设需搭建一个“学术论文写作助手”，请设计一个多维度的 Reflection 机制，从段落逻辑性、方法创新性、语言表达、引用规范等不同角度进行综合反思和改进。</li></ol><p><strong>【解析】</strong></p><ul><li><strong>异构模型矩阵（Actor-Critic 非对称架构）的影响：</strong><ul><li><strong>经济学与延迟优化：</strong> 机械的代码生成或初稿书写可交由廉价且高吞吐的端侧小模型（Actor）完成；而发现逻辑漏洞、提供全局视野的“反思”任务，则交由昂贵的千亿参数顶级模型（Critic）完成。这在保证输出下限的同时，将总 API 成本与延迟大幅降低。</li><li><strong>破除自我证实偏差：</strong> 大模型极难发现自己刚刚生成的文本漏洞。使用权重分布不同的模型进行交叉审查，能提供真正的“外部视角”，显著提升 Bug 或逻辑谬误的检出率。</li></ul></li><li><strong>智能终止机制（防震荡与收敛设计）：</strong><ul><li><strong>弊端分析：</strong> LLM 可能在两种等价的代码实现之间反复修改（震荡），或者因为过度苛求完美而耗尽迭代次数。</li><li><strong>优化方案：</strong> 引入<strong>外部评估标量（Scoring Function）</strong>。每次反思后强制输出一个量化评分（1-10分）。若连续 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="2.009ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 888 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g></g></g></svg></mjx-container> 轮迭代评分未见提升，或出现评分倒退，立即触发熔断机制，回滚至历史最高分的版本并强行终止。</li></ul></li><li><strong>多维度学术反思（Multi-perspective Reflection）设计：</strong><ul><li><strong>并行审稿专家池（Mixture of Experts）：</strong> 实例化四个独立的“反思代理（Reflector Agent）”。<ul><li><em>Agent A（逻辑审查员）：</em> 专职检查章节间的连贯性与论证的因果关系。</li><li><em>Agent B（创新评估员）：</em> 评估方法论与前人研究的差异性。</li><li><em>Agent C（语言修饰员）：</em> 检查学术用语的客观性与词汇丰富度。</li><li><em>Agent D（合规审查员）：</em> 校验引用格式（APA/IEEE）与数据溯源。</li></ul></li><li><strong>共识聚合机制：</strong> 四个代理并行生成反馈报告，交由一个“主编 Agent（Meta-Reflector）”进行冲突消解和去重，最终输出一份统一的修订清单供执行层进行定稿优化。</li></ul></li></ul><h4 id="提示工程的结构化设计与微调策略"><a href="#提示工程的结构化设计与微调策略" class="headerlink" title="提示工程的结构化设计与微调策略"></a>提示工程的结构化设计与微调策略</h4><p><strong>【原问题】</strong><br>提示词工程（Prompt Engineering）直接影响智能体的底层决策逻辑。</p><ol><li>对比 ReAct 和 Plan-and-Solve 的提示词模板，它们在结构设计上有何根本性差异？这些差异是如何服务于各自范式的核心逻辑的？</li><li>在 Reflection 的提示词中，如果将角色设定从“极其严格的代码评审专家”修改为“注重代码可读性的开源项目维护者”，会对智能体的输出产生什么实质性改变？</li><li>在复杂的格式化要求中，加入 Few-shot（少样本示例）为何能显著提升模型对特定格式的遵循能力？</li></ol><p><strong>【解析】</strong></p><ul><li><strong>范式提示词的结构性差异：</strong><ul><li><strong>ReAct 提示词：</strong> 强调用<strong>分隔符</strong>与<strong>步骤标签</strong>（如 <code>Thought:</code>, <code>Action:</code>）强制约束模型的输出轨迹，以服务于逐帧切分、交替执行的微观控制流。它强调的是“当前状态”与“下一步动作”的映射。</li><li><strong>P&amp;S 提示词：</strong> 强调<strong>全局数据结构</strong>的输出（如包裹在 <code>```python</code> 中的列表）。它隔离了环境交互，迫使模型将计算算力集中在“高维任务降维拆解”上，服务于宏观隔离的静态执行管道。</li></ul></li><li><strong>角色扮演（Role-playing）对搜索空间的约束：</strong><br>修改角色设定本质上是改变了模型在隐空间中的“概率激活分布”。<ul><li>“严格的算法专家”会激活底层权重中与“时间复杂度”、“内存泄漏”、“数据结构优化”相关的高维特征，使得输出代码趋向于极致的执行效率（如牺牲内存换取时间的空间换时间算法）。</li><li>“开源项目维护者”则会激活与“PEP-8 规范”、“详尽的 Docstring 撰写”、“变量命名清晰度”相关的特征，使得输出代码可能在算法上中规中矩，但工程可维护性极强。</li></ul></li><li><strong>Few-shot（少样本示例）的隐式对齐机制：</strong><br>大语言模型是强大的模式匹配引擎（Pattern Matcher）。Zero-shot 仅仅依靠语义指导，极易在输出格式上出现概率漂移。而加入 Few-shot 相当于在推理时（In-context Learning）提供了一个局部的、高权重的梯度锚点，使模型无需改变底层权重，即刻对齐到开发者期望的句法结构（如精确的 JSON 键值对或 Markdown 排版）中，极大降低了外围代码的解析崩溃率。</li></ul><h4 id="复杂业务落地：企业级电商客服智能体架构"><a href="#复杂业务落地：企业级电商客服智能体架构" class="headerlink" title="复杂业务落地：企业级电商客服智能体架构"></a>复杂业务落地：企业级电商客服智能体架构</h4><p><strong>【原问题】</strong><br>某电商初创公司希望构建“全自动客服智能体”，需具备：理解用户长文退款诉求、查询数据库及物流状态、根据政策智能裁决、生成邮件，并在置信度较低（争议件）时触发自我反思。<br>作为产品技术架构师：</p><ol><li>你会选择哪种范式（或组合）作为系统的核心骨架？</li><li>该系统必须具备哪些外部工具？（至少列举 3 个并定义其接口语义）。</li><li>应如何设计系统提示词，以确保智能体的决策在“保护公司利润”与“维持用户友好体验”之间取得平衡？</li><li>系统上线后将面临哪些高危风险？应如何通过底层技术手段进行兜底防御？</li></ol><p><strong>【解析】</strong></p><ul><li><strong>混合架构选型（SOP-Guided Agentic Workflow）：</strong><ul><li><strong>主控骨干：</strong> 采用 <strong>Plan-and-Solve（工作流版本）</strong>。因为客服处理是一个合规性极强的标准操作程序（SOP：查单 -&gt; 判责 -&gt; 回复），不可随意改变顺序。</li><li><strong>微观执行：</strong> 在“查单”环节嵌套 <strong>ReAct</strong>，应对物流单号格式错误时的自动纠错查询。</li><li><strong>争议兜底：</strong> 在“判责”环节嵌套 <strong>Reflection</strong>。模型强制输出决策置信度，若置信度低于阈值，激活“审计员”角色进行交叉盘问，确保裁决公正。</li></ul></li><li><strong>核心工具集定义：</strong><ol><li><code>query_order_status(user_id: str, order_id: str)</code>：数据库直连工具。返回订单购买时间、签收状态、金额及历史客诉率。</li><li><code>fetch_refund_policy(item_category: str)</code>：RAG 政策检索引擎。传入商品类目（如“生鲜”或“数码”），返回企业规章中对应的退换货边界约束。</li><li><code>draft_and_send_email(user_email: str, subject: str, body: str)</code>：执行器工具。调用企业邮局系统向客户发送处理结果。</li></ol></li><li><strong>系统提示词的对抗性约束设计：</strong><br>在 System Prompt 中设置分层权重的指令体系：<br>“你是一个代表企业形象的高级客服仲裁官。<br>【第一原则-合规底线】：所有退款决策必须严格以 <code>fetch_refund_policy</code> 检索出的条款为准，绝不允许私自豁免（极高权重）。<br>【第二原则-柔性沟通】：无论裁决结果是通过还是驳回，你在生成邮件时必须展现极高的同理心，对用户的困扰表示遗憾，并使用温和、不带评判色彩的职业话术（中高权重）。”</li><li><strong>高危风险预判与兜底防御体系（Guardrails）：</strong><ul><li><strong>风险 1：恶意套现与错误退款（财务损失）。</strong><ul><li><em>防御：</em> <strong>Human-in-the-loop（人工介入）</strong> 机制。系统只赋予大模型审批 100 元以下退款的物理权限。高客单价或涉及复杂争议的决策，系统仅生成“建议方案”，必须触发审批流转交人工点击最终确认。</li></ul></li><li><strong>风险 2：模型陷入工具调用的无限死循环（算力枯竭）。</strong><ul><li><em>防御：</em> 硬件与代码级别的强制死锁断路器。设定 <code>MAX_TOOL_CALLS = 5</code>。一旦查询工具调用失败超过 5 次，强制切断大模型上下文，向用户抛出降级预案：“抱歉，您的订单数据当前系统拉取超时，已为您转接人工专员处理。”</li></ul></li></ul></li></ul><p>这是一份为您精心整理的学术级专业学习笔记。笔记严格遵循了层级标题结构，去除了偏向科普的冗余修辞，将其转化为逻辑严密、结构清晰的专业技术笔记，并对课后习题进行了无上下文依赖的重构与深度的逻辑推导解析。</p><h2 id="第五章-基于低代码平台的智能体搭建"><a href="#第五章-基于低代码平台的智能体搭建" class="headerlink" title="第五章 - 基于低代码平台的智能体搭建"></a>第五章 - 基于低代码平台的智能体搭建</h2><p>在前序章节中，我们通过了解了智能体的经典架构（如 ReAct、Plan-and-Solve 等）。这虽有利于深刻理解底层状态机与控制流，但在复杂的工业落地中，面临着极高的代码维护成本与调试难度。<br>本章聚焦于智能体工程化中的“平台化范式（Platformization）”，探讨如何利用低代码平台实现从“硬核代码构建”向“业务逻辑编排”的工程跃迁。</p><h3 id="平台化构建的核心驱动力"><a href="#平台化构建的核心驱动力" class="headerlink" title="平台化构建的核心驱动力"></a>平台化构建的核心驱动力</h3><p>低代码（Low-Code）平台并非对代码的摒弃，而是在更高维度上建立的一层抽象。其在 MAS（多智能体系统）开发中的核心价值体现在：</p><ul><li><strong>技术门槛降维与敏捷验证：</strong> 平台将底层繁琐的 API 轮询、并发线程控制、上下文状态管理封装为可视化的“节点（Node）”。开发者可借此在极短时间内完成业务原型（Prototype）的闭环验证。</li><li><strong>端到端的可观测性（Observability）：</strong> 复杂的 Agent 工作流极易出现逻辑断裂或死循环。图形化编排提供了天然的数据流追踪链路，开发者可直观监控 Token 消耗、各个节点的耗时瓶颈以及工具调用的异常堆栈。</li><li><strong>最佳实践的范式沉淀：</strong> 成熟的平台内置了工业级标准的 ReAct 模板、高性能的 RAG（检索增强生成）检索引擎以及鉴权机制，避免了开发者重复踩坑。</li></ul><p>主流低代码平台在产品定位上存在显著的分野：</p><ol><li><strong>Coze（扣子）：</strong> 极致的低门槛与 C 端生态。提供极简的拖拽体验、海量开箱即用的插件，以及向微信、飞书等社交/办公平台的一键多渠道分发能力。</li><li><strong>Dify：</strong> 偏向后端的 LLMOps（大语言模型运维）平台。主打极其灵活的模型兼容性、企业级的高级 RAG 编排、多智能体协同路由以及深度的二次开发与私有化部署。</li><li><strong>n8n：</strong> 强流程驱动的业务自动化枢纽。其核心是异构系统间的互联互通（连接数百种 SaaS 服务），AI Agent 仅作为其工作流中的一个高级计算/决策节点存在。</li></ol><h3 id="平台一：Coze（插件生态与极速应用分发）"><a href="#平台一：Coze（插件生态与极速应用分发）" class="headerlink" title="平台一：Coze（插件生态与极速应用分发）"></a>平台一：Coze（插件生态与极速应用分发）</h3><p>Coze 的架构哲学是“搭积木”，其业务重心在于降低智能体开发与分发的摩擦力。</p><h4 id="Coze-的核心组件映射"><a href="#Coze-的核心组件映射" class="headerlink" title="Coze 的核心组件映射"></a>Coze 的核心组件映射</h4><p>如果将 Coze 视为一个游戏引擎：</p><ul><li><strong>工作流（Workflow）/ 对话流：</strong> 定义 Agent 处理复杂任务的静态路径或动态交互逻辑。</li><li><strong>插件（Plugins）：</strong> Agent 干预外部环境的技能卡（如 RSS 订阅、GitHub 数据抓取）。</li><li><strong>知识库（Knowledge）：</strong> 提供特定领域事实的 RAG 挂载点。</li><li><strong>发布渠道（Publish）：</strong> 平台级的分发出口。</li></ul><h4 id="工程实践：聚合式-AI-简报助手"><a href="#工程实践：聚合式-AI-简报助手" class="headerlink" title="工程实践：聚合式 AI 简报助手"></a>工程实践：聚合式 AI 简报助手</h4><p>通过一个“每日 AI 简报”案例，展示 Coze 的聚合能力：</p><ul><li><strong>多源异构数据抓取：</strong> 同步接入 36 氪/虎嗅的 RSS 插件、GitHub 热门项目拉取插件、arXiv 论文检索插件。</li><li><strong>角色与 Prompt 约束：</strong> 通过设定 System Prompt 强制模型执行结构化输出（如限制新闻条数、强制要求 Markdown 格式并附带原链接）。</li><li><strong>一键工程化部署：</strong> 将配置完成的 Agent 经过调试后，直接映射并发布至外部聊天工具（如飞书或微信生态）中提供服务。</li></ul><h4 id="平台特性评估"><a href="#平台特性评估" class="headerlink" title="平台特性评估"></a>平台特性评估</h4><ul><li><strong>优势：</strong> 零代码起步；极其庞大的插件生态与聚合分发能力，缩短了 GTM（Go-to-Market）时间。</li><li><strong>局限：</strong> 在复杂工程下，对代码级逻辑（如复杂的 JSON 结构重组）支持疲软；且当前节点尚未接入标准的 MCP 协议（Model Context Protocol），限制了企业级深度定制的上限；工作流导出受限。</li></ul><h3 id="平台二：Dify（企业级-LLMOps-与多智能体编排）"><a href="#平台二：Dify（企业级-LLMOps-与多智能体编排）" class="headerlink" title="平台二：Dify（企业级 LLMOps 与多智能体编排）"></a>平台二：Dify（企业级 LLMOps 与多智能体编排）</h3><p>Dify 旨在弥合“原型玩具”与“生产级应用”之间的鸿沟，提供全栈式的 LLM 应用开发体验。</p><h4 id="平台架构与核心能力"><a href="#平台架构与核心能力" class="headerlink" title="平台架构与核心能力"></a>平台架构与核心能力</h4><ul><li><strong>模型中立与接入调度：</strong> 统一的抽象接口层，兼容国内外数百种闭源/开源模型，允许针对不同的子节点无缝切换性价比最高的大模型。</li><li><strong>高阶 RAG 管道：</strong> 提供可视化的文档分块（Chunking）、多路召回（Hybrid Search）及重排（Rerank）配置引擎。</li><li><strong>基于 MCP（模型上下文协议）的工具拓展：</strong> Dify 深度整合 MCP，允许开发者将外部私有系统封装为标准 MCP Server，Agent 可通过统一协议动态发现并调用这些工具（如连接企业内部 OA 或特定垂类 API）。</li></ul><h4 id="工程实践：多模态超级个人助手"><a href="#工程实践：多模态超级个人助手" class="headerlink" title="工程实践：多模态超级个人助手"></a>工程实践：多模态超级个人助手</h4><p>展示 Dify 在构建复杂 MAS 架构时的强大能力，采用**“意图路由（Semantic Router） + 领域专家智能体”**的分层架构：</p><ul><li><strong>意图分类器节点：</strong> 接收用户输入，利用极速小模型或关键字规则，将流量分发给对应的下游执行模块。</li><li><strong>并发专家网络：</strong><ul><li><em>文案优化模块：</em> 通过 Prompt 限定语气和篇幅。</li><li><em>多模态生成模块：</em> 串联外部文生图（如豆包生图）和视频生成工具链。</li><li><em>数据洞察模块：</em> 挂载数据库读取权限，结合 Text-to-SQL 工具生成数据概览，再通过图表生成插件将数据转化为可视化仪表盘。</li><li><em>MCP 外部服务：</em> 通过 SSE（Server-Sent Events）模式直连外部 MCP 市场（如高德地图、实时新闻 API）。</li></ul></li></ul><h4 id="平台特性评估-1"><a href="#平台特性评估-1" class="headerlink" title="平台特性评估"></a>平台特性评估</h4><ul><li><strong>优势：</strong> 极高的可玩性与企业级安全性（支持本地容器化私有部署、RBAC 权限管控）；海量的 Marketplace 生态；优异的高级 RAG 性能表现。</li><li><strong>局限：</strong> 学习曲线相对陡峭；核心后端基于 Python 使得其在超高并发场景下的吞吐量存在物理瓶颈。</li></ul><h3 id="平台三：n8n（业务流程驱动的自动化引擎）"><a href="#平台三：n8n（业务流程驱动的自动化引擎）" class="headerlink" title="平台三：n8n（业务流程驱动的自动化引擎）"></a>平台三：n8n（业务流程驱动的自动化引擎）</h3><p>与前两者“以大模型为绝对中心”不同，n8n 是一个泛用的工作流自动化平台（Workflow Automation）。在此平台中，LLM 被视为强化现有业务流流转的“认知中间件”。</p><h4 id="节点（Node）与事件驱动模型"><a href="#节点（Node）与事件驱动模型" class="headerlink" title="节点（Node）与事件驱动模型"></a>节点（Node）与事件驱动模型</h4><ul><li><strong>Trigger Nodes（触发器）：</strong> 监听外部事件以唤醒工作流（如 Webhook 钩子、定时任务 Cron、特定邮箱收到新邮件）。</li><li><strong>Regular Nodes（处理节点）：</strong> 包含数据清洗、API 请求、逻辑分支（IF/Switch）等。</li><li><strong>数据载体：</strong> 节点间强制通过标准化的 JSON 报文进行上下文传递。</li></ul><h4 id="工程实践：基于-Agent-节点的智能邮件中枢"><a href="#工程实践：基于-Agent-节点的智能邮件中枢" class="headerlink" title="工程实践：基于 Agent 节点的智能邮件中枢"></a>工程实践：基于 Agent 节点的智能邮件中枢</h4><p>有别于传统的硬编码邮件回复，该实践利用了 n8n 最新的<strong>集成式 AI Agent 节点</strong>，将记忆、工具与模型收束为一个整体。</p><ul><li><strong>内存 RAG 的注入：</strong> 创建一个独立的辅助工作流，将特定的业务规则（如“工作时间表”、“默认回复策略”）通过 Embedding 向量化并持久化存储。</li><li><strong>主工作流编排：</strong><ol><li><em>触发：</em> Gmail Trigger 监听到新邮件，提取发件人、主题及正文体。</li><li><em>Agent 决策中心：</em> 将邮件数据打入 AI Agent 节点。挂载 <code>Simple Vector Store</code>（用于读取上述内部规则）和 <code>SerpAPI</code>（用于解答邮件中未知的外部事实）。</li><li><em>执行：</em> Agent 根据自身推理输出结构化的回复 JSON（包含状态前缀与回复正文），并驱动下游的 Gmail Sender 节点执行物理发送动作。</li></ol></li></ul><h4 id="平台特性评估-2"><a href="#平台特性评估-2" class="headerlink" title="平台特性评估"></a>平台特性评估</h4><ul><li><strong>优势：</strong> “流程连通性”天下无敌，支持极其复杂的异步、分支和鉴权逻辑；支持完全私有化部署。</li><li><strong>局限：</strong> 默认的轻量级 Memory 和 Vector Store 是基于内存的非持久化存储，重启即丢失，生产环境必须外挂重型数据库（如 Redis/Pinecone）；工作流版本管控（Version Control）较弱，调试长链路的 JSON 穿透时较为繁琐。</li></ul><h3 id="课后习题解析-1"><a href="#课后习题解析-1" class="headerlink" title="课后习题解析"></a>课后习题解析</h3><h4 id="大规模数据库查询的-Schema-动态注入机制"><a href="#大规模数据库查询的-Schema-动态注入机制" class="headerlink" title="大规模数据库查询的 Schema 动态注入机制"></a>大规模数据库查询的 Schema 动态注入机制</h4><p><strong>【原问题】</strong><br>在构建数据分析智能体（Text-to-SQL）时，需要为大模型提供清晰的表结构（DDL）信息。<br>如果企业数据库极其庞大（例如包含 50 张表，每张表 20 个字段），将所有的 DDL 语句全部静态硬编码进提示词中，会瞬间耗尽大模型的上下文窗口，并引发严重的“注意力弥散”。<br>请设计一套更智能的工程方案来解决超大数据库的 Schema 注入问题。</p><p><strong>【解析】</strong><br>面对海量表结构的注入，必须摒弃“全量静态注入”的暴力做法，引入 <strong>检索增强表结构（Schema-RAG / 动态路由注入）</strong> 架构：</p><ul><li><strong>离线向量化阶段（Schema Embedding）：</strong><br>将数据库中 50 张表的 DDL 语句、字段注释以及表之间的外键关系（Foreign Keys），以“单张表”或“高内聚表簇”为基本切分单位。利用 Embedding 模型将这些元数据向量化，存入独立的轻量级向量数据库中。</li><li><strong>在线动态路由阶段（Dynamic Retrieval）：</strong><ul><li><strong>意图召回：</strong> 当用户提出自然语言查询（如“统计上个月华东区销售额最高的商品”）时，系统首先利用该 Query 去向量数据库中进行 Top-K 相似度检索，精准召回相关的表（例如仅召回 <code>sales_record</code> 和 <code>product_info</code> 两张表的 DDL）。</li><li><strong>上下文按需拼接：</strong> 将召回的 2-3 张表的 DDL 动态拼接到 SQL 生成 Agent 的系统提示词中。</li></ul></li><li><strong>多轮渐进式探测（Agentic Schema Exploration）：</strong><br>除了 RAG，还可以赋予智能体直接执行数据库元数据查询工具（如 <code>SHOW TABLES</code>, <code>DESCRIBE table_name</code>）的权限。<br>让智能体像真实的数据分析师一样，先查询表名，再查询感兴趣表的结构，通过 ReAct 循环逐步获取所需上下文。这种方式彻底解除了数据库规模对上下文窗口的限制。</li></ul><h4 id="企业级平台部署模式的多维评估"><a href="#企业级平台部署模式的多维评估" class="headerlink" title="企业级平台部署模式的多维评估"></a>企业级平台部署模式的多维评估</h4><p><strong>【原问题】</strong><br>Dify 等企业级大语言模型开发平台通常同时提供本地私有化部署（Local Deployment）和云端 SaaS 部署（Cloud Deployment）两种模式。<br>请从数据安全、运营成本、系统性能、维护难度等维度深度对比这两种模式，并说明它们各自适用的商业场景。</p><p><strong>【解析】</strong><br>两种部署模式在 IT 架构与商业考量上存在本质差异：</p><ul><li><strong>数据安全与隐私合规：</strong><ul><li><em>本地部署：</em> 极高。数据流转闭环于企业内网防火墙内，适合处理机密文件、金融流水或医疗病历，满足严格的行业数据不出域审计要求。</li><li><em>云端部署：</em> 较低。数据需上传至第三方云服务商，安全性依赖于服务商的合规承诺（如 SOC2 / GDPR）。</li></ul></li><li><strong>基础设施与运营成本：</strong><ul><li><em>本地部署：</em> 前期资本支出（CapEx）高昂。需自行采购 GPU 算力集群或租赁高配云主机资源。</li><li><em>云端部署：</em> 按需付费的运营支出（OpEx）。零硬件采购成本，适合初期预算有限的团队。</li></ul></li><li><strong>系统性能与并发弹性：</strong><ul><li><em>本地部署：</em> 性能受限于本地硬件上限。面对突发的超高并发请求（如营销活动），扩容周期长，容易造成系统拥塞。</li><li><em>云端部署：</em> 依托云原生架构的弹性伸缩能力，可实现毫秒级的算力扩缩容，吞吐量和高可用性（SLA）更具保障。</li></ul></li><li><strong>维护难度与迭代效率：</strong><ul><li><em>本地部署：</em> 极高。需要专业的 DevOps 团队负责容器编排、数据库备份、版本升级以及漏洞修复。</li><li><em>云端部署：</em> 极低。开箱即用，由服务商兜底系统运维和新特性的无感热更新。</li></ul></li><li><strong>适用场景论断：</strong> 本地部署是大型政企、金融/医疗机构以及拥有成熟 IT 团队的必然选择；云端部署则是初创企业、中小规模敏捷团队进行 MVP（最小可行性产品）验证和轻量级业务落地的首选。</li></ul><h4 id="业务自动化总线的状态持久化改造"><a href="#业务自动化总线的状态持久化改造" class="headerlink" title="业务自动化总线的状态持久化改造"></a>业务自动化总线的状态持久化改造</h4><p><strong>【原问题】</strong><br>在使用 n8n 平台构建“智能邮件助手”的工作流时，基础教程通常使用 <code>Simple Vector Store</code>（简单向量库）和 <code>Simple Memory</code>（简单记忆）节点。<br>但这些节点是基于进程内存的，一旦 n8n 服务重启或容器销毁，所有的知识库与对话历史将彻底丢失。<br>请说明如何通过配置将其替换为工业级的持久化存储方案（如 Pinecone、Redis）。</p><p><strong>【解析】</strong><br>在生产环境中，必须将瞬时状态剥离出执行节点，实现计算与存储的物理分离：</p><ul><li><strong>向量知识库持久化改造（以 Pinecone 为例）：</strong><ol><li><strong>节点替换：</strong> 在 n8n 画布中删除 <code>Simple Vector Store</code>，拖入 <code>Pinecone Vector Store</code> 节点。</li><li><strong>凭证配置：</strong> 创建 Pinecone 外部账户，获取 API Key 与 Environment/Host 地址，在 n8n 节点内部配置鉴权（Credentials）。</li><li><strong>索引映射：</strong> 指定目标 Index Name，并确保前置的 <code>Embeddings</code> 节点输出的向量维度（如 OpenAI 的 1536 维）与 Pinecone 数据库中的索引维度严格一致。</li></ol></li><li><strong>对话记忆持久化改造（以 Redis 为例）：</strong><ol><li><strong>节点替换：</strong> 将 Agent 节点的记忆模块替换为 <code>Redis Chat Memory</code> 或 <code>Postgres Chat Memory</code> 节点。</li><li><strong>网络连通：</strong> 配置 Redis 服务器的 Host、Port 及 Password。</li><li><strong>会话隔离映射（Session ID Routing）：</strong> 极其关键的一步。必须在 Memory 节点中设置动态的 <code>Session ID</code>。<br>例如针对邮件助手，需使用表达式 <code>{{ $json.threadId }}</code> 或 <code>{{ $json.From }}</code> 作为 Key。这样系统在处理并发邮件时，才能精准从 Redis 召回对应用户的历史上下文，防止不同用户的记忆发生串话。</li></ol></li></ul><h4 id="自动化工作流的多模态认知扩展"><a href="#自动化工作流的多模态认知扩展" class="headerlink" title="自动化工作流的多模态认知扩展"></a>自动化工作流的多模态认知扩展</h4><p><strong>【原问题】</strong><br>当前的邮件助手仅具备文本解析能力。在实际业务中，用户发送的客诉邮件往往附带了 PDF 发票凭证或损坏商品的实拍图片。如何扩展现有的 n8n 工作流，使得智能体能够感知并理解这些多模态附件内容，并做出融合性回复？</p><p><strong>【解析】</strong><br>扩展多模态能力的核心在于在触发器与 Agent 节点之间，建立**“附件提取与异构解析路由”**流水线：</p><ul><li><strong>步骤一：附件捕获与数据清洗。</strong> 在 Gmail Trigger 节点中开启 <code>Download Attachments</code>（下载附件）功能，将附件实体转为 n8n 的 Binary Data（二进制数据流）。</li><li><strong>步骤二：多模态路由分发（Switch Node）。</strong> 利用节点的条件判定功能，依据附件的 MIME Type（如 <code>application/pdf</code>, <code>image/jpeg</code>）进行分流处理。</li><li><strong>步骤三：异构格式降维解析。</strong><ul><li><em>对于 PDF 附件：</em> 引入 <code>Read PDF</code> 节点，将二进制流反序列化为长文本。若为扫描版，需进一步串联 OCR（光学字符识别）API 节点。</li><li><em>对于图像附件：</em> 引入具备视觉能力的 LLM 节点（如 <code>OpenAI (Vision)</code> 或 <code>Claude 3.5 Sonnet</code>），将图片数据转换为 Base64 编码送入视觉模型，提示词设定为“详细描述此商品损坏的部位与程度”，将其降维为文本描述。</li></ul></li><li><strong>步骤四：上下文融合（Context Merging）。</strong> 利用 <code>Merge</code> 或 <code>Set</code> 节点，将原始邮件正文、PDF 解析文本、图像诊断文本组装成一个结构化的 JSON 对象，统一作为 Input 打入最终的 AI Agent 决策中心。</li></ul><h4 id="复杂电商业务链的网状自动化设计"><a href="#复杂电商业务链的网状自动化设计" class="headerlink" title="复杂电商业务链的网状自动化设计"></a>复杂电商业务链的网状自动化设计</h4><p><strong>【原问题】</strong><br>n8n 的核心壁垒是极强的 API “连接”能力。假设需设计一个全链路电商后处理场景：当客户在 Shopify 下单后，系统需自动执行：发送确认邮件、更新 MySQL 库存、通知 ShipStation 物流系统生成面单、并在 Salesforce CRM 中记录客户购买行为。<br>请梳理该复杂工作流的节点连接拓扑图与关键参数映射逻辑。</p><p><strong>【解析】</strong><br>该工作流摒弃了单线流转，需采用<strong>扇出（Fan-out）与并行处理</strong>架构提升执行吞吐量：</p><ul><li><strong>触发层（Trigger Layer）：</strong><ul><li><strong>节点：</strong> Shopify Trigger 或通用的 Webhook 节点。</li><li><strong>配置：</strong> 监听 <code>Order Created</code> 事件，捕获包含 <code>Order_ID</code>, <code>Customer_Info</code>, <code>Items_List</code> 的全局 JSON 负载。</li></ul></li><li><strong>编排与并行分发层（Orchestration Layer）：</strong><br>通过连线将 Trigger 节点的输出同时指向下游的四个独立业务节点，实现并发执行：<ul><li><strong>分支 A（触达）：Gmail / SendGrid Node</strong> -&gt; 动作：Send Email。参数映射：To 填入 <code>{{ $json.customer.email }}</code>，Body 注入订单号。</li><li><strong>分支 B（库存）：MySQL Node</strong> -&gt; 动作：Execute Query。参数映射：编写 <code>UPDATE inventory SET stock = stock - {{ $json.items[0].quantity }} WHERE SKU = '{{ $json.items[0].sku }}'</code> 的动态 SQL。</li><li><strong>分支 C（履约）：HTTP Request Node (对接物流)</strong> -&gt; 动作：POST。向物流系统 API 发送建单请求，Body 中映射订单详细地址信息。</li><li><strong>分支 D（客户资产）：Salesforce Node</strong> -&gt; 动作：Upsert Contact/Record。依据客户邮箱判断是否存在，不存在则新建线索，并追加本单消费金额。</li></ul></li><li><strong>异常捕获与死信队列（Error Handling）：</strong><br>为 MySQL 或 HTTP 节点挂载 <code>Error Trigger</code>，若出现网络波动导致物流推单失败，将失败的 Payload 推送至 Slack 告警节点，提醒人工介入。</li></ul><h4 id="平台级提示词工程的语义与结构对比"><a href="#平台级提示词工程的语义与结构对比" class="headerlink" title="平台级提示词工程的语义与结构对比"></a>平台级提示词工程的语义与结构对比</h4><p><strong>【原问题】</strong><br>Coze、Dify 和 n8n 均大量依赖提示词工程（Prompt Engineering），但对比这三者的典型提示词设计，其在句法结构、约束风格和业务侧重点上存在明显差异。请分析这些差异的根源及其与底层平台特性的内在关联。</p><p><strong>【解析】</strong><br>提示词的形态是平台底层调度哲学的显性折射：</p><ul><li><strong>Coze（C 端交互与人设驱动）：</strong><ul><li><em>风格：</em> 极度注重“角色扮演（Role-playing）”与富文本排版（如频繁要求使用 Markdown、加粗和 Emoji 表情）。</li><li><em>关联解析：</em> Coze 的最终载体多为微信、飞书等社交/办公对话框。它本质上是一个<strong>对话式交互代理（Conversational UI）</strong>，因此提示词的重心在于打磨输出的“可读性”、“语气拟人化”与“视觉排版呈现”。</li></ul></li><li><strong>Dify（企业级规则与严谨流控）：</strong><ul><li><em>风格：</em> 结构极其森严（如使用 <code># 一、角色</code>, <code># 四、限制提示 (Limit)</code> 的公文格式），侧重于列举不可触碰的边界红线（Negative Prompts）。</li><li><em>关联解析：</em> Dify 面向复杂的企业级业务流（如合同审查、数据清洗）。其内核是<strong>确定性规则引擎</strong>。提示词设计的核心诉求是压制大模型的发散性“幻觉”，确保其输出在商业合规和逻辑严密性上绝对受控。</li></ul></li><li><strong>n8n（数据管道与机器语言约束）：</strong><ul><li><em>风格：</em> 编程化色彩浓厚，充斥着大量的动态变量插值（如 <code>{{ $json.Subject }}</code>），并且极其严苛地约束大模型<strong>必须以纯净的 JSON 格式输出</strong>。</li><li><em>关联解析：</em> 在 n8n 中，大模型只是流水线上的一个加工节点。其输出不直接给人看，而是要被下游节点（如数据库节点）直接解析执行。因此，它本质上是一个<strong>结构化数据转换器</strong>，提示词的唯一目的是确保输出格式 100% 符合机器序列化标准。</li></ul></li></ul><h4 id="长度硬性约束的逻辑困境与弹性设计"><a href="#长度硬性约束的逻辑困境与弹性设计" class="headerlink" title="长度硬性约束的逻辑困境与弹性设计"></a>长度硬性约束的逻辑困境与弹性设计</h4><p><strong>【原问题】</strong><br>在 Dify 文案优化模块的提示词中，硬性规定了“优化后的文案文本须超过 500 字”。在现代提示词工程理论中，这种对输出长度的硬性物理限制是否合理？在何种业务场景下应该严格锁死长度，又在何种场景下应当释放大模型的自由生成能力？</p><p><strong>【解析】</strong><br>采用物理字数（如“严格 500 字”）作为约束边界，在绝大多数场景下是<strong>不合理且存在极大副作用</strong>的。</p><ul><li><strong>副作用剖析：</strong> 大语言模型是概率生成器，缺乏全局的字数规划意识。强制要求长度会导致模型在字数不足时疯狂堆砌毫无意义的车轱辘话（废话填充），或在逻辑未阐述完毕时戛然而止，严重破坏文本的信息密度和内在逻辑。</li><li><strong>应当限制长度的场景（前端规约驱动）：</strong> 只有在下游系统存在物理截断限制时才应限制长度。例如：Twitter 自动发文 Agent（严格限制 280 字符）、短信营销推送（限制 70 字）、UI 卡片展示摘要。此时应使用“请压缩在 50 个 token 以内”或“一句话总结”的软约束。</li><li><strong>应当自由发挥的场景（内容质量驱动）：</strong> 逻辑推理（如 CoT）、深度学术调研、文案创作。</li><li><strong>工程改良方案（维度替代法）：</strong> 不应限制字数，而应限制 <strong>“内容信息量”</strong>。应将“超过 500 字”的提示词修改为：“文案必须包含以下三个维度的论述：1. 痛点共鸣；2. 核心技术差异化分析；3. 紧迫的行动号召（Call-to-Action）。请充分展开论述，确保逻辑丰满。”</li></ul><h4 id="跨平台自定义工具扩展的底层逻辑"><a href="#跨平台自定义工具扩展的底层逻辑" class="headerlink" title="跨平台自定义工具扩展的底层逻辑"></a>跨平台自定义工具扩展的底层逻辑</h4><p><strong>【原问题】</strong><br>尽管 Coze 拥有海量插件商店，Dify 拥有 8000+ 插件市场，n8n 提供数百个预置节点，但在企业级落地的深水区，必然会遇到平台市场中不存在的“特定内部私有 API”（如连接公司内网的自研考勤系统）。面对这种标准平台无法覆盖的盲区，作为开发者应如何通过底层扩展机制解决此问题？</p><p><strong>【解析】</strong><br>所有成熟的低代码平台均提供了“逃生舱（Escape Hatch）”机制，以支撑协议级的自定义扩展：</p><ul><li><strong>Coze / Dify：基于 OpenAPI 规范的声明式接入。</strong><br>不需要编写胶水代码。开发者只需将内网 API 的接口定义提取为标准的 OpenAPI Schema（YAML 或 JSON 格式），导入平台。平台底层的解析引擎会自动根据 Schema 中的 <code>description</code> 字段将该 API 注册为一个 LLM 可理解的 Tool。</li><li><strong>n8n：底层通信节点的降维使用。</strong><br>摒弃寻找现成的具象化节点，直接拖入图灵完备的底层节点：<ul><li>使用 <code>HTTP Request</code> 节点，手工配置 Method、Headers 与 Body，直接对内网 API 发起原生 RESTful/GraphQL 调用。</li><li>若内网 API 的鉴权逻辑极度复杂（如特殊的哈希签名验签），直接使用 <code>Code</code> 节点编写原生的 JavaScript 或 Python 脚本，以纯代码硬核突围。</li></ul></li></ul><h4 id="协议革命：MCP-对传统-Tool-Calling-的降维打击"><a href="#协议革命：MCP-对传统-Tool-Calling-的降维打击" class="headerlink" title="协议革命：MCP 对传统 Tool Calling 的降维打击"></a>协议革命：MCP 对传统 Tool Calling 的降维打击</h4><p><strong>【原问题】</strong><br>在 Dify 案例中，我们接入了基于 MCP（Model Context Protocol）的高德地图与新闻服务。请从底层架构的视角深刻剖析：MCP 协议与传统的 RESTful API 以及平台专有的 Tool Calling 机制存在哪些本质的代差？为什么行业公认 MCP 是未来智能体工具调用的“新标准”？</p><p><strong>【解析】</strong><br>MCP 引发了工具接入架构的解耦革命。</p><ul><li><strong>传统的 RESTful API：数据中心化。</strong> 它只是一套网络接口规范，大模型根本“看不懂”接口的意义。必须由人类工程师编写大量的代码将其组装，无法实现大模型的自主调用。</li><li><strong>传统的 Tool Calling：平台深度耦合的孤岛。</strong> OpenAI、Anthropic、Dify 等平台各自为战。开发者如果开发了一个“机票查询”插件，必须按照 OpenAI 的 JSON Schema 格式写一遍，再按 Dify 的规范改写一遍。工具与平台底层绑定，形成严重的生态壁垒。</li><li><strong>MCP（模型上下文协议）：Client-Server 标准化解耦。</strong><ul><li>它类似于电脑外设的“USB-C 接口协议”。外部系统（如高德地图、本地文件系统）只需实现一次标准的 <strong>MCP Server</strong>。</li><li>任何支持 MCP 的框架（Claude 桌面端、Dify 等）作为 <strong>MCP Client</strong>，通过标准握手协议即可<strong>动态枚举（List Tools）<strong>和</strong>动态挂载</strong>这些能力。</li><li><strong>新标准的意义：</strong> 彻底打破了平台墙。企业只需在内网架设一个连接敏感数据库的 MCP Server，未来无论前端的大模型技术如何迭代，都可以实现即插即用、动态拉取能力，实现了 AI 时代真正的<strong>底层能力基础设施化</strong>。</li></ul></li></ul><h4 id="Dify-自定义私有知识库插件的开发链路"><a href="#Dify-自定义私有知识库插件的开发链路" class="headerlink" title="Dify 自定义私有知识库插件的开发链路"></a>Dify 自定义私有知识库插件的开发链路</h4><p><strong>【原问题】</strong><br>假设企业内部拥有一个高度定制化的 Elasticsearch 知识库系统，现需为 Dify 开发一个自定义插件，使 Agent 能够动态查询该系统。请依据 Dify 的插件开发哲学，概述该开发的生命周期与关键技术锚点。</p><p><strong>【解析】</strong><br>在 Dify 中开发自定义插件，本质上是定义一套标准的数据通信契约：</p><ol><li><strong>接口标准化抽象（Schema 定义）：</strong> 审查企业 Elasticsearch 系统的查询接口。按照 OpenAPI 规范（Swagger）编写一份极度精确的 YAML/JSON 文件，着重打磨接口描述（Description）与入参说明，这是大模型准确进行路由的前提。</li><li><strong>鉴权策略配置（Authentication）：</strong> 在 Dify 的插件配置台中，设定接口的鉴权机制（如 API Key 注入 Headers、OAuth2 等），确保 Dify 云端调用内网服务时的安全性。</li><li><strong>开发与打包（Manifest）：</strong> 如果接口逻辑直接可用，单凭 OpenAPI 规范即可零代码生成插件；若涉及复杂的内网请求签名或结果后处理（如提取庞大的 JSON 树中的摘要字段），则需使用 Dify SDK 编写 Python 代码，打包为标准的插件结构（包含 <code>.yaml</code> 声明文件）。</li><li><strong>本地联调与发布（Remote Debugging）：</strong> 利用 Dify 提供的本地调试隧道，打通云端 Dify 与本地插件环境，观察 LLM 的 Tool Calling JSON 负载是否能正确打入内网系统。调试无误后发布至私有 Workspace 供智能体挂载使用。</li></ol><h4 id="复杂业务场景的架构级选型决策博弈"><a href="#复杂业务场景的架构级选型决策博弈" class="headerlink" title="复杂业务场景的架构级选型决策博弈"></a>复杂业务场景的架构级选型决策博弈</h4><p><strong>【原问题】</strong><br>作为技术负责人，公司计划开发以下三款 AI 应用。请在（Coze、Dify、n8n、纯代码开发）中为每个应用挑选最优技术栈，并从“技术可行性、开发与运营成本、可维护性、合规安全性”等维度详细阐述你的架构推演逻辑。</p><ul><li><strong>应用 A：</strong> 面向 C 端的“AI 写作辅助”小程序。需抢占市场极速上线，预算紧缺，团队仅有 1 名产品与 1名前端。</li><li><strong>应用 B：</strong> 面向政府/金融客户的“智能合同审查系统”。涉密程度极高（数据不出域），需深度嵌入客户老旧的本地 OA 系统。</li><li><strong>应用 C：</strong> 公司内部的“研发效能中枢”。需全自动串联 GitLab 代码审查、生成测试报告、同步 Jira 进度等长链路研发流。</li></ul><p><strong>【解析】</strong><br>技术选型的本质是**“用最小的系统复杂度覆盖业务需求的核心短板”**。</p><ul><li><strong>应用 A（C 端写作小程序）：绝对倾斜于 Coze。</strong><ul><li><em>推演逻辑：</em> 核心痛点是“资金匮乏与试错速度”。缺乏后端与运维资源使得纯代码和 Dify 私有化部署直接出局。<br>Coze 提供了极致的零代码画布，产品经理可独立完成提示词调优；其云端免费算力与一键生成 API/小程序的特性，将开发周期从数周压缩至数天，完美实现了敏捷 MVP 的低成本跨端分发。</li></ul></li><li><strong>应用 B（企服合同审查中枢）：采用 Dify（本地私有化部署）+ 局部纯代码。</strong><ul><li><em>推演逻辑：</em> 核心痛点是“涉密数据的物理隔离与长文本解析的严谨度”。数据绝对不能上公有云，Coze 和云端 n8n 无法过合规审计。Dify 提供了完善的 Docker 本地私有化方案，并内置了业界顶尖的文档解析策略与高级 RAG 工作流（合同条款检索极其依赖 RAG 精度）。<br>针对对接老旧 OA 系统的痛点，可通过纯代码编写定制化的 Dify 插件（或 MCP Server）作为桥梁，实现现代 AI 引擎与遗留系统（Legacy System）的平滑对接。</li></ul></li><li><strong>应用 C（研发长链路自动化流）：绝对倾斜于 n8n。</strong><ul><li><em>推演逻辑：</em> 核心痛点是“多重异构系统的事件监听与状态穿透”。这不是一个对话应用，而是一个底层事件总线。GitLab PR 提交、Jira 状态变更均需要强悍的 Webhook 监听与路由分发。</li><li>n8n 天生具备几百个第三方系统的鉴权凭证和无缝对接节点，技术团队可以像画流程图一样，将代码拉取、大模型审查、Bug 单回写等一系列异步动作编排在同一个画布上。相较于用纯 Python 代码维护几百个乱如乱麻的异步回调函数，n8n 在系统可维护性与拓展性上构成了压倒性的降维打击。</li></ul></li></ul><p>这是一份为您深度整理的学术级专业学习笔记。笔记严格遵循了层级标题结构，去除了偏向科普的冗余修辞，将其转化为逻辑严密、结构清晰的专业技术笔记，并对课后习题进行了无上下文依赖的重构与深度的逻辑推导解析。</p><h2 id="第六章-框架开发实践"><a href="#第六章-框架开发实践" class="headerlink" title="第六章 - 框架开发实践"></a>第六章 - 框架开发实践</h2><p>在前序阶段，通过原生脚本（如 Python）实现智能体，有助于深刻理解底层的状态机与控制流。<br>然而，当业务复杂度上升时，原生脚本在状态持久化、并发调度以及多智能体协同（Multi-Agent System, MAS）方面往往面临极高的维护成本。<br>智能体框架（Agentic Frameworks）的引入，标志着从“一次性脚本”向“工程化产品”的思维跃迁。</p><h3 id="智能体框架的工程跃迁与价值"><a href="#智能体框架的工程跃迁与价值" class="headerlink" title="智能体框架的工程跃迁与价值"></a>智能体框架的工程跃迁与价值</h3><p>智能体框架的本质是提供一套工业级的“规范与抽象层”，以解决复杂系统开发的工程痛点：</p><ul><li><strong>架构解耦与高扩展性：</strong> 强制隔离模型层（Model Layer）、工具层（Tool Layer）与记忆层（Memory Layer）。开发者可在不侵入核心业务逻辑的前提下，热替换底层大模型或数据检索引擎。</li><li><strong>标准化状态管理：</strong> 框架接管了复杂的上下文生命周期，自动处理 Token 截断、多轮会话状态持久化以及长程记忆的滑动窗口机制，消除了手动维护全局变量的风险。</li><li><strong>深度的可观测性（Observability）：</strong> 内置事件钩子（Hooks）和回调系统（Callbacks），在智能体生命周期的关键节点（如 <code>on_llm_start</code>, <code>on_tool_error</code>）自动触发日志记录，为复杂决策树的断点调试提供了标准化手段。</li></ul><h3 id="AutoGen：对话驱动的多智能体协同"><a href="#AutoGen：对话驱动的多智能体协同" class="headerlink" title="AutoGen：对话驱动的多智能体协同"></a>AutoGen：对话驱动的多智能体协同</h3><p>AutoGen 的核心设计哲学是将一切复杂的软件工程或业务流程，映射为多角色之间的“自动化群聊通信”。</p><h4 id="核心组件与演进架构"><a href="#核心组件与演进架构" class="headerlink" title="核心组件与演进架构"></a>核心组件与演进架构</h4><p>在新版架构中，AutoGen 引入了“底层异步 + 上层组件化”的分层设计：</p><ul><li><strong>分层与异步优先：</strong> 底层 <code>autogen-core</code> 负责 I/O 密集型的模型通信与事件分发（完全基于 <code>asyncio</code> 重写）；上层 <code>autogen-agentchat</code> 负责高阶对话逻辑。异步设计彻底消除了并发场景下的线程阻塞。</li><li><strong>智能体基类分离：</strong><ul><li><strong>AssistantAgent（思考引擎）：</strong> 挂载大语言模型，依靠系统提示词（System Message）进行角色化（Role-playing）与逻辑推理。</li><li><strong>UserProxyAgent（执行/网关引擎）：</strong> 作为代码沙盒执行器，或充当人类介入（Human-in-the-loop）的代理节点，明确剥离了“思考决策”与“物理执行”的系统边界。</li></ul></li></ul><h4 id="控制流机制：群聊状态机"><a href="#控制流机制：群聊状态机" class="headerlink" title="控制流机制：群聊状态机"></a>控制流机制：群聊状态机</h4><p>AutoGen 摒弃了硬编码的工作流，采用群聊控制器（Team/GroupChatManager）驱动状态流转：</p><ul><li><strong>RoundRobinGroupChat（轮询拓扑）：</strong> 适用于顺序固定的流水线任务（如：PM -&gt; Engineer -&gt; Reviewer）。智能体按预设顺序依次被激活，并共享全局聊天上下文。</li><li><strong>文本级终止信号（Termination Condition）：</strong> 协作的退出依赖于特定智能体输出预设信号（如 <code>TERMINATE</code>），框架捕获该信号后自动销毁当前会话。</li></ul><h3 id="AgentScope：消息驱动与工业级工程架构"><a href="#AgentScope：消息驱动与工业级工程架构" class="headerlink" title="AgentScope：消息驱动与工业级工程架构"></a>AgentScope：消息驱动与工业级工程架构</h3><p>AgentScope 是由阿里巴巴达摩院开源的、面向高并发与分布式企业级 MAS 设计的平台，其核心差异在于用“结构化消息”彻底取代了传统的“函数调用”。</p><h4 id="消息总线驱动（Message-Driven-Architecture）"><a href="#消息总线驱动（Message-Driven-Architecture）" class="headerlink" title="消息总线驱动（Message-Driven Architecture）"></a>消息总线驱动（Message-Driven Architecture）</h4><ul><li><strong>MsgHub（消息中枢）：</strong> 框架摒弃了点对点的强耦合调用，所有的上下文传递均被封装为标准化的 <code>Msg</code> 对象。<code>MsgHub</code> 扮演分布式消息队列的角色，负责动态的路由分发（广播、组播、单播）。</li><li><strong>时空解耦与分布式原生：</strong> 消息发送方与接收方在时间（异步非阻塞）和物理空间（位置透明）上彻底解耦，底层通过 RPC（远程过程调用）处理跨节点通信。</li></ul><h4 id="并发管线与强类型约束"><a href="#并发管线与强类型约束" class="headerlink" title="并发管线与强类型约束"></a>并发管线与强类型约束</h4><ul><li><strong>结构化输出验证（Structured Output）：</strong> 在复杂博弈场景（如“三国狼人杀”），深度结合 Pydantic 模型，将业务规则转化为数据验证模型（Schema Validation），在解析层自动拦截非法输出，保障逻辑树不崩溃。</li><li><strong>Fanout 异步并发拓扑：</strong> 提供 <code>fanout_pipeline</code> 语法糖，支持向多个智能体并行分发请求并在异步等待后进行状态规约，极大提升了系统在群体决策阶段的吞吐量。</li></ul><h3 id="CAMEL：基于角色扮演的对齐与涌现"><a href="#CAMEL：基于角色扮演的对齐与涌现" class="headerlink" title="CAMEL：基于角色扮演的对齐与涌现"></a>CAMEL：基于角色扮演的对齐与涌现</h3><p>CAMEL 致力于在最少的人工干预下，通过互补专家的思维碰撞，涌现出解决复杂任务的最优解。</p><h4 id="引导性提示与控制协议（Inception-Prompting）"><a href="#引导性提示与控制协议（Inception-Prompting）" class="headerlink" title="引导性提示与控制协议（Inception Prompting）"></a>引导性提示与控制协议（Inception Prompting）</h4><ul><li><strong>双螺旋角色模型：</strong> 强制将宏大任务拆分为需求方的 <code>AI User</code>（发包者/主导者）与供给方的 <code>AI Assistant</code>（执行者/专业顾问）。</li><li><strong>元提示注入（Meta-Prompt）：</strong> 系统底层动态生成一套严苛的交互契约，强制双方：一次只推进一个微小步骤；接收方必须给出审核意见；达成共识前禁止跳出当前逻辑。这种设计在“学术科普写作”等发散性创作中，能够自动形成高度拟人化的流水线。</li></ul><h3 id="LangGraph：图计算抽象与确定性状态机"><a href="#LangGraph：图计算抽象与确定性状态机" class="headerlink" title="LangGraph：图计算抽象与确定性状态机"></a>LangGraph：图计算抽象与确定性状态机</h3><p>LangGraph 采用了与对话驱动截然不同的技术路线，它将 MAS 退化并重构为一个极度严密的有向图（Directed Graph），赋予开发者对控制流的绝对掌控权。</p><h4 id="图论视角的组件重构"><a href="#图论视角的组件重构" class="headerlink" title="图论视角的组件重构"></a>图论视角的组件重构</h4><ul><li><strong>全局状态张量（State）：</strong> 摒弃杂乱的聊天历史，定义严格的、单例的 <code>TypedDict</code>。所有节点仅对该状态字典中的特定字段进行增量读写。</li><li><strong>节点与路由（Nodes &amp; Edges）：</strong><ul><li><em>执行节点（Nodes）：</em> 纯粹的 Python 函数，接收状态、执行模型或工具调用，并返回增量状态。</li><li><em>条件边（Conditional Edges）：</em> 通过判别函数动态改变流转拓扑。这是实现状态机循环（如“评估-重试”机制）的核心基础设施。</li></ul></li><li><strong>工程优势：</strong> 天然支持复杂循环（Cycles）、状态的细粒度可观测性，以及工业级的断点续传（Checkpointing）能力。</li></ul><h3 id="课后习题解析-2"><a href="#课后习题解析-2" class="headerlink" title="课后习题解析"></a>课后习题解析</h3><h4 id="智能体框架的底层哲学辨析"><a href="#智能体框架的底层哲学辨析" class="headerlink" title="智能体框架的底层哲学辨析"></a>智能体框架的底层哲学辨析</h4><p><strong>【原问题】</strong><br>智能体开发框架呈现出多样的设计思路。</p><ol><li>请对比 AutoGen 和 LangGraph 两个框架，从“协作模式”、“控制方式”以及“适用场景”三个核心维度进行深度的架构剖析。</li><li>软件系统设计中常存在“涌现式协作（Emergent Collaboration）”与“显式控制（Explicit Control）”的权衡。请结合大语言模型的非确定性，阐述这两种设计哲学在智能体工程落地中的利弊。</li></ol><p><strong>【解析】</strong></p><ol><li><strong>AutoGen VS LangGraph 架构深度对比：</strong><ul><li><strong>协作模式：</strong> AutoGen 是“去中心化的社会学模拟”，多主体共享上下文沙盒，依靠阅读他人的自然语言触发自身动作；LangGraph 是“中心化的车间流水线”，其本质上不存在具备自主意识的独立智能体，只有被图结构串联的“处理工序”，共同读写一个中心化的全局黑板（State）。</li><li><strong>控制方式：</strong> AutoGen 是“语义驱动路由（Semantic-driven）”，控制边界模糊，下一步的走向高度依赖大模型的意图理解与预设轮询；LangGraph 是“图逻辑驱动路由（Graph-driven）”，流转路径被代码中的条件边（Conditional Edges）硬性卡死，控制边界绝对清晰。</li><li><strong>适用场景：</strong> AutoGen 适合“探索性强、需要头脑风暴”的发散型任务（如剧本共创、代码攻防）；LangGraph 适合“SOP（标准作业程序）严苛、零容错”的收敛型业务流（如金融核保、自动化运维排障）。</li></ul></li><li><strong>“涌现”与“控制”的架构博弈：</strong><ul><li><strong>涌现式协作（如 CAMEL / AutoGen）：</strong> 承认并利用 LLM 跨维度的解题能力。<strong>优势：</strong> 能够跳出人类程序员预设的思维定势，找到创新解。<strong>代价：</strong> 系统的非确定性（Non-deterministic），极易在生产环境中引发主题漂移、死循环等幻觉崩溃，且几乎无法编写确定性的单元测试。</li><li><strong>显式控制（如 LangGraph）：</strong> 将模型视作高级的非结构化数据处理器，关进有限状态机的牢笼。<strong>优势：</strong> 在工业界落地时具有极高的可预测性、可审计性和商业安全性。<strong>代价：</strong> 牺牲了模型的高维发散智慧，系统的能力上限被架构师画流程图的能力所死死锁住。</li></ul></li></ol><h4 id="对话驱动协同（AutoGen）的动态调度重构"><a href="#对话驱动协同（AutoGen）的动态调度重构" class="headerlink" title="对话驱动协同（AutoGen）的动态调度重构"></a>对话驱动协同（AutoGen）的动态调度重构</h4><p><strong>【原问题】</strong><br>在 AutoGen 框架中，通过 <code>RoundRobinGroupChat</code> 构建的模拟软件开发团队（产品经理、工程师、代码审查员）是按固定顺序发言的。</p><ol><li>如果在代码审查环节发现需求存在偏差，需要将代码打回给产品经理重新规划，应如何修改协作流程，设计一个支持“动态回退”的机制？</li><li>为该团队引入一个“测试工程师（QA）”角色，请设计其 <code>System Message</code>，使其能在代码审查后介入执行自动化测试。</li><li>对话式协作极易陷入逻辑死循环或偏离主题。在工程设计上，如何构建一套“对话质量监控”机制进行及时干预？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>突破轮询，设计动态回退机制（FSM 路由重构）：</strong><br>放弃僵化的 <code>RoundRobinGroupChat</code>，改用底层的 <code>GroupChat</code> 并自定义发言人选择策略（Speaker Selection Method）。<br>通过注入有限状态机（FSM）的转移图机制：正常情况下 <code>Engineer -&gt; Reviewer</code>；当状态机捕获到 <code>Reviewer</code> 的输出中包含特定标识（如“需求歧义”或“架构错误”）时，转移概率发生突变，状态强制跃迁回 <code>ProductManager</code>，实现流程回溯。</li><li><strong>测试工程师角色的 System Message 设计：</strong><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">你是一位严谨的软件测试工程师（QA）。</span><br><span class="line">你的职责是在接收到审查通过的代码后，设计并执行自动化测试用例。</span><br><span class="line">工作流约束：</span><br><span class="line">1. 阅读工程师的代码与审查员的意见。</span><br><span class="line">2. 编写覆盖正常路径与异常边界的 Python pytest 代码。</span><br><span class="line">3. 若测试发现 Bug，请详细输出 Error Trace，并明确回复“测试未通过，请工程师修复”。</span><br><span class="line">4. 若测试全部通过，请明确回复“测试通过，允许交付”。</span><br></pre></td></tr></table></figure></li><li><strong>对话质量监控机制（System-level Interruption）：</strong><ul><li><strong>隐形监督者（Supervisor Agent）：</strong> 在系统中植入一个具有极高权限的后台监控钩子。</li><li><strong>轮次熔断（Timeout Circuit Breaker）：</strong> 设定硬性阈值（如连续 5 轮未产出有效代码，直接熔断并抛出异常）。</li><li><strong>语义偏移检测：</strong> 监督者定期抽取最近的会话窗口，计算其与初始 Task Prompt 的特征向量余弦相似度。<br>若相似度跌破安全阈值，认定发生“主题漂移”，系统自动向群聊中注入一条高权重 Meta-Message：“System Alert: 讨论已偏离核心任务，请立即终止发散，回归到代码实现环节！”以强制纠偏。</li></ul></li></ol><h4 id="消息驱动（AgentScope）的并发与分布式挑战"><a href="#消息驱动（AgentScope）的并发与分布式挑战" class="headerlink" title="消息驱动（AgentScope）的并发与分布式挑战"></a>消息驱动（AgentScope）的并发与分布式挑战</h4><p><strong>【原问题】</strong><br>AgentScope 利用消息总线（<code>MsgHub</code>）和结构化输出支撑了复杂的“三国狼人杀”游戏场景。</p><ol><li>相比传统框架中直接使用函数调用，<code>MsgHub</code> 消息驱动架构的本质优势是什么？</li><li>游戏中通过定义 Pydantic 模型约束输出。请设计一个新的游戏角色“猎人”的结构化输出模型（含字段定义与验证规则）。</li><li>在实时博弈场景中，将不同智能体进行分布式部署会引发哪些技术挑战？如何保证全局消息的时序一致性？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>消息驱动架构的降维打击：</strong><br>传统的函数调用是同步且强耦合的（Call-and-Wait），某个 Agent 响应慢会导致整个主线程阻塞。<code>MsgHub</code> 实现了计算与通信在时间（异步非阻塞）和空间（位置透明）上的彻底解耦。<br>它天然支持事件广播（Broadcast）和并发收集（Fanout），是实现大规模“多智能体并发运作”的基础设施。</li><li><strong>猎人角色的强类型输出模型设计（Pydantic Schema）：</strong><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pydantic <span class="keyword">import</span> BaseModel, Field, validator</span><br><span class="line"><span class="keyword">from</span> typing <span class="keyword">import</span> <span class="type">Optional</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">HunterActionModel</span>(<span class="title class_ inherited__">BaseModel</span>):</span><br><span class="line">    trigger_skill: <span class="built_in">bool</span> = Field(description=<span class="string">"当前出局时，是否发动猎人技能开枪"</span>)</span><br><span class="line">    target_player: <span class="type">Optional</span>[<span class="built_in">str</span>] = Field(description=<span class="string">"若发动技能，目标玩家的名称"</span>, default=<span class="literal">None</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">    @validator(<span class="params"><span class="string">"target_player"</span></span>)</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">validate_target</span>(<span class="params">cls, v, values</span>):</span><br><span class="line">        <span class="keyword">if</span> values.get(<span class="string">"trigger_skill"</span>) <span class="keyword">and</span> <span class="keyword">not</span> v:</span><br><span class="line">            <span class="keyword">raise</span> ValueError(<span class="string">"发动技能时必须指定合法的目标玩家"</span>)</span><br><span class="line">        <span class="keyword">return</span> v</span><br></pre></td></tr></table></figure></li><li><strong>分布式 MAS 的并发时序挑战与解决（CAP 定理投影）：</strong><ul><li><strong>技术挑战：</strong> 网络延迟抖动与时钟漂移。若玩家 A 的“反驳消息”因网络拥塞，晚于玩家 B 的“投票消息”到达裁判节点，会引发“因果逻辑倒置”，导致大模型基于错乱的时序生成违背规则的推演。</li><li><strong>时序一致性保障设计：</strong> 摒弃物理时间戳，引入<strong>逻辑时钟（Logical Clocks，如 Lamport 时间戳）</strong>。MsgHub 核心调度器为每个生成的事件打上绝对递增的 Sequence ID。<br>在分布式接收端设置<strong>因果组装缓冲区（Causal Buffer Barrier）</strong>，必须等待消息齐备并按 Sequence ID 排序后，再将有序的上下文推入 LLM。</li></ul></li></ol><h4 id="角色扮演协同（CAMEL）的死锁与架构拓展"><a href="#角色扮演协同（CAMEL）的死锁与架构拓展" class="headerlink" title="角色扮演协同（CAMEL）的死锁与架构拓展"></a>角色扮演协同（CAMEL）的死锁与架构拓展</h4><p><strong>【原问题】</strong><br>CAMEL 框架通过“引导性提示”实现双智能体（如心理学家与作家）的自治协作。当检测到 <code>&lt;CAMEL_TASK_DONE&gt;</code> 标志时强制终止。</p><ol><li>如果两个智能体意见分歧（一位认为已完成，一位拒不接受），导致陷入逻辑死锁，系统应如何设计仲裁机制？</li><li>查阅 CAMEL 的多智能体协作模块（Workforce），说明其在协作拓扑图上与 AutoGen 的群聊模式有何根本不同？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>死锁破除与仲裁设计（Conflict Resolution）：</strong><br>纯粹依赖 LLM 内部和解极不可靠，必须引入系统级干预。<ul><li><strong>非递增驳回熔断：</strong> 维护一个连续拒绝计数器，若 User 连续驳回 Assistant 的产出超过 3 次，挂起当前对话线程。</li><li><strong>引入仲裁者（Moderator Agent）：</strong> 激活一个拥有系统级权限的独立模型。将僵持的上下文抛入，由 Moderator 判定 Assistant 的产出是否已实质满足初始 Task Prompt。<br>若满足，Moderator 代发强行终止指令；若不满足，由 Moderator 给出具体的强制修改锚点。</li></ul></li><li><strong>协作拓扑差异（群聊 vs 层级委派）：</strong><ul><li><strong>AutoGen 的群聊：</strong> 属于“扁平化网状拓扑”，所有节点在一个大沙盒中共享全量的广播上下文，容易产生信息噪音。</li><li><strong>CAMEL 的 Workforce：</strong> 采用了<strong>树状分层委派架构（Hierarchical Task Delegation）</strong>。<br>定义了一个 <code>Coordinator</code>（主节点）和多个底层的 <code>Worker</code>。主节点负责将大任务解耦并点对点分发给特定 Worker，Worker 完成后仅向主节点汇报，彼此间不交叉。这种结构有效隔离了上下文噪音，更适合处理多级复杂业务流。</li></ul></li></ol><h4 id="状态机与图计算（LangGraph）的循环拓扑设计"><a href="#状态机与图计算（LangGraph）的循环拓扑设计" class="headerlink" title="状态机与图计算（LangGraph）的循环拓扑设计"></a>状态机与图计算（LangGraph）的循环拓扑设计</h4><p><strong>【原问题】</strong><br>LangGraph 将智能体流程建模为有向图。</p><ol><li>请绘制出“三步问答助手（理解 -&gt; 搜索 -&gt; 回答）”的图结构，精确标注节点、有向边和状态转换条件。</li><li>为打破单向线性流程，请增加一个“深度反思（Reflection）”节点：在回答后进行质量评估，若不合格则回退重新搜索。说明条件边的路由逻辑。</li><li>结合原生循环特性，设计一个“代码生成 -&gt; 编译测试 -&gt; 异常修复”的复杂应用场景，说明核心流转机制。</li></ol><p><strong>【解析】</strong></p><ol><li><strong>线性有向图拓扑映射：</strong><br><code>[START]</code> –(常规边)–&gt; <code>[Node: Understand (提取搜索词)]</code> –(常规边)–&gt; <code>[Node: Search (调用API更新状态)]</code> –(常规边)–&gt; <code>[Node: Answer (生成最终文本)]</code> –(常规边)–&gt; <code>[END]</code></li><li><strong>带循环的反思拓扑重构（Cyclic Reflection Graph）：</strong><br>切断 <code>Answer</code> 通向 <code>END</code> 的边，插入新节点 <code>[Node: Evaluator]</code>。<ul><li><strong>新增节点：</strong> <code>Evaluator</code> 读取状态中的 <code>final_answer</code> 进行评分，更新布尔状态 <code>state["is_qualified"]</code>，并递增 <code>state["retry_count"]</code>。</li><li><strong>条件边（Conditional Edge）路由逻辑：</strong> 从 <code>Evaluator</code> 拉出条件边。判定函数逻辑：<br><code>if state["is_qualified"] == True or state["retry_count"] &gt;= 3:</code><br><code>return "end_workflow"</code> -&gt; 路由指向 <code>[END]</code><br><code>else:</code><br><code>return "need_research"</code> -&gt; 路由跳回 <code>[Node: Search]</code>。</li></ul></li><li><strong>“生成-测试-修复”复杂循环设计：</strong><ul><li>节点一：<code>[Node: Coder]</code>（接收需求或报错日志，生成代码片段）。</li><li>节点二：<code>[Node: Sandbox_Tester]</code>（在隔离沙盒中物理执行代码。若成功，记录状态 <code>PASS</code>；若失败，提取 <code>stderr</code> 写入状态，记为 <code>FAIL</code>）。</li><li><strong>核心流转（条件边）：</strong> 挂载于 <code>Sandbox_Tester</code> 之后。判定 <code>state.status</code>，若为 <code>PASS</code>，流向 <code>[END]</code>；若为 <code>FAIL</code>，流回 <code>[Node: Coder]</code>。<br>此时 Coder 节点读取增量状态中上一次失败的代码和终端报错栈，进行针对性 Patch 修复，再次流入 Tester 进行验证。</li></ul></li></ol><h4 id="企业级业务架构：框架选型深度推演"><a href="#企业级业务架构：框架选型深度推演" class="headerlink" title="企业级业务架构：框架选型深度推演"></a>企业级业务架构：框架选型深度推演</h4><p><strong>【原问题】</strong><br>作为 AI 公司的技术架构师，请为以下三个应用选择最合适的底层开发路径（AutoGen, AgentScope, CAMEL, LangGraph, 或纯代码开发），并从技术可行性、并发性能、可控审计等维度详细阐述推演逻辑：</p><ul><li><strong>应用A：智能客服系统</strong>。需处理 1000+ QPS，响应延迟 &lt; 2s，支持水平无状态扩容。</li><li><strong>应用B：科研论文辅助写作平台</strong>。要求“研究员”与“写作”智能体进行多轮深度辩论，高度自主推进任务。</li><li><strong>应用C：金融风控审批系统</strong>。包含资料审核、风险评估等严格分支逻辑，要求流程百分百可追溯、可审计。</li></ul><p><strong>【解析】</strong><br>架构选型的本质是评估系统的“确定性容忍度”与“工程吞吐量”。</p><ul><li><strong>应用 A（高并发极速客服）：必须选用原生代码重构（或极度裁剪的 AgentScope）。</strong><ul><li><em>推演逻辑：</em> 1000 QPS 的并发量是极其严苛的工程红线。<br>依赖繁重聊天历史拼接的 AutoGen / CAMEL，其框架层级的状态序列化开销、以及冗长的 Prompt 包装，会引发灾难性的 CPU 负载与 Token 延迟，彻底违背 &lt;2s 的 SLA。<br>必须剥离一切抽象，采用基于 FastAPI/Go 的纯异步原生代码，搭配 Redis 外置记忆，实现极致精简的无状态（Stateless）流式 RPC 调用，以支撑 Kubernetes 的水平弹性扩容。</li></ul></li><li><strong>应用 B（高度自主的学术涌现）：绝对倾斜于 CAMEL（或 AutoGen）。</strong><ul><li><em>推演逻辑：</em> 学术科研的核心诉求是突破信息茧房，引发“群体智能的涌现（Emergent Intelligence）”。解题路径在初始阶段是未知的，真理诞生于苏格拉底式的辩论中。<br>CAMEL 极简的双角色对齐协议（Inception Prompting）天然为这种发散-收敛推演而生。这类产品追求探索的逻辑张力而非流转的确定性，因此赋予模型极高自治权的对话驱动框架是首选。</li></ul></li><li><strong>应用 C（金融级刚性风控中枢）：唯一指定方案为 LangGraph。</strong><ul><li><em>推演逻辑：</em> 金融风控对 LLM 的“自由发挥（幻觉）”零容忍，它需要的是“戴着镣铐跳舞的组件”。LangGraph 底层是一个强类型的“有向图状态机（FSM）”，能够将信贷的数十个 SOP 死死钉在图节点上。<br>最关键的是，LangGraph 拥有原生的 Checkpointing（断点检查）机制，每次节点流转的状态增量都能落盘数据库。这为金融合规提供了不可篡改的<strong>时间旅行追踪（Time Travel）与全量审计日志</strong>能力，一举击穿监管痛点。</li></ul></li></ul>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8Ahello%20agents%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%9E%84%E5%BB%BA%E4%BD%A0%E7%9A%84%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%99%BA%E8%83%BD%E4%BD%93/</id>
    <link href="https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8Ahello%20agents%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%9E%84%E5%BB%BA%E4%BD%A0%E7%9A%84%E5%A4%A7%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E6%99%BA%E8%83%BD%E4%BD%93/"/>
    <published>2026-03-06T14:08:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="智能体经典范式构建"><a href="#智能体经典范式构建" class="headerlink" title="智能体经典范式构建"></a>智能体经典范式构建</h2><p>大语言模型（LLM）具备强大的推理与语言生成能力，但将其转化为能够与物理或数字环境交]]>
    </summary>
    <title>《hello agents》读书笔记 - 构建你的大语言模型智能体</title>
    <updated>2026-03-06T14:08:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="读书笔记" scheme="https://cooooing.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="《hello agents》" scheme="https://cooooing.github.io/tags/%E3%80%8Ahello-agents%E3%80%8B/"/>
    <category term="AI" scheme="https://cooooing.github.io/tags/AI/"/>
    <category term="Agent" scheme="https://cooooing.github.io/tags/Agent/"/>
    <content>
      <![CDATA[<h2 id="第一章-初识智能体"><a href="#第一章-初识智能体" class="headerlink" title="第一章 - 初识智能体"></a>第一章 - 初识智能体</h2><p>本章从人工智能的发展脉络出发，系统阐述了智能体（Agent）的本质定义、演进路线，以及在大语言模型（LLM）驱动下产生的新范式。通过探讨其内部架构、交互机制与协作模式，为理解多智能体系统（MAS）奠定了核心理论基础。</p><h3 id="什么是智能体？"><a href="#什么是智能体？" class="headerlink" title="什么是智能体？"></a>什么是智能体？</h3><p>在探讨前沿 AI 之前，必须先明确智能体的本体论定义。智能体并非单纯的代码脚本，而是一个具备环境感知与干预能力的闭环系统。</p><ul><li><strong>四大核心要素：</strong><ul><li><strong>环境（Environment）：</strong> 智能体所处的外部动态世界（如自动驾驶的道路、量化交易的市场）。</li><li><strong>传感器（Sensors）：</strong> 智能体获取环境状态的途径（如摄像头、雷达、API 数据流）。</li><li><strong>执行器（Actuators）：</strong> 智能体对环境施加影响的物理或虚拟载体（如机械臂、代码执行器、服务调用）。</li><li><strong>自主性（Autonomy）：</strong> 智能体的灵魂，即不依赖被动指令，而是基于内部状态和感知数据进行独立决策并达成目标的能力。</li></ul></li><li><strong>交互闭环：</strong> “感知 -&gt; 决策 -&gt; 行动”构成了所有智能体存在的底层逻辑。</li></ul><h4 id="传统视角下的智能体演进路线"><a href="#传统视角下的智能体演进路线" class="headerlink" title="传统视角下的智能体演进路线"></a>传统视角下的智能体演进路线</h4><p>在 LLM 爆发前，符号主义与早期机器学习主导了智能体的设计，其演进呈现出从“被动反应”到“主动规划与学习”的阶梯式发展：</p><ul><li><strong>简单反射智能体（Simple Reflex Agent）：</strong> 基于“条件-动作”规则（If-Then）运行。无记忆、无预测，极度依赖当前瞬间的感知（如老式恒温器）。</li><li><strong>基于模型的反射智能体（Model-Based Reflex Agent）：</strong> 引入了“内部世界模型”以维持状态记忆，能够处理部分不可见的环境信息，实现上下文连贯的决策。</li><li><strong>基于目标的智能体（Goal-Based Agent）：</strong> 从“被动反应”转向“主动预测”。通过搜索与规划算法（如 A* 算法），探索达成特定未来状态的最优路径（如 GPS 导航）。</li><li><strong>基于效用的智能体（Utility-Based Agent）：</strong> 面对多重且冲突的目标时，通过引入“效用函数”来量化各个状态的满意度，以最大化期望效用为决策导向，更贴近理性经济人假设。</li><li><strong>学习型智能体（Learning Agent）：</strong> 突破了人类预设规则的瓶颈。由“性能元件”（决策）和“学习元件”（优化）组成。通过与环境互动（如强化学习 RL），利用奖励机制自我进化（如 AlphaGo）。</li></ul><h4 id="大语言模型驱动的新范式"><a href="#大语言模型驱动的新范式" class="headerlink" title="大语言模型驱动的新范式"></a>大语言模型驱动的新范式</h4><p>LLM 的出现颠覆了传统智能体的构建逻辑。传统智能体依赖显式编程与边界清晰的规则，而 LLM 智能体则依赖海量预训练数据中涌现的隐式知识与通用推理能力。</p><ul><li><strong>范式对比的核心维度：</strong><ul><li><strong>核心引擎：</strong> 传统（规则/逻辑搜索/强化学习） vs 新范式（大语言模型神经网络）。</li><li><strong>知识来源：</strong> 传统（人工注入先验知识） vs 新范式（海量语料预训练得到的隐式世界模型）。</li><li><strong>任务拆解：</strong> 传统（开发者硬编码） vs 新范式（模型自主进行链式推理与逻辑分解）。</li><li><strong>交互方式：</strong> 传统（结构化数据/指令） vs 新范式（高层级、模糊的自然语言）。</li></ul></li><li><strong>LLM 智能体的三大涌现特征：</strong><ol><li><strong>规划与推理：</strong> 能够将宏大的自然语言目标自主降维分解为可执行的子任务树。</li><li><strong>工具使用：</strong> 识别能力边界，主动调用外部 API（如查天气、日历）弥补信息缺口。</li><li><strong>动态修正：</strong> 具备基于环境反馈或用户约束（如“超出预算”）进行自我纠错与路径重规划的能力。</li></ol></li></ul><h4 id="智能体的多维分类体系"><a href="#智能体的多维分类体系" class="headerlink" title="智能体的多维分类体系"></a>智能体的多维分类体系</h4><p>智能体可以从以下三个互补的学术维度进行深度剖析：</p><ul><li><strong>基于时间与反应性的权衡：</strong><ul><li><strong>反应式（Reactive）：</strong> 决策延迟极低，基于直接映射行动。优势是速度快、计算开销小；劣势是短视，易陷入局部最优（如高频交易、安全气囊）。</li><li><strong>规划式（Deliberative）：</strong> 行动前进行深度前瞻探索。优势是具战略远见；劣势是计算成本极高，在动态环境中易错失良机（如复杂路线规划）。</li><li><strong>混合式（Hybrid）：</strong> 现代 LLM 智能体的主流形态。在宏观层面通过大模型进行审慎规划（Reasoning），在微观层面通过调用工具获取即时反馈（Acting &amp; Observing），实现长远规划与敏捷反应的统一。</li></ul></li><li><strong>基于知识表示的流派（AI 哲学的核心分野）：</strong><ul><li><strong>符号主义 AI（Symbolic AI）：</strong> 知识表现为显式的逻辑规则（如知识图谱）。透明、可解释，但存在“知识获取瓶颈”，对模糊现实的鲁棒性差。</li><li><strong>亚符号主义 AI（Sub-symbolic AI / 连接主义）：</strong> 知识内隐于神经网络的权重中。擅长模式识别、抗噪性强，但属于“黑盒”，缺乏严密的因果解释能力。</li><li><strong>神经符号混合主义（Neuro-Symbolic AI）：</strong> 对应卡尼曼的“双系统理论”。系统 1（神经网络）负责快速直觉与模式识别，系统 2（符号逻辑）负责慢速审慎推理。LLM 智能体正是此范式的绝佳体现：内核为神经网络（系统 1），但输出为结构化的思考步骤和工具调用参数（系统 2）。</li></ul></li></ul><h3 id="智能体的构成与运行原理"><a href="#智能体的构成与运行原理" class="headerlink" title="智能体的构成与运行原理"></a>智能体的构成与运行原理</h3><p>要使智能体在真实场景中落地，必须对其所处的环境约束及内部运行调度机制进行严密的工程化定义。</p><h4 id="任务环境的-PEAS-规约"><a href="#任务环境的-PEAS-规约" class="headerlink" title="任务环境的 PEAS 规约"></a>任务环境的 PEAS 规约</h4><p>设计智能体的第一步是使用 <strong>PEAS 模型</strong>对其边界进行结构化描述：</p><ul><li><strong>P (Performance):</strong> 性能度量（评估任务成功与否的客观指标）。</li><li><strong>E (Environment):</strong> 运行环境（应用场景及约束条件）。</li><li><strong>A (Actuators):</strong> 执行器（改变环境状态的工具/接口）。</li><li><strong>S (Sensors):</strong> 传感器（接收反馈信息的通道）。</li></ul><p><strong>数字环境的复杂特性挑战：</strong><br>LLM 智能体常面临<strong>部分可观察</strong>（需记忆与探索）、<strong>随机性</strong>（需处理不确定结果）、<strong>多智能体环境</strong>（存在动态博弈）以及<strong>序贯且动态</strong>（决策影响未来，环境实时突变）的复杂挑战。</p><h4 id="智能体的感知、思考与行动闭环"><a href="#智能体的感知、思考与行动闭环" class="headerlink" title="智能体的感知、思考与行动闭环"></a>智能体的感知、思考与行动闭环</h4><p>智能体通过 <strong>Agent Loop（智能体循环）</strong> 不断向目标收敛。在现代框架中，这一过程被严谨的 <strong>交互协议（Interaction Protocol）</strong> 所规范，最典型的是 <strong>Thought-Action-Observation 范式</strong>：</p><ol><li><strong>感知 (Perception / Observation):</strong> 环境反馈的原始数据通过传感器被解析器“清洗”并转化为 LLM 易于理解的自然语言上下文。</li><li><strong>思考 (Thought):</strong><ul><li><em>规划 (Planning):</em> LLM 基于 Observation 和内部记忆，更新对当前状态的理解。</li><li><em>工具选择 (Tool Selection):</em> 从工具库中匹配最佳能力，并提取相关参数。这一步是不可见的内部张量运算转化为可见的逻辑表述的过程。</li></ul></li><li><strong>行动 (Action):</strong> 输出结构化指令（如 <code>function_name(args)</code>），通过执行器触发外部真实世界的改变，进而引发下一轮循环。</li></ol><h3 id="智能体应用的协作模式"><a href="#智能体应用的协作模式" class="headerlink" title="智能体应用的协作模式"></a>智能体应用的协作模式</h3><p>随着自主性程度的跃升，智能体在人类生产流中扮演的角色正在发生本质迁移。</p><h4 id="Workflow-与-Agent-的核心差异辨析"><a href="#Workflow-与-Agent-的核心差异辨析" class="headerlink" title="Workflow 与 Agent 的核心差异辨析"></a>Workflow 与 Agent 的核心差异辨析</h4><p>这是理解现代自动化的核心。</p><ul><li><strong>Workflow（工作流）：</strong> 静态的、基于预设规则的编排系统。高度可控、结果确定，但极其僵化，无法处理流程图之外的边缘情况（如硬编码的审批流）。</li><li><strong>Agent（智能体）：</strong> 动态的、以目标为导向的自主系统。LLM 充当大脑，能够实时推理、处理非结构化信息，并动态生成“执行路径”。具备极高的灵活性，但牺牲了部分确定性。</li></ul><h4 id="两种主流应用范式"><a href="#两种主流应用范式" class="headerlink" title="两种主流应用范式"></a>两种主流应用范式</h4><ul><li><strong>作为开发者工具（Human-in-the-loop）：</strong> 深度嵌入现有工作流，扮演“副驾驶”（Copilot）。例如 GitHub Copilot、Cursor，通过代码补全、重构建议增强人类创造力。人类下达微观指令，系统被动响应。</li><li><strong>作为自主协作者（Autonomous Teammate）：</strong> 人类转变为“目标下达者”与“结果验收者”。智能体自主完成复杂任务。<ul><li><em>单智能体闭环：</em> 自主规划与反思（如 AutoGPT）。</li><li><em>多智能体协作（MAS）：</em> 模拟人类组织架构，通过明确的角色分配（Role-playing）或标准作业程序（SOP）进行协同。例如 MetaGPT 模拟整个软件公司流程，不同智能体分别扮演 PM、架构师、工程师，通过消息总线进行复杂的网状通信与协作。</li></ul></li></ul><h3 id="课后习题解析"><a href="#课后习题解析" class="headerlink" title="课后习题解析"></a>课后习题解析</h3><h4 id="智能体概念与分类辨析"><a href="#智能体概念与分类辨析" class="headerlink" title="智能体概念与分类辨析"></a>智能体概念与分类辨析</h4><p><strong>【原问题】</strong><br>在人工智能领域，智能体被定义为能够通过传感器感知环境，并自主地通过执行器采取行动以达成特定目标的实体。同时，智能体可以根据决策架构、反应时间、知识表示等维度进行分类。请基于此定义和分类维度，分析以下四个案例中的主体是否属于智能体。如果是，请说明其属于哪种类型并给出理由：</p><ul><li><strong>Case A：</strong> 一台符合冯·诺依曼结构的超级计算机，拥有高达每秒 2EFlop 的峰值算力。</li><li><strong>Case B：</strong> 特斯拉自动驾驶系统在高速公路上行驶时，突然检测到前方有障碍物，需要在毫秒级做出刹车或变道决策。</li><li><strong>Case C：</strong> AlphaGo 在与人类棋手对弈时，需要评估当前局面并规划未来数十步的最优策略。</li><li><strong>Case D：</strong> ChatGPT 扮演的智能客服在处理用户投诉时，需要查询订单信息、分析问题原因、提供解决方案并安抚用户情绪。</li></ul><p><strong>【解析】</strong></p><ul><li><strong>Case A：不是智能体。</strong> 超级计算机仅是一个硬件实体或算力工具。它缺乏“自主性”和“特定目标”，没有感知环境并主动决策的闭环，仅仅是被动执行人类输入的程序。</li><li><strong>Case B：是智能体。</strong><ul><li><em>决策架构：</em> 属于<strong>基于模型/效用的反射型智能体</strong>。它拥有外部道路的内部模型，并基于安全效用做出决策。</li><li><em>时间与反应性：</em> 属于典型的<strong>反应式智能体（Reactive）</strong>。在毫秒级约束下，系统优先选择极低延迟的条件映射而非深思熟虑的长远规划。</li><li><em>知识表示：</em> 属于<strong>亚符号主义</strong>。其视觉感知高度依赖底层深度神经网络对障碍物模式的快速提取。</li></ul></li><li><strong>Case C：是智能体。</strong><ul><li><em>决策架构：</em> 属于<strong>学习型智能体</strong>与<strong>基于效用/目标的智能体</strong>的结合。通过强化学习自我进化，并以“赢棋”为终极效用。</li><li><em>时间与反应性：</em> 属于典型的<strong>规划式智能体（Deliberative）</strong>。利用蒙特卡洛树搜索（MCTS）在内部世界模型中模拟未来数十步的状态空间。</li></ul></li><li><strong>Case D：是智能体。</strong><ul><li><em>决策架构：</em> 属于 <strong>LLM 驱动的新范式智能体</strong>。</li><li><em>时间与反应性：</em> 属于<strong>混合式智能体（Hybrid）</strong>。既有宏观的解决步骤规划，又能在查询订单 API 时进行即时的微观观察和反馈。</li><li><em>知识表示：</em> 属于<strong>神经符号主义的实践</strong>。底层依赖神经网络（直觉、安抚情绪的语言生成），表层输出结构化的工具调用指令（符号化的 API 查询）。</li></ul></li></ul><h4 id="任务环境边界界定（PEAS）"><a href="#任务环境边界界定（PEAS）" class="headerlink" title="任务环境边界界定（PEAS）"></a>任务环境边界界定（PEAS）</h4><p><strong>【原问题】</strong><br>评估智能体运作的核心是精确描述其任务环境。通常使用包含性能度量(Performance)、环境(Environment)、执行器(Actuators)和传感器(Sensors)的 PEAS 模型来进行规约。<br>同时，环境具有部分可观察、随机性、动态性等特征。假设你需要为一个”智能健身教练”设计任务环境：它能够监测生理数据、动态调整计划、提供实时语音指导并给出饮食建议。请使用 PEAS 模型完整描述，并深度分析其环境特性。</p><p><strong>【解析】</strong><br><strong>1. PEAS 模型描述：</strong></p><ul><li><strong>性能度量 (P):</strong> 用户的各项生理指标改善幅度（体脂率下降、心肺功能提升）、用户留存率/活跃度、训练过程中的安全指标（无过劳或受伤预警）。</li><li><strong>环境 (E):</strong> 用户的物理锻炼场所（室内/户外）、用户的当前身体状况、饮食环境。</li><li><strong>执行器 (A):</strong> 智能耳机的语音合成模块（发声指导）、App 屏幕界面（展示动作视频/饮食清单）、推送通知系统。</li><li><strong>传感器 (S):</strong> 智能手表/手环（心率、血氧传感器）、手机摄像头（动作捕捉与姿态识别）、麦克风（接收用户喘息声或语音反馈）。</li></ul><p><strong>2. 环境特性分析：</strong></p><ul><li><strong>部分可观察性（Partially Observable）：</strong> 智能体无法完全感知用户的内部生理状态（如肌肉酸痛程度、昨晚的睡眠质量、心理疲劳度），只能通过心率等外部特征进行推断。</li><li><strong>高度随机性（Stochastic）：</strong> 即便提供完全相同的训练计划，用户每天的执行效果、体能消耗乃至突发事件（如突然抽筋）都是不可精准预测的。</li><li><strong>高动态性（Dynamic）：</strong> 用户的生理状态（如心率飙升）在智能体进行语音播报时仍在实时变化，要求系统必须具备极低的延迟和实时介入能力。</li><li><strong>序贯性（Sequential）：</strong> 今天的超负荷训练会直接影响明天的体能状态，智能体的决策必须具有长远的连贯性规划。</li></ul><h4 id="系统架构选型（Workflow-vs-Agent）"><a href="#系统架构选型（Workflow-vs-Agent）" class="headerlink" title="系统架构选型（Workflow vs Agent）"></a>系统架构选型（Workflow vs Agent）</h4><p><strong>【原问题】</strong><br>在实现任务自动化时，存在两种截然不同的底层逻辑：一种是基于预设规则和结构化编排的工作流（Workflow），另一种是基于大模型自主推理、动态制定计划的智能体（Agent）。某电商公司正在考虑两种方案来处理售后退款申请：</p><ul><li><strong>方案 A（Workflow）：</strong> 设计固定规则树。例如：7天内+低于100元自动退款；100-500元客服审；超500元或超7天或特殊商品转主管审。</li><li><strong>方案 B（Agent）：</strong> 搭建智能体，让其摄入退款政策，综合分析用户历史信用、商品耗损情况的图片文字描述，自主决策退款额度或驳回。<br>请分析两种方案的优缺点及适用场景，并作为架构师提出一种融合两者优势的“方案 C”。</li></ul><p><strong>【解析】</strong><br><strong>1. 方案评估：</strong></p><ul><li><strong>方案 A (Workflow):</strong><ul><li><em>优点：</em> 确定性极高（0% 幻觉），执行成本低，审计溯源极其清晰。</li><li><em>缺点：</em> 僵化。一旦出现复杂纠纷（如“商品虽然在7天内且便宜，但用户恶意退款”），系统无法识别上下文，极易被钻漏洞。</li><li><em>适用场景：</em> 业务逻辑清晰、条件可严格量化的标准化流水线。</li></ul></li><li><strong>方案 B (Agent):</strong><ul><li><em>优点：</em> 极具柔性和智能化。能处理非结构化数据（如用户长篇幅的情绪投诉），能识别潜在欺诈模式（推理能力）。</li><li><em>缺点：</em> 存在“幻觉”风险（可能误判高客单价商品给公司造成损失），每次处理的 Token 推理成本高，不可解释性强。</li><li><em>适用场景：</em> 需要大量上下文阅读、情感分析和模糊判断的个性化服务场景。</li></ul></li></ul><p><strong>2. 方案 C（混合架构 / 神经符号分层路由）：</strong><br>作为负责人，我将采用 <strong>“Workflow 为主干，Agent 为特种节点”</strong> 的混合路由分发架构：</p><ul><li><strong>第一层（规则初筛 / Workflow）：</strong> 所有请求先进入工作流。对于低风险、金额小且符合政策的请求（如单价30元的日用品），直接无感知秒退。降低计算成本，提升用户体验。</li><li><strong>第二层（Agent 智能审核节点）：</strong> 当 Workflow 触发“异常条件”（如金额&gt;500、用户退货率异常、或者含有长篇文字诉求）时，将数据打包转发给 Agent 节点。Agent 阅读政策并分析截图，给出判定建议并列出推导逻辑（Thought）。</li><li><strong>第三层（Human-in-the-loop）：</strong> Agent 的高风险决策（如驳回高价值用户的退款）必须生成工单，由人工主管（Agent 作为辅助工具）点击最终确认，确保商业安全性。</li></ul><h4 id="核心机制优化（Thought-Action-Observation-闭环扩展）"><a href="#核心机制优化（Thought-Action-Observation-闭环扩展）" class="headerlink" title="核心机制优化（Thought-Action-Observation 闭环扩展）"></a>核心机制优化（Thought-Action-Observation 闭环扩展）</h4><p><strong>【原问题】</strong><br>现代大语言模型智能体通常依赖于一个持续的“感知(Observation) - 思考(Thought) - 行动(Action)”循环机制来推进任务。<br>假设现有一个基于此机制构建的“智能旅行助手”（具备查询天气和搜索景点的工具），请思考如何通过修改或扩展其内部的 Thought-Action-Observation 循环，来为其添加以下三个高级功能：</p><ol><li>记忆功能（如记住用户偏好）。</li><li>Fallback（备选方案）功能（如门票售罄时自动调整）。</li><li>自我反思机制（如连续被用户拒绝 3 次后改变策略）。</li></ol><p><strong>【解析】</strong><br>这涉及对 Agent 内部状态机和 Prompt 工程的深度改造：</p><ul><li><strong>1. 注入长期/短期记忆：</strong><ul><li><em>改造点：</em> 发生在 <code>Thought</code> 之前的上下文拼装阶段。</li><li><em>实现思路：</em> 引入向量数据库（如 ChromaDB）。每次用户发起请求前，检索历史偏好（如“预算500内”、“讨厌爬山”），将其动态注入到 <code>System Prompt</code> 或当前轮次的初始 <code>Observation</code> 中。LLM 在生成 <code>Thought</code> 时就会强制将这些边界条件纳入规划体系。</li></ul></li><li><strong>2. 实现环境异常拦截（Fallback）：</strong><ul><li><em>改造点：</em> 强化 <code>Observation</code> 解析与新一轮 <code>Thought</code> 的纠错逻辑。</li><li><em>实现思路：</em> 当执行 <code>Action: book_ticket</code> 时，如果 API 返回“已售罄”，解析器将此结果封装为明确的警告 <code>Observation: [Error] 门票已售罄</code>。并在提示词中增加规则：“当 Observation 提示失败时，不要结束任务，你的下一个 Thought 必须分析失败原因，并生成调用 <code>get_attraction</code> 寻找附近替代景点的 Action”。</li></ul></li><li><strong>3. 元认知与自我反思（Reflection）：</strong><ul><li><em>改造点：</em> 增加外部状态追踪，并引入“双层 Thought”机制。</li><li><em>实现思路：</em> 维护一个外部计数器 <code>reject_count</code>。当用户回复“不喜欢”时计数加一。当 <code>reject_count == 3</code> 时，拦截常规循环，强行触发一个预设的 <code>Meta-Thought (自我反思)</code> 步骤。</li><li><em>Prompt 示例注入：</em> “你已连续 3 次推荐失败。现在的 Thought 必须停止盲目搜索，先分析前三次被拒绝的景点的共性（如是不是都太累了？），并主动向用户生成一个提问（Action: ask_user）以重新对齐偏好。”</li></ul></li></ul><h4 id="神经符号主义的商业落地"><a href="#神经符号主义的商业落地" class="headerlink" title="神经符号主义的商业落地"></a>神经符号主义的商业落地</h4><p><strong>【原问题】</strong><br>诺贝尔经济学奖得主丹尼尔·卡尼曼提出的双系统理论为理解神经符号主义 AI 提供了绝佳类比：系统 1 是快速、凭直觉的模式识别（类似神经网络）；系统 2 是缓慢、有条理、基于逻辑的推理（类似符号逻辑）。<br>请构思一个具体的智能体落地应用场景（如医疗诊断、法律咨询等），并详细说明在此场景中，哪些任务应由系统 1 处理？哪些应由系统 2 处理？二者如何形成完美的协同闭环？</p><p><strong>【解析】</strong><br><strong>应用场景：智能医疗急诊分诊与初步诊断系统</strong></p><ul><li><strong>系统 1（亚符号/神经网络）的任务域 —— 负责“感知与直觉”：</strong><ul><li><em>医学影像初筛：</em> 瞬间读取患者的 X 光片或 CT 扫描，识别潜在的阴影或出血点病灶模式。</li><li><em>非结构化病史提取：</em> 倾听急诊患者焦急、混乱的口述（语音识别与 NLP），瞬间提取出关键症状（如“撕裂样胸痛”、“放射到后背”）。</li><li><em>情绪安抚与拟人化交互：</em> 生成带有同理心的自然语言回复，安抚患者情绪。</li></ul></li><li><strong>系统 2（符号主义/逻辑规则）的任务域 —— 负责“严谨与安全”：</strong><ul><li><em>医学图谱验证：</em> 系统 1 提取出症状后，系统 2 必须在庞大的医学知识图谱（确定性的关系数据库）中进行严格的图谱遍历，确保“主动脉夹层”的推断符合严密的医学排他逻辑。</li><li><em>禁忌症硬性拦截：</em> 在生成用药建议（Action）前，必须通过符号逻辑计算，交叉比对患者的过敏史与药物相互作用库。这是零容错的步骤，绝不能交给带有概率性的神经网络处理。</li></ul></li><li><strong>协同工作闭环机制：</strong><br>患者上传资料 -&gt; <strong>系统 1</strong>（神经网络）瞬间完成“多模态感知”，将混乱的音频和图像转化为结构化的症状标签（Symbol） -&gt; 这些符号被送入 <strong>系统 2</strong>（知识图谱引擎与专家规则系统）进行严密的逻辑链条推理，计算出风险评级与鉴别诊断方案 -&gt; 方案送回 <strong>系统 1</strong>，由大语言模型将其润色为通俗易懂、温和的医疗建议输出给医生和患者。<br>这种协同既保证了交互的温度与对复杂数据的宽容，又守住了医疗安全的严密底线。</li></ul><h4 id="局限性与系统评估"><a href="#局限性与系统评估" class="headerlink" title="局限性与系统评估"></a>局限性与系统评估</h4><p><strong>【原问题】</strong><br>尽管由大语言模型驱动的智能体系统展现出了强大的规划和工具调用能力，但其底层机制依然存在局限性。请结合智能体的运行机制分析以下三个问题：</p><ol><li>为什么智能体在推理或合成结果时会产生”幻觉”？</li><li>智能体的交互依赖于持续的“循环”机制，如果不对循环次数进行强制限制，系统可能会陷入什么风险？</li><li>仅依靠“任务准确率”来评估一个智能体的智能程度是否足够？如果不足，还应引入哪些维度的评估指标？</li></ol><p><strong>【解析】</strong></p><ul><li><strong>1. 幻觉的底层根源：</strong><br>LLM 驱动的智能体其核心引擎本质上依然是基于概率分布的下一个 Token 预测机器。它试图拟合语言的连贯性，而非物理世界的真实客观规律。<br>当任务超出其预训练知识边界，或由于工具返回的 Observation 含有歧义时，模型内部缺乏绝对的“真理锚点”（Grounding），只能用高度自信的语言概率模型拼凑出符合语法但违背事实的伪逻辑（如瞎编 API 参数或捏造不存在的景点）。</li><li><strong>2. 无限循环的死锁风险：</strong><br>Agent Loop 极易陷入“执行焦油坑”。常见情况包括：<ul><li><em>API 连续故障：</em> 工具报错，智能体在 Thought 中决定“重试”，从而形成无休止的 <code>Error -&gt; Retry</code> 死循环，瞬间耗尽巨量 Token 额度。</li><li><em>推理死胡同（Logic Loop）：</em> 智能体的规划逻辑形成闭环，例如在网页中寻找特定按钮，未找到 -&gt; 滚动屏幕 -&gt; 重新搜索 -&gt; 依然未找到 -&gt; 再次滚动，完全无法产生跳出框架的创新思路（Meta-thinking）。</li><li><em>强制截断的必要性：</em> 设定 <code>Max_Loops</code> 是一种工程上的“防故障保险”，强制打断可能失控的系统并交由人工介入。</li></ul></li><li><strong>3. 多维评估体系的构建：</strong><br>仅依靠“准确率”是极其单薄的，因为它无法衡量达成任务所付出的代价与系统的鲁棒性。综合评估应引入：<ul><li><strong>规划效率（Efficiency of Trajectory）：</strong> 完成同一任务，优秀的智能体只需 3 轮循环，劣质的可能需要 10 轮盲目的试错。步骤冗余度是核心指标。</li><li><strong>自主度（Degree of Autonomy）：</strong> 在执行长程任务时，需要人类介入修正（Human Intervention）的频率有多高。</li><li><strong>工具掌握度（Tool-use Mastery）：</strong> 面对未见过的全新 API 说明书，智能体能否一次性正确理解其参数规范并成功调用（Zero-shot Tool Use）。</li><li><strong>错误恢复力（Error Recoverability）：</strong> 当故意给智能体注入错误的 Observation 时，它能否敏锐察觉异常、反思（Reflect）并自主修正路径，这是衡量其系统强健性的金标准。</li></ul></li></ul><h2 id="第二章-智能体发展史"><a href="#第二章-智能体发展史" class="headerlink" title="第二章 智能体发展史"></a>第二章 智能体发展史</h2><p>理解现代智能体（Agent）的底层逻辑，必须回溯其历史演进。人工智能的发展并非一蹴而就，而是一场由“痛点驱动”的范式革命：从试图用纯粹逻辑定义智能的古典时代，到反思集中式架构并走向分布式协作，再到最终确立通过大规模数据与交互进行“学习”的现代体系。</p><h3 id="基于符号与逻辑的早期智能体"><a href="#基于符号与逻辑的早期智能体" class="headerlink" title="基于符号与逻辑的早期智能体"></a>基于符号与逻辑的早期智能体</h3><p>早期人工智能研究深受数理逻辑影响，形成了第一个核心流派——<strong>符号主义（Symbolicism）</strong>（亦称传统 AI 或逻辑 AI）。<br>该流派坚信，人类的高级认知能力完全可以通过形式化的符号操作来完美复刻。此时的智能体，其“智慧”不源自学习，而是完全依靠人类专家的心血浇筑与硬编码。</p><h4 id="物理符号系统假说"><a href="#物理符号系统假说" class="headerlink" title="物理符号系统假说"></a>物理符号系统假说</h4><p>这是符号主义的理论基石，由图灵奖得主艾伦·纽厄尔（Allen Newell）与赫伯特·西蒙（Herbert A. Simon）提出。该假说将抽象的“心智”降维为工程上可实现的计算问题。</p><ul><li><strong>核心构成：</strong> 智能被抽象为由“符号”（表征外部世界）与“过程”（对符号进行创建、修改、组合的逻辑操作）组成的物理实体系统。</li><li><strong>两大论断：</strong><ul><li><strong>充分性论断：</strong> 只要拥有完备的物理符号系统，就足以产生通用智能。</li><li><strong>必要性论断：</strong> 任何展现出通用智能的实体，其底层必定是一个物理符号系统。</li></ul></li></ul><h4 id="专家系统的崛起与巅峰"><a href="#专家系统的崛起与巅峰" class="headerlink" title="专家系统的崛起与巅峰"></a>专家系统的崛起与巅峰</h4><p>在物理符号系统假说的指引下，<strong>专家系统（Expert System）</strong> 成为 20 世纪 70-80 年代最璀璨的应用成果。其核心思想是“知识与推理相分离”，试图在特定垂直领域达到人类专家水平。</p><ul><li><strong>经典架构拆解：</strong><ul><li><strong>知识库（Knowledge Base）：</strong> 通过“IF-THEN”的产生式规则（Production Rules）存储人类专家的先验知识。</li><li><strong>推理机（Inference Engine）：</strong> 独立的计算引擎，分为“数据驱动”的正向链（由已知推结果）和“目标驱动”的反向链（由假设反推所需证据）。</li></ul></li><li><strong>里程碑案例 MYCIN：</strong><ul><li>用于诊断细菌性血液感染。</li><li>采用反向链推理，通过向医生提问收集证据。</li><li><strong>核心创新：</strong> 引入了<strong>置信因子（Certainty Factor, CF）</strong>，突破了经典布尔逻辑的非黑即白，使机器具备了处理医学界普遍存在的“不确定性与模糊性”的能力。</li></ul></li></ul><h4 id="综合性微观世界智能体：SHRDLU"><a href="#综合性微观世界智能体：SHRDLU" class="headerlink" title="综合性微观世界智能体：SHRDLU"></a>综合性微观世界智能体：SHRDLU</h4><p>如果说专家系统代表了符号 AI 的“深度”，由 Terry Winograd 开发的 SHRDLU 则展示了其“广度”。</p><ul><li><strong>核心成就：</strong> 在一个名为“积木世界”的封闭三维虚拟环境中，SHRDLU 首次将自然语言解析、上下文指代消解、动作规划与记忆问答整合进一个统一的系统。</li><li><strong>历史意义：</strong> 它验证了“感知-思考-行动”闭环的可行性，是现代智能体架构的最早雏形。但其成功也暴露出一个致命弱点——它的智能仅在这个规则绝对清晰、没有例外的“玩具世界”中有效。</li></ul><h4 id="符号主义的根本性挑战"><a href="#符号主义的根本性挑战" class="headerlink" title="符号主义的根本性挑战"></a>符号主义的根本性挑战</h4><p>当符号系统试图走出实验室迈向真实的、开放的物理世界时，遭遇了不可逾越的理论柏林墙：</p><ul><li><strong>知识获取瓶颈（Knowledge Acquisition Bottleneck）：</strong> 人类的常识与直觉往往是内隐的（如“水是湿的”），无法被穷举并转化为“IF-THEN”规则。人工编码成本极高且无法规模化。</li><li><strong>框架问题（Frame Problem）：</strong> 在动态世界中，智能体执行一个动作后，机器很难通过逻辑推演来高效判断“哪些事物没有发生改变”，这会导致计算量爆炸。</li><li><strong>系统脆弱性（Brittleness）：</strong> 极度依赖预设边界，面对规则库外的新情况毫无泛化能力，系统会瞬间崩溃。</li></ul><h3 id="基于规则的对话系统实践"><a href="#基于规则的对话系统实践" class="headerlink" title="基于规则的对话系统实践"></a>基于规则的对话系统实践</h3><p>为了直观理解符号主义的运作机制与局限，早期自然语言处理的代表作——ELIZA，提供了一个极佳的研究样本。</p><h4 id="ELIZA-的设计思想与核心机制"><a href="#ELIZA-的设计思想与核心机制" class="headerlink" title="ELIZA 的设计思想与核心机制"></a>ELIZA 的设计思想与核心机制</h4><p>ELIZA 是一个模拟心理治疗师的聊天机器人程序。它的惊人之处在于：它完全不“理解”用户在说什么，却通过巧妙的错觉引发了著名的“ELIZA 效应”（让人类对其产生情感投射）。</p><ul><li><strong>模式匹配与文本替换：</strong> ELIZA 的算法本质是一个巨大的正则表达式匹配引擎。<ul><li><strong>触发机制：</strong> 扫描输入，匹配优先级最高的关键词（如 <code>mother</code>）。</li><li><strong>分解与代词转换：</strong> 将句子拆解，并执行简单的第一/第二人称反转（如 <code>I</code> 转为 <code>You</code>）。</li><li><strong>重组输出：</strong> 将转换后的文本填入预设的开放式提问模板中（如“告诉我更多关于你母亲的事”），从而将对话责任重新抛回给用户。</li></ul></li></ul><h4 id="规则驱动的根本局限性"><a href="#规则驱动的根本局限性" class="headerlink" title="规则驱动的根本局限性"></a>规则驱动的根本局限性</h4><p>从 ELIZA 的工程实现中可以清晰总结出早期 AI 的致命弱点：</p><ul><li><strong>缺乏语义锚点：</strong> 只是形式上的字符串替换，对否定语境、复杂从句毫无理解力。</li><li><strong>无状态性（Stateless）：</strong> 没有上下文记忆机制，无法维持连贯的深度对话。</li><li><strong>组合爆炸：</strong> 试图通过增加规则来覆盖所有人类对话场景，最终会导致规则库庞大到无法维护，规则间的冲突无法调和。</li></ul><h3 id="分布式智能的启蒙：心智社会"><a href="#分布式智能的启蒙：心智社会" class="headerlink" title="分布式智能的启蒙：心智社会"></a>分布式智能的启蒙：心智社会</h3><p>面对符号系统“大一统”中央推理引擎的僵化，马文·明斯基（Marvin Minsky）提出了颠覆性的理论，将 AI 研究的视角从“单体巨兽”转向了“群体生态”。</p><h4 id="对单一整体智能模型的反思"><a href="#对单一整体智能模型的反思" class="headerlink" title="对单一整体智能模型的反思"></a>对单一整体智能模型的反思</h4><p>明斯基质疑了构建全知全能中央处理器的可行性。他指出，自然智能并非基于一套完美的逻辑原理，而是由视觉、情感、推理等无数个异构的子过程拼凑而成的“大杂烩”。</p><h4 id="作为协作体的智能与涌现机制"><a href="#作为协作体的智能与涌现机制" class="headerlink" title="作为协作体的智能与涌现机制"></a>作为协作体的智能与涌现机制</h4><p>在《心智社会》中，明斯基重新定义了智能的产生方式：</p><ul><li><strong>无心的智能体：</strong> 底层 Agent（如“寻找线条”的视觉 Agent）本身是极度愚蠢且没有意识的，仅执行极简指令。</li><li><strong>机构（Agency）与激活链：</strong> 这些底层 Agent 组成机构，通过相互之间的激活与抑制信号传递控制流。</li><li><strong>涌现（Emergence）：</strong> 高级智能（如“搭积木塔”的目标）并非由最高指挥官写明步骤，而是由无数底层无心 Agent 在局部交互、彼此竞争与协作中<strong>自发涌现</strong>出来的宏观现象。</li></ul><h4 id="对多智能体系统（MAS）的理论启发"><a href="#对多智能体系统（MAS）的理论启发" class="headerlink" title="对多智能体系统（MAS）的理论启发"></a>对多智能体系统（MAS）的理论启发</h4><p>这一哲学思考直接孕育了今天的多智能体系统（MAS）理论：</p><ul><li><strong>去中心化控制：</strong> 彻底抛弃中央大脑，研究基于局部状态的自组织协同。</li><li><strong>社会性抽象：</strong> 将人类社会的沟通协议（如契约网、协商机制）引入计算实体的交互中，AI 从“单兵作战”进化为“虚拟社会”。</li></ul><h3 id="学习范式的演进与现代智能体"><a href="#学习范式的演进与现代智能体" class="headerlink" title="学习范式的演进与现代智能体"></a>学习范式的演进与现代智能体</h3><p>既然复杂的规则无法被人类完美设计出来，智能体就必须具备“自主学习”的能力。这引发了长达数十年的学习范式革命。</p><h4 id="从符号逻辑到联结主义"><a href="#从符号逻辑到联结主义" class="headerlink" title="从符号逻辑到联结主义"></a>从符号逻辑到联结主义</h4><p><strong>联结主义（Connectionism）</strong>（即深度学习的前身）从仿生学汲取灵感，主张自下而上的智能构建路径。</p><ul><li><strong>分布式权重表示：</strong> 知识不再是清晰的 IF-THEN 规则，而是被隐晦地分布在成千上万个人工神经元的连接权重之中。</li><li><strong>自适应优化：</strong> 机器通过接触海量样本，利用反向传播等算法自主迭代权重，在模式识别（如视觉、语音）上彻底击败了脆弱的符号系统。</li></ul><h4 id="强化学习与序贯决策"><a href="#强化学习与序贯决策" class="headerlink" title="强化学习与序贯决策"></a>强化学习与序贯决策</h4><p>如果说联结主义解决了“感知环境”的问题，那么<strong>强化学习（Reinforcement Learning, RL）</strong> 则解决了智能体如何“行动”的问题。</p><ul><li><strong>试错机制：</strong> 智能体在未知环境中通过不断试错（Trial and Error）来积累经验。</li><li><strong>核心循环：</strong> 智能体在特定状态（State）下采取行动（Action），环境随之改变并反馈一个延迟的奖励信号（Reward）。</li><li><strong>远见规划：</strong> 智能体优化的目标不是眼前的蝇头小利，而是全局累积回报最大化（如 AlphaGo 为了最终赢棋可以牺牲局部棋子），从而赋予了机器序贯决策的能力。</li></ul><h4 id="大规模预训练与通用基础模型"><a href="#大规模预训练与通用基础模型" class="headerlink" title="大规模预训练与通用基础模型"></a>大规模预训练与通用基础模型</h4><p>强化学习面临着“冷启动”的难题：如果让智能体从零开始试错，效率极低且缺乏常识。NLP 领域的“预训练-微调”范式提供了最终的解法。</p><ul><li><strong>知识的隐式压缩：</strong> 通过在万亿级 Token 的互联网语料上进行“预测下一个词”的自监督学习，庞大的世界知识、语法逻辑与常识被压缩进了神经网络的参数中。</li><li><strong>涌现能力（Emergent Abilities）：</strong> 当模型规模突破临界点，LLM 突然具备了未被直接训练过的高级能力，如少样本学习（Few-shot learning）和通过“思维链（CoT）”进行的逻辑推理。</li></ul><h4 id="大语言模型驱动的现代智能体架构"><a href="#大语言模型驱动的现代智能体架构" class="headerlink" title="大语言模型驱动的现代智能体架构"></a>大语言模型驱动的现代智能体架构</h4><p>历史的支流最终汇聚。现代 LLM Agent 是符号主义的逻辑规划能力与联结主义的感知学习能力的集大成者（神经符号混合）。</p><ul><li><strong>感知模块：</strong> 将多模态的环境反馈转化为自然语言文本。</li><li><strong>记忆与思考模块（LLM 核心）：</strong> LLM 充当中枢大脑，结合记忆历史，通过思维链将复杂任务拆解为子任务（规划与反思）。</li><li><strong>执行模块：</strong> 将 LLM 的决策转化为结构化的工具调用（API、代码执行器），从而对外部物理或数字世界施加实质性影响，完成动态闭环。</li></ul><h3 id="课后习题解析-1"><a href="#课后习题解析-1" class="headerlink" title="课后习题解析"></a>课后习题解析</h3><h4 id="物理符号系统假说的当代审视"><a href="#物理符号系统假说的当代审视" class="headerlink" title="物理符号系统假说的当代审视"></a>物理符号系统假说的当代审视</h4><p><strong>【原问题】</strong><br>物理符号系统假说（PSSH）由纽厄尔和西蒙提出，认为智能的本质是物理实体对符号的计算与处理，这成为了符号主义时代的理论基石。请分析：</p><ol><li>该假说的“充分性论断”和“必要性论断”分别包含什么深刻含义？</li><li>结合智能体演进史，符号主义在尝试构建复杂系统时遭遇的哪些实际阻碍，直接对该假说的“充分性”提出了致命挑战？</li><li>当前由大语言模型（LLM）驱动的现代智能体，是否仍然符合这一物理符号系统假说？请结合神经符号主义进行辨析。</li></ol><p><strong>【解析】</strong></p><ol><li><strong>论断拆解：</strong><ul><li><em>充分性论断</em>意味着“算力+正确的符号操作系统 = 智能”。它给出了工程上的乐观承诺：只要规则够完备，机器必能产生智能，无需依赖任何神秘的生物灵魂。</li><li><em>必要性论断</em>意味着“人类大脑的本质也是一台符号处理机”。它是一种哲学还原论，认为即使是直觉、情感，底层也是符号的物理运算。</li></ul></li><li><strong>充分性的现实破产：</strong><br>在封闭的“积木世界（SHRDLU）”中充分性看似成立，但在真实世界遭遇了<strong>常识获取瓶颈</strong>与<strong>框架问题</strong>。<br>因为真实世界的知识是连续的、高度上下文依赖的，甚至包含不可言传的默会知识。这证明了：试图用离散的、有限的符号规则来“充分”表征连续、无限的物理世界，在计算复杂性上是一条死胡同。</li><li><strong>LLM 与 PSSH 的辩证关系：</strong><br>这是一个经典的“神经符号主义”前沿议题。<ul><li><strong>在微观/底层架构上（不符合）：</strong> LLM 的内核是人工神经网络（联结主义）。其内部运作依赖于连续的浮点数矩阵乘法和非线性激活，不存在明确定义的离散符号和“IF-THEN”逻辑分支，知识是高度弥散且分布式的。</li><li><strong>在宏观/应用表现上（符合）：</strong> 当 LLM 被封装为 Agent 时，它的输入和输出（自然语言、代码、API 调用参数）都是极其严密的“符号体系”。Agent 展现出的“思维链（CoT）”推理，本质上就是对语言符号的创造、重组与逻辑推演。</li><li><strong>结论：</strong> 现代 LLM Agent 是 PSSH 在更高维度的复兴。它用联结主义的黑盒代替了脆弱的人工知识库，但依然依靠符号（语言）来实现高级认知和逻辑控制。</li></ul></li></ol><h4 id="专家系统的商业化困境与系统重构"><a href="#专家系统的商业化困境与系统重构" class="headerlink" title="专家系统的商业化困境与系统重构"></a>专家系统的商业化困境与系统重构</h4><p><strong>【原问题】</strong><br>在 20 世纪 70 年代，MYCIN 专家系统通过“IF-THEN”规则和置信因子，在血液感染的诊断上达到了人类专家的水平。然而，尽管技术指标优异，MYCIN 最终并未被大规模部署于临床。请思考：</p><ol><li>除了技术层面的“知识难以获取”和“系统脆弱性”，还有哪些非技术因素（如伦理、法律、交互）阻碍了专家系统在医疗等高风险领域的落地？</li><li>如果现在由你主导设计一款医疗辅助诊断智能体，你会如何融合现代架构来克服 MYCIN 当年的局限？</li><li>在大模型横行的今天，是否还有特定垂直领域，基于纯规则的专家系统依然是优于深度学习的最佳选择？请举例论证。</li></ol><p><strong>【解析】</strong></p><ol><li><strong>非技术因素：专家系统落地受阻的关键原因：</strong><ul><li><strong>责任归属与法律真空：</strong> 在医疗场景中，如果系统给出的诊断建议导致误诊或治疗失误，责任应由谁承担并不清晰。在缺乏明确法律框架的情况下，医疗机构往往倾向于避免引入可能带来额外法律风险的系统。</li><li><strong>与医疗工作流的不匹配：</strong> MYCIN 运行在早期终端环境中，医生需要手动输入大量病理指标和实验数据。这种交互方式与临床工作节奏并不匹配，增加了医生的操作负担，也降低了门诊效率，从而影响了系统的接受度。</li><li><strong>医生对系统建议的信任问题：</strong> 尽管 MYCIN 能够提供推理过程，但其输出形式通常是概率或置信度数值。相比医生在临床实践中依赖的综合观察、经验判断和直觉，这种抽象的数值结果较难建立直观信任，因此在实际决策中很难成为主要依据。</li></ul></li><li><strong>现代医疗 Agent 的架构设计：</strong><br>如果在当前技术条件下重新设计类似系统，可以采用神经符号融合（Neuro-Symbolic）架构，将大模型与结构化知识系统结合。<ul><li><em>感知与交互层（LLM）：</em> 使用多模态 LLM 作为前台。医生只需用语音口述，或系统直接扫描电子病历（EMR）及化验单图像，LLM 负责将非结构化信息自动提取为结构化数据，彻底消除输入痛点。</li><li><em>知识推理层（规则与知识图谱）：</em> 将提取的数据送入后端的医学知识图谱与现代专家规则引擎进行严格的逻辑推演和禁忌症拦截。可以降低模型幻觉带来的风险，并提高系统的可验证性。</li><li><em>解释输出：</em> LLM 进行语言组织，将规则推理结果转化为：易读的诊断建议、清晰的推理依据、相关医学证据或指南引用。医生仍然是最终决策者，系统主要提供结构化的信息支持和辅助判断。</li></ul></li><li><strong>纯规则系统的当代优势领域：</strong><br>在 <strong>零容错、强监管、需绝对可解释性</strong> 的领域，专家系统依然不可替代。<ul><li><em>金融反洗钱与合规审查：</em> 必须基于央行下发的死规定（如某类交易金额触发红线必须冻结）。深度学习的概率输出和不可解释性（无法向监管机构提供数学证明）在这里是致命缺陷。</li><li><em>工业核心安全控制系统：</em> 如核电站冷却系统监控，必须遵循严苛的物理定律 IF-THEN 熔断机制，不能容忍幻觉。</li></ul></li></ol><h4 id="规则驱动引擎的“组合爆炸”与架构对比"><a href="#规则驱动引擎的“组合爆炸”与架构对比" class="headerlink" title="规则驱动引擎的“组合爆炸”与架构对比"></a>规则驱动引擎的“组合爆炸”与架构对比</h4><p><strong>【原问题】</strong><br>ELIZA 是早期著名的规则驱动聊天机器人，通过正则匹配、代词反转和模板重组来伪装心理医生。但其处理开放域对话时极具局限性。请基于此机制：</p><ol><li>构思如何从工程代码层面为其扩展“上下文记忆”功能（例如让系统记住用户的名字并用于后续回复）。</li><li>从知识表示、状态管理和泛化能力三个维度，深度对比你扩展后的规则引擎与当代基于大语言模型的对话系统（如 ChatGPT）。</li><li>为什么基于规则的方法在应对开放域对话时必然走向“组合爆炸”的死局？能否用简单的数学逻辑来证明这种不可维护性？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>扩展记忆的工程思路：</strong><br>引入全局字典变量（如 <code>user_memory = {}</code>）。增加特定的实体抽取规则：<ul><li>触发规则：当正则匹配到 <code>I am (.*)</code> 时，将捕获的组存入 <code>user_memory['name']</code>。</li><li>模板修改：在其它重组模板中注入该变量。例如匹配到 <code>I feel sad</code> 时，返回 <code>Why do you feel sad, {user_memory['name']}?</code>。</li></ul></li><li><strong>多维度的代差级对比：</strong><ul><li><em>知识表示：</em> ELIZA 知识是显式的字符串（硬编码正则）；ChatGPT 的知识是隐式高维向量权重（包含人类语言的潜在概率分布）。</li><li><em>状态管理：</em> ELIZA 是短时变量存储，遇到变量冲突极难处理；ChatGPT 基于自注意力机制（Self-Attention），能够在极长上下文窗口内综合考量所有对话历史的依赖关系。</li><li><em>泛化能力：</em> ELIZA 对未在正则规则库中的句式完全抓瞎（Zero-generalization）；ChatGPT 可以基于语义张量空间，完美理解并回应从未在训练集中出现过的生僻组合句型。</li></ul></li><li><strong>“组合爆炸”的数学论证：</strong><br>假设人类语言中常用的意图有 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="2.009ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 888 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g></g></g></svg></mjx-container> 种，表达情绪的副词有 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="2.378ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 1051 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D440" d="M289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629Z"></path></g></g></g></svg></mjx-container> 种，对话可能发生的历史轮次（上下文深度）为 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="2.011ex" height="1.545ex" role="img" focusable="false" viewBox="0 -683 889 683"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D43E" d="M285 628Q285 635 228 637Q205 637 198 638T191 647Q191 649 193 661Q199 681 203 682Q205 683 214 683H219Q260 681 355 681Q389 681 418 681T463 682T483 682Q500 682 500 674Q500 669 497 660Q496 658 496 654T495 648T493 644T490 641T486 639T479 638T470 637T456 637Q416 636 405 634T387 623L306 305Q307 305 490 449T678 597Q692 611 692 620Q692 635 667 637Q651 637 651 648Q651 650 654 662T659 677Q662 682 676 682Q680 682 711 681T791 680Q814 680 839 681T869 682Q889 682 889 672Q889 650 881 642Q878 637 862 637Q787 632 726 586Q710 576 656 534T556 455L509 418L518 396Q527 374 546 329T581 244Q656 67 661 61Q663 59 666 57Q680 47 717 46H738Q744 38 744 37T741 19Q737 6 731 0H720Q680 3 625 3Q503 3 488 0H478Q472 6 472 9T474 27Q478 40 480 43T491 46H494Q544 46 544 71Q544 75 517 141T485 216L427 354L359 301L291 248L268 155Q245 63 245 58Q245 51 253 49T303 46H334Q340 37 340 35Q340 19 333 5Q328 0 317 0Q314 0 280 1T180 2Q118 2 85 2T49 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Z"></path></g></g></g></svg></mjx-container>。<br>在规则引擎中，为了实现完美的上下文连贯，程序员必须穷举所有的组合可能。系统需要的 IF-THEN 规则数量大致与 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="15.416ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 6813.9 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mo" transform="translate(763,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1152,0)"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mo" transform="translate(2262.2,0)"><path data-c="D7" d="M630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29Z"></path></g><g data-mml-node="mi" transform="translate(3262.4,0)"><path data-c="1D440" d="M289 629Q289 635 232 637Q208 637 201 638T194 648Q194 649 196 659Q197 662 198 666T199 671T201 676T203 679T207 681T212 683T220 683T232 684Q238 684 262 684T307 683Q386 683 398 683T414 678Q415 674 451 396L487 117L510 154Q534 190 574 254T662 394Q837 673 839 675Q840 676 842 678T846 681L852 683H948Q965 683 988 683T1017 684Q1051 684 1051 673Q1051 668 1048 656T1045 643Q1041 637 1008 637Q968 636 957 634T939 623Q936 618 867 340T797 59Q797 55 798 54T805 50T822 48T855 46H886Q892 37 892 35Q892 19 885 5Q880 0 869 0Q864 0 828 1T736 2Q675 2 644 2T609 1Q592 1 592 11Q592 13 594 25Q598 41 602 43T625 46Q652 46 685 49Q699 52 704 61Q706 65 742 207T813 490T848 631L654 322Q458 10 453 5Q451 4 449 3Q444 0 433 0Q418 0 415 7Q413 11 374 317L335 624L267 354Q200 88 200 79Q206 46 272 46H282Q288 41 289 37T286 19Q282 3 278 1Q274 0 267 0Q265 0 255 0T221 1T157 2Q127 2 95 1T58 0Q43 0 39 2T35 11Q35 13 38 25T43 40Q45 46 65 46Q135 46 154 86Q158 92 223 354T289 629Z"></path></g><g data-mml-node="mo" transform="translate(4535.7,0)"><path data-c="D7" d="M630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29Z"></path></g><g data-mml-node="mi" transform="translate(5535.9,0)"><path data-c="1D43E" d="M285 628Q285 635 228 637Q205 637 198 638T191 647Q191 649 193 661Q199 681 203 682Q205 683 214 683H219Q260 681 355 681Q389 681 418 681T463 682T483 682Q500 682 500 674Q500 669 497 660Q496 658 496 654T495 648T493 644T490 641T486 639T479 638T470 637T456 637Q416 636 405 634T387 623L306 305Q307 305 490 449T678 597Q692 611 692 620Q692 635 667 637Q651 637 651 648Q651 650 654 662T659 677Q662 682 676 682Q680 682 711 681T791 680Q814 680 839 681T869 682Q889 682 889 672Q889 650 881 642Q878 637 862 637Q787 632 726 586Q710 576 656 534T556 455L509 418L518 396Q527 374 546 329T581 244Q656 67 661 61Q663 59 666 57Q680 47 717 46H738Q744 38 744 37T741 19Q737 6 731 0H720Q680 3 625 3Q503 3 488 0H478Q472 6 472 9T474 27Q478 40 480 43T491 46H494Q544 46 544 71Q544 75 517 141T485 216L427 354L359 301L291 248L268 155Q245 63 245 58Q245 51 253 49T303 46H334Q340 37 340 35Q340 19 333 5Q328 0 317 0Q314 0 280 1T180 2Q118 2 85 2T49 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Z"></path></g><g data-mml-node="mo" transform="translate(6424.9,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container> 呈指数级相关。<br>自然语言的词汇组合趋于无限大，一旦引入上下文约束，规则树的分支将呈几何级数膨胀。这导致任何微小的规则修改，都会在庞大的树状逻辑中引发蝴蝶效应式的冲突（Rule Conflict），使得系统从工程角度上彻底不可维护。</li></ol><h4 id="心智社会与现代多智能体网络（MAS）的碰撞"><a href="#心智社会与现代多智能体网络（MAS）的碰撞" class="headerlink" title="心智社会与现代多智能体网络（MAS）的碰撞"></a>心智社会与现代多智能体网络（MAS）的碰撞</h4><p><strong>【原问题】</strong><br>马文·明斯基在《心智社会》中提出，高级智能并非源于单一完美的中央处理核心，而是由海量“无心（Mindless）”的、极其简单的子智能体（如专门负责识别线条或执行抓握的模块）通过动态的激活与抑制信号协作涌现而成的。</p><ol><li>在其描述的“搭建积木塔”场景中，如果底层的 GRASP（抓握）智能体发生硬件或逻辑损坏，整个心智社会系统会呈现出怎样的状态？这种去中心化架构的根本利弊是什么？</li><li>将明斯基的构想与当前主流的生成式多智能体协作框架（如 MetaGPT 中的产品经理、架构师、工程师角色扮演）进行对比，两者在“节点智能度”与“协作拓扑”上有何异同？</li><li>既然现代大模型（LLM）已经具备了极其强大的中央推理能力，这是否宣告了“智能由无心模块涌现”的心智社会理论在当代已经被证伪？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>节点失效与架构利弊：</strong><br>如果 GRASP 损坏，上层的 ADD-BLOCK 机构会被无限期挂起（无法完成），但系统不会像集中式程序那样直接抛出致命异常并崩溃。系统的其他机构（如 VISION 视觉机构）依然可以正常运作，这展现了去中心化架构的<strong>优雅降级（Graceful Degradation）</strong> 能力。<ul><li><em>优势：</em> 极高的鲁棒性、高并发处理能力，以及自下而上的强泛化涌现能力。</li><li><em>劣势：</em> 系统的宏观行为极度不可控，调试（Debug）极其困难。由于缺乏中央指令，很难预测局部规则修改会对全局造成何种混沌的影响。</li></ul></li><li><strong>古典心智理论 VS 现代 MAS 框架：</strong><ul><li><strong>差异（节点智能度）：</strong> 明斯基系统中的 Agent 是“极瘦”的（Thin Agent），只能执行极简的布尔逻辑；而 MetaGPT 中的 Agent 是“极胖”的（Fat Agent），每个节点（如虚拟产品经理）背后都有庞大 LLM 支撑，具备极高的个体认知带宽与自然语言推理能力。</li><li><strong>关联（协作拓扑）：</strong> 宏观的协作思想一脉相承。都不依赖硬编码的工作流图（Workflow），而是依赖个体间的协议传递（如明斯基的抑制信号，现代 MAS 的自然语言消息总线），最终“涌现”出复杂的成果（如搭好积木，或写出一个完整的软件工程）。</li></ul></li><li><strong>对心智社会理论的当代重构：</strong><br>并没有被证伪，反而以更深层的方式被印证了。我们可以从两个尺度来看：<ul><li><em>微观尺度（LLM 内部）：</em> 一个 LLM 的内核（Transformer 架构）极具“心智社会”特征。数十亿个神经元、成百上千个注意力头（Attention Heads），它们每一个都没有意识，只做简单的向量点乘（Mindless）。但当它们在深层网络中相互组合、激活与抑制时，强大的宏观逻辑推理能力就“涌现”了。</li><li><em>宏观尺度（MAS 外部）：</em> 随着任务复杂度超越单一 LLM 的上下文极限，人类又开始将多个 LLM 组成“社会”（如不同角色的 Agent），在更高维度上重演了明斯基的去中心化协作理念。</li></ul></li></ol><h4 id="学习范式演进：RL-的试错机制与预训练的世界模型"><a href="#学习范式演进：RL-的试错机制与预训练的世界模型" class="headerlink" title="学习范式演进：RL 的试错机制与预训练的世界模型"></a>学习范式演进：RL 的试错机制与预训练的世界模型</h4><p><strong>【原问题】</strong><br>智能体的能力获取经历了从“人工硬编码”到“从交互中学习”，再到“从海量静态数据中预训练”的范式跃迁。请分析：</p><ol><li>以训练一个能通关“超级马里奥”游戏的智能体为例，如果分别采用监督学习（SL）和强化学习（RL），在数据需求形态和核心驱动机制上有何本质差异？为何强化学习天然适合此类序贯决策任务？</li><li>预训练-微调（Pre-training &amp; Fine-tuning）范式是如何通过自监督学习彻底打破符号时代的“知识获取瓶颈”的？它所构建的世界模型在知识表示上与专家系统的知识库有何根本区别？</li><li>在当前构建顶尖大语言模型（如 GPT-4）的完整流程中，强化学习（如 RLHF）扮演了怎样不可或缺的“修剪者”角色？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>马里奥通关：SL 与 RL 的殊途：</strong><ul><li><em>监督学习（SL）：</em> 极度依赖<strong>离线的高质量人类标注数据</strong>。你需要录制人类高手的游玩视频，提取每一帧画面（特征 X）及人类当时按下的手柄按键（标签 Y）。机器只是在拟合“在这个画面下，人类会怎么按”。一旦遇到人类未遇到过的画面，机器瞬间失能。</li><li><em>强化学习（RL）：</em> 不需要人类教。只需提供一个游戏模拟器（环境）。机器随机乱按（Action），模拟器返回游戏画面（状态 S）和分数增减/死亡（奖励 R）。机器通过“试错机制”自行摸索，甚至能发现人类从未想过的高分捷径。</li><li><em>为何适合序贯决策：</em> 马里奥是一个极度依赖时间纵深的场景（现在按起跳，是为了两秒后踩死怪物）。RL 优化的目标是<strong>最大化未来累积期望回报</strong>，天然具备时间上的“深谋远虑”，这是仅关注当前静态映射的 SL 无法做到的。</li></ul></li><li><strong>预训练对知识瓶颈的粉碎式打击：</strong><ul><li><em>突破瓶颈：</em> 预训练最伟大的成就在于采用了“自监督”。它不再需要人类知识工程师去苦心编撰规则，也不需要昂贵的人工标注。它直接将互联网海量文本作为自己的老师，“挖掉一句话的最后一个词，让模型猜”，通过无限算力暴力汲取人类文明的知识。</li><li><em>表示维度的降维打击：</em> 专家系统的知识是<strong>离散的、图谱式的、清晰边界</strong>的，极度脆弱；而预训练模型的知识是<strong>连续的、分布式的隐性特征（高维稠密张量）</strong>，具备强大的语义容错性与泛化重组能力。</li></ul></li><li><strong>RLHF（基于人类反馈的强化学习）的关键作用：</strong><br>纯粹通过预训练得到的 LLM 只是一个“词语接龙”的概率生成器，它可能满口脏话，也可能在用户询问“如何制作炸弹”时提供详细教程。<br>强化学习（RLHF）在预训练后介入，扮演了 <strong>“社会规范对齐者”</strong> 的角色。通过建立人类偏好的奖励模型（Reward Model），并使用 RL 算法（如 PPO）不断惩罚模型的不当输出，奖励符合人类价值观和指令意图的回答。它将一个狂野的“百科全书引擎”最终打磨成了一个安全、有用、可控的现代智能助理（Agent Brain）。</li></ol><h2 id="第三章-大语言模型基础"><a href="#第三章-大语言模型基础" class="headerlink" title="第三章 - 大语言模型基础"></a>第三章 - 大语言模型基础</h2><p>现代多智能体系统（MAS）的认知中枢是大语言模型（LLM）。理解智能体如何处理指令、进行规划与推理，必须深入语言模型的底层架构、交互机制及其能力边界。</p><h3 id="语言模型演进：从统计学到神经网络"><a href="#语言模型演进：从统计学到神经网络" class="headerlink" title="语言模型演进：从统计学到神经网络"></a>语言模型演进：从统计学到神经网络</h3><p>语言模型的核心数学任务是估计特定词序列（句子）的联合概率分布，借此评估文本的自然度与合理性。</p><h4 id="统计语言模型与马尔可夫假设"><a href="#统计语言模型与马尔可夫假设" class="headerlink" title="统计语言模型与马尔可夫假设"></a>统计语言模型与马尔可夫假设</h4><p>在深度学习普及之前，统计方法是主流。基于概率链式法则，计算整个句子的概率需要庞大的条件概率连乘，这在现实语料中极易遇到数据稀疏问题。</p><ul><li><strong>马尔可夫假设（Markov Assumption）：</strong> 引入局部性近似，假设当前词的出现概率仅依赖于其前有限的 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="5.906ex" height="1.731ex" role="img" focusable="false" viewBox="0 -683 2610.4 765"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mo" transform="translate(1110.2,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(2110.4,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g></svg></mjx-container> 个词。由此衍生出 <strong>N-gram 模型</strong>（如 Bigram, Trigram）。</li><li><strong>最大似然估计（MLE）：</strong> N-gram 的条件概率通过语料库中的频次直接统计估算。</li><li><strong>根本局限：</strong><ul><li><strong>数据稀疏性（Sparsity）：</strong> 未在训练集中出现的词元组合概率为零，无法处理长尾表达。</li><li><strong>语义泛化缺失：</strong> 词被视为孤立的离散符号（One-hot），模型无法捕捉“同义词”或“近义词”之间的内在语义关联。</li></ul></li></ul><h4 id="词嵌入与前馈神经网络"><a href="#词嵌入与前馈神经网络" class="headerlink" title="词嵌入与前馈神经网络"></a>词嵌入与前馈神经网络</h4><p>为解决离散符号的泛化问题，前馈神经网络语言模型引入了连续向量空间表征。</p><ul><li><strong>分布式语义表示（Word Embedding）：</strong> 将高维稀疏的词汇映射为低维稠密的连续向量。在此空间内，语义相似度可通过几何关系（如余弦相似度）直接度量。</li><li><strong>网络拟合：</strong> 神经网络通过反向传播调整词向量权重，使其在预测任务中捕获丰富的上下文语义（如抽象的类比关系）。</li><li><strong>局限：</strong> 依然受限于固定的上下文窗口大小，无法处理任意长度的序列。</li></ul><h4 id="循环神经网络（RNN）与长短时记忆（LSTM）"><a href="#循环神经网络（RNN）与长短时记忆（LSTM）" class="headerlink" title="循环神经网络（RNN）与长短时记忆（LSTM）"></a>循环神经网络（RNN）与长短时记忆（LSTM）</h4><p>为打破固定窗口限制，模型引入了时序递归结构。</p><ul><li><strong>隐藏状态（Hidden State）：</strong> RNN 引入状态向量作为“短期记忆”，当前输出依赖当前输入与上一时刻的隐藏状态，实现了信息的时序传递。</li><li><strong>长期依赖危机：</strong> 由于序列深度等同于网络深度，标准 RNN 在反向传播时极易发生梯度消失或梯度爆炸，导致长程上下文记忆丢失。</li><li><strong>LSTM 的门控机制：</strong> 通过引入独立的“细胞状态（Cell State）”主干道，并辅以<strong>遗忘门、输入门、输出门</strong>，使网络能够显式学习“保留什么”与“遗忘什么”，大幅缓解了长程梯度衰减问题。</li></ul><h3 id="Transformer-架构深度剖析"><a href="#Transformer-架构深度剖析" class="headerlink" title="Transformer 架构深度剖析"></a>Transformer 架构深度剖析</h3><p>RNN 强依赖时序步骤，导致无法利用 GPU 进行大规模并行矩阵运算。Transformer 架构彻底抛弃了循环结构，代之以全局注意力机制，奠定了大模型时代的硬件加速与规模化基础。</p><h4 id="编码器-解码器全局框架"><a href="#编码器-解码器全局框架" class="headerlink" title="编码器-解码器全局框架"></a>编码器-解码器全局框架</h4><p>Transformer 最初为机器翻译等端到端任务设计，包含两个核心解耦模块：</p><ul><li><strong>编码器（Encoder）：</strong> 负责并行提取输入序列的全局上下文特征，生成富含语境的表征矩阵。</li><li><strong>解码器（Decoder）：</strong> 采用自回归方式，结合自身已生成的上文与编码器提供的特征矩阵，逐词生成目标序列。</li></ul><h4 id="自注意力与多头注意力机制（Self-Attention）"><a href="#自注意力与多头注意力机制（Self-Attention）" class="headerlink" title="自注意力与多头注意力机制（Self-Attention）"></a>自注意力与多头注意力机制（Self-Attention）</h4><p>自注意力机制是架构的核心，允许序列中的每一个词元直接与序列中所有其他词元进行交互，计算复杂度与序列物理距离无关。</p><ul><li><strong>Q-K-V 机制：</strong> 对每个输入向量进行线性映射，生成查询向量（Query）、键向量（Key）和值向量（Value）。</li><li><strong>缩放点积注意力：</strong> 计算 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.439ex;" xmlns="http://www.w3.org/2000/svg" width="1.79ex" height="2.032ex" role="img" focusable="false" viewBox="0 -704 791 898"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D444" d="M399 -80Q399 -47 400 -30T402 -11V-7L387 -11Q341 -22 303 -22Q208 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435Q740 255 592 107Q529 47 461 16L444 8V3Q444 2 449 -24T470 -66T516 -82Q551 -82 583 -60T625 -3Q631 11 638 11Q647 11 649 2Q649 -6 639 -34T611 -100T557 -165T481 -194Q399 -194 399 -87V-80ZM636 468Q636 523 621 564T580 625T530 655T477 665Q429 665 379 640Q277 591 215 464T153 216Q153 110 207 59Q231 38 236 38V46Q236 86 269 120T347 155Q372 155 390 144T417 114T429 82T435 55L448 64Q512 108 557 185T619 334T636 468ZM314 18Q362 18 404 39L403 49Q399 104 366 115Q354 117 347 117Q344 117 341 117T337 118Q317 118 296 98T274 52Q274 18 314 18Z"></path></g></g></g></svg></mjx-container> 与 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="3.443ex" height="1.904ex" role="img" focusable="false" viewBox="0 -841.7 1521.8 841.7"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msup"><g data-mml-node="mi"><path data-c="1D43E" d="M285 628Q285 635 228 637Q205 637 198 638T191 647Q191 649 193 661Q199 681 203 682Q205 683 214 683H219Q260 681 355 681Q389 681 418 681T463 682T483 682Q500 682 500 674Q500 669 497 660Q496 658 496 654T495 648T493 644T490 641T486 639T479 638T470 637T456 637Q416 636 405 634T387 623L306 305Q307 305 490 449T678 597Q692 611 692 620Q692 635 667 637Q651 637 651 648Q651 650 654 662T659 677Q662 682 676 682Q680 682 711 681T791 680Q814 680 839 681T869 682Q889 682 889 672Q889 650 881 642Q878 637 862 637Q787 632 726 586Q710 576 656 534T556 455L509 418L518 396Q527 374 546 329T581 244Q656 67 661 61Q663 59 666 57Q680 47 717 46H738Q744 38 744 37T741 19Q737 6 731 0H720Q680 3 625 3Q503 3 488 0H478Q472 6 472 9T474 27Q478 40 480 43T491 46H494Q544 46 544 71Q544 75 517 141T485 216L427 354L359 301L291 248L268 155Q245 63 245 58Q245 51 253 49T303 46H334Q340 37 340 35Q340 19 333 5Q328 0 317 0Q314 0 280 1T180 2Q118 2 85 2T49 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Z"></path></g><g data-mml-node="mi" transform="translate(974,363) scale(0.707)"><path data-c="1D447" d="M40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40Z"></path></g></g></g></g></svg></mjx-container> 的点积评估词元相关性，除以维度缩放因子 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.372ex;" xmlns="http://www.w3.org/2000/svg" width="4.128ex" height="2.398ex" role="img" focusable="false" viewBox="0 -895.6 1824.4 1060"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msqrt"><g transform="translate(853,0)"><g data-mml-node="msub"><g data-mml-node="mi"><path data-c="1D451" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path></g><g data-mml-node="mi" transform="translate(553,-150) scale(0.707)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g></g></g><g data-mml-node="mo" transform="translate(0,35.6)"><path data-c="221A" d="M95 178Q89 178 81 186T72 200T103 230T169 280T207 309Q209 311 212 311H213Q219 311 227 294T281 177Q300 134 312 108L397 -77Q398 -77 501 136T707 565T814 786Q820 800 834 800Q841 800 846 794T853 782V776L620 293L385 -193Q381 -200 366 -200Q357 -200 354 -197Q352 -195 256 15L160 225L144 214Q129 202 113 190T95 178Z"></path></g><rect width="971.4" height="60" x="853" y="775.6"></rect></g></g></g></svg></mjx-container>（防止 Softmax 梯度溢出），最后乘以 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.05ex;" xmlns="http://www.w3.org/2000/svg" width="1.74ex" height="1.595ex" role="img" focusable="false" viewBox="0 -683 769 705"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D449" d="M52 648Q52 670 65 683H76Q118 680 181 680Q299 680 320 683H330Q336 677 336 674T334 656Q329 641 325 637H304Q282 635 274 635Q245 630 242 620Q242 618 271 369T301 118L374 235Q447 352 520 471T595 594Q599 601 599 609Q599 633 555 637Q537 637 537 648Q537 649 539 661Q542 675 545 679T558 683Q560 683 570 683T604 682T668 681Q737 681 755 683H762Q769 676 769 672Q769 655 760 640Q757 637 743 637Q730 636 719 635T698 630T682 623T670 615T660 608T652 599T645 592L452 282Q272 -9 266 -16Q263 -18 259 -21L241 -22H234Q216 -22 216 -15Q213 -9 177 305Q139 623 138 626Q133 637 76 637H59Q52 642 52 648Z"></path></g></g></g></svg></mjx-container> 进行加权聚合。</li><li><strong>多头机制（Multi-Head）：</strong> 将特征空间切分为多个子空间，允许模型并行捕捉多种复杂的语义关联（如同时关注语法指代与主谓关系），最后拼接融合。</li></ul><h4 id="逐位置前馈网络（Position-wise-FFN）"><a href="#逐位置前馈网络（Position-wise-FFN）" class="headerlink" title="逐位置前馈网络（Position-wise FFN）"></a>逐位置前馈网络（Position-wise FFN）</h4><p>在注意力层完成序列维度的空间交互后，FFN 负责在特征维度上进行非线性特征提取。</p><ul><li>通常采用“先升维再降维”的瓶颈结构配合 ReLU 激活函数。所有序列位置共享同一套 FFN 权重，保持了平移不变性并降低了参数量。</li></ul><h4 id="残差连接与层归一化（Add-Norm）"><a href="#残差连接与层归一化（Add-Norm）" class="headerlink" title="残差连接与层归一化（Add & Norm）"></a>残差连接与层归一化（Add &amp; Norm）</h4><ul><li><strong>残差连接（Add）：</strong> 允许特征与梯度绕过非线性层直接传递（<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="25.995ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 11490 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mi" transform="translate(763,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(1335,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mi" transform="translate(1696,0)"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g><g data-mml-node="mi" transform="translate(2199,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(2771,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(3409.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mi" transform="translate(4465.6,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path></g><g data-mml-node="mo" transform="translate(5259.8,0)"><path data-c="2B" d="M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z"></path></g><g data-mml-node="mi" transform="translate(6260,0)"><path data-c="1D446" d="M308 24Q367 24 416 76T466 197Q466 260 414 284Q308 311 278 321T236 341Q176 383 176 462Q176 523 208 573T273 648Q302 673 343 688T407 704H418H425Q521 704 564 640Q565 640 577 653T603 682T623 704Q624 704 627 704T632 705Q645 705 645 698T617 577T585 459T569 456Q549 456 549 465Q549 471 550 475Q550 478 551 494T553 520Q553 554 544 579T526 616T501 641Q465 662 419 662Q362 662 313 616T263 510Q263 480 278 458T319 427Q323 425 389 408T456 390Q490 379 522 342T554 242Q554 216 546 186Q541 164 528 137T492 78T426 18T332 -20Q320 -22 298 -22Q199 -22 144 33L134 44L106 13Q83 -14 78 -18T65 -22Q52 -22 52 -14Q52 -11 110 221Q112 227 130 227H143Q149 221 149 216Q149 214 148 207T144 186T142 153Q144 114 160 87T203 47T255 29T308 24Z"></path></g><g data-mml-node="mi" transform="translate(6905,0)"><path data-c="1D462" d="M21 287Q21 295 30 318T55 370T99 420T158 442Q204 442 227 417T250 358Q250 340 216 246T182 105Q182 62 196 45T238 27T291 44T328 78L339 95Q341 99 377 247Q407 367 413 387T427 416Q444 431 463 431Q480 431 488 421T496 402L420 84Q419 79 419 68Q419 43 426 35T447 26Q469 29 482 57T512 145Q514 153 532 153Q551 153 551 144Q550 139 549 130T540 98T523 55T498 17T462 -8Q454 -10 438 -10Q372 -10 347 46Q345 45 336 36T318 21T296 6T267 -6T233 -11Q189 -11 155 7Q103 38 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(7477,0)"><path data-c="1D44F" d="M73 647Q73 657 77 670T89 683Q90 683 161 688T234 694Q246 694 246 685T212 542Q204 508 195 472T180 418L176 399Q176 396 182 402Q231 442 283 442Q345 442 383 396T422 280Q422 169 343 79T173 -11Q123 -11 82 27T40 150V159Q40 180 48 217T97 414Q147 611 147 623T109 637Q104 637 101 637H96Q86 637 83 637T76 640T73 647ZM336 325V331Q336 405 275 405Q258 405 240 397T207 376T181 352T163 330L157 322L136 236Q114 150 114 114Q114 66 138 42Q154 26 178 26Q211 26 245 58Q270 81 285 114T318 219Q336 291 336 325Z"></path></g><g data-mml-node="mi" transform="translate(7906,0)"><path data-c="1D459" d="M117 59Q117 26 142 26Q179 26 205 131Q211 151 215 152Q217 153 225 153H229Q238 153 241 153T246 151T248 144Q247 138 245 128T234 90T214 43T183 6T137 -11Q101 -11 70 11T38 85Q38 97 39 102L104 360Q167 615 167 623Q167 626 166 628T162 632T157 634T149 635T141 636T132 637T122 637Q112 637 109 637T101 638T95 641T94 647Q94 649 96 661Q101 680 107 682T179 688Q194 689 213 690T243 693T254 694Q266 694 266 686Q266 675 193 386T118 83Q118 81 118 75T117 65V59Z"></path></g><g data-mml-node="mi" transform="translate(8204,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(8733,0)"><path data-c="1D466" d="M21 287Q21 301 36 335T84 406T158 442Q199 442 224 419T250 355Q248 336 247 334Q247 331 231 288T198 191T182 105Q182 62 196 45T238 27Q261 27 281 38T312 61T339 94Q339 95 344 114T358 173T377 247Q415 397 419 404Q432 431 462 431Q475 431 483 424T494 412T496 403Q496 390 447 193T391 -23Q363 -106 294 -155T156 -205Q111 -205 77 -183T43 -117Q43 -95 50 -80T69 -58T89 -48T106 -45Q150 -45 150 -87Q150 -107 138 -122T115 -142T102 -147L99 -148Q101 -153 118 -160T152 -167H160Q177 -167 186 -165Q219 -156 247 -127T290 -65T313 -9T321 21L315 17Q309 13 296 6T270 -6Q250 -11 231 -11Q185 -11 150 11T104 82Q103 89 103 113Q103 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(9223,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(9689,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mo" transform="translate(10140,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(10529,0)"><path data-c="1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path></g><g data-mml-node="mo" transform="translate(11101,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container>），解决极深网络中的梯度消失问题。</li><li><strong>层归一化（Norm）：</strong> 对单个样本的特征维度进行标准化处理，缓解内部协变量偏移，确保深层网络各层输入分布的稳定性。</li></ul><h4 id="位置编码（Positional-Encoding）"><a href="#位置编码（Positional-Encoding）" class="headerlink" title="位置编码（Positional Encoding）"></a>位置编码（Positional Encoding）</h4><p>自注意力机制本质上是集合运算，缺乏序列顺序感知能力。</p><ul><li>通过预设的三角函数（正弦与余弦）生成绝对位置向量，直接叠加在底层词嵌入上。这使得模型能够在并行计算时隐式推导出词元的相对距离和先后顺序。</li></ul><h3 id="Decoder-Only-架构与自回归生成"><a href="#Decoder-Only-架构与自回归生成" class="headerlink" title="Decoder-Only 架构与自回归生成"></a>Decoder-Only 架构与自回归生成</h3><p>在探索大规模预训练时，研究界发现完整的编解码架构存在结构冗余。以 GPT 系列为代表的 <strong>Decoder-Only 架构</strong> 凭借其极致的简洁性成为了当今模型的主流。</p><ul><li><strong>自回归生成（Autoregressive）：</strong> 将一切自然语言任务统一抽象为“预测下一个词”。模型持续将生成的词元回填至输入序列末尾，循环迭代。</li><li><strong>掩码自注意力（Masked Self-Attention）：</strong> 为了防止模型在预测第 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container> 个词时“偷看”未来的真实文本，系统在注意力分数矩阵的右上角施加负无穷掩码。经过 Softmax 后，未来信息的权重强制归零，严格保证了因果逻辑的单向性。</li><li><strong>工程优势：</strong> 训练目标极其纯粹，模型结构高度同质化，极易利用分布式集群进行参数规模的横向扩展，天然契合生成式任务。</li></ul><h3 id="大语言模型交互与工程化处理"><a href="#大语言模型交互与工程化处理" class="headerlink" title="大语言模型交互与工程化处理"></a>大语言模型交互与工程化处理</h3><p>大模型作为智能体的底层计算单元，开发者需要通过特定的工程手段（如提示工程和分词器管理）来对其输入输出进行精确编码与控制。</p><h4 id="模型采样参数的数学干预"><a href="#模型采样参数的数学干预" class="headerlink" title="模型采样参数的数学干预"></a>模型采样参数的数学干预</h4><p>大模型的输出本质上是基于 Logits 的概率分布。修改采样参数等同于干预模型的概率输出策略：</p><ul><li><strong>Temperature（温度 T）：</strong> 调节 Softmax 函数的平滑程度。低温度分布趋于尖锐，生成确定性强的事实文本；高温度分布趋于平滑，低概率词被激活，适合发散性创意写作。</li><li><strong>Top-k 截断：</strong> 强制将每一步的候选集限制在概率最高的 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="1.179ex" height="1.595ex" role="img" focusable="false" viewBox="0 -694 521 705"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g></g></g></svg></mjx-container> 个 Token 内，消除极端低概率词引起的乱码或崩坏风险。</li><li><strong>Top-p（核采样）：</strong> 按概率降序累加，当累加值达到阈值 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.439ex;" xmlns="http://www.w3.org/2000/svg" width="1.138ex" height="1.439ex" role="img" focusable="false" viewBox="0 -442 503 636"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D45D" d="M23 287Q24 290 25 295T30 317T40 348T55 381T75 411T101 433T134 442Q209 442 230 378L240 387Q302 442 358 442Q423 442 460 395T497 281Q497 173 421 82T249 -10Q227 -10 210 -4Q199 1 187 11T168 28L161 36Q160 35 139 -51T118 -138Q118 -144 126 -145T163 -148H188Q194 -155 194 -157T191 -175Q188 -187 185 -190T172 -194Q170 -194 161 -194T127 -193T65 -192Q-5 -192 -24 -194H-32Q-39 -187 -39 -183Q-37 -156 -26 -148H-6Q28 -147 33 -136Q36 -130 94 103T155 350Q156 355 156 364Q156 405 131 405Q109 405 94 377T71 316T59 280Q57 278 43 278H29Q23 284 23 287ZM178 102Q200 26 252 26Q282 26 310 49T356 107Q374 141 392 215T411 325V331Q411 405 350 405Q339 405 328 402T306 393T286 380T269 365T254 350T243 336T235 326L232 322Q232 321 229 308T218 264T204 212Q178 106 178 102Z"></path></g></g></g></svg></mjx-container> 时截断候选集。相比 Top-k，它能根据当前上下文的概率置信度动态调整候选池大小。</li></ul><h4 id="提示工程与认知引导"><a href="#提示工程与认知引导" class="headerlink" title="提示工程与认知引导"></a>提示工程与认知引导</h4><ul><li><strong>指令调优（Instruction Tuning）：</strong> 将预训练的“无条件续写模型”转化为“助理模型”的关键步骤，使其具备服从自然语言指令的能力。</li><li><strong>样本提示策略：</strong> 利用模型强大的上下文学习（In-context Learning）能力，涵盖零样本（Zero-shot）、单样本（One-shot）到少样本（Few-shot）。</li><li><strong>思维链（Chain-of-Thought, CoT）：</strong> 解决复杂系统推理问题的核心技巧。通过在提示中注入“请逐步思考”的显式指令，迫使模型将高维度的复杂逻辑坍缩为低维度的线性子步骤，大幅降低中间计算的逻辑跳跃与错误率。</li></ul><h4 id="文本分词（Tokenization）与-BPE-算法"><a href="#文本分词（Tokenization）与-BPE-算法" class="headerlink" title="文本分词（Tokenization）与 BPE 算法"></a>文本分词（Tokenization）与 BPE 算法</h4><p>计算机无法直接处理字符串，分词是将连续文本映射为离散张量的桥梁。</p><ul><li><strong>按词/按字符分词的困境：</strong> 单字符分词丧失词法语义且序列过长；单词分词面临词表爆炸及未登录词（OOV）困境。</li><li><strong>字节对编码（BPE）：</strong> 一种数据驱动的贪心压缩算法。初始化单字符词表，反复统计语料中相邻字符对的最高频组合并进行合并（如 <code>u</code> + <code>g</code> -&gt; <code>ug</code>）。既保留常见词的完整语义，又利用子词片段拼凑生僻词，彻底消灭了 OOV 问题。</li><li><strong>工程约束：</strong> 分词规则直接决定了模型的上下文窗口消耗速度与 API 计费成本，且是导致模型出现特定算术或代码排版“低级错误”的潜在因素。</li></ul><h3 id="模型生态、缩放法则与局限性"><a href="#模型生态、缩放法则与局限性" class="headerlink" title="模型生态、缩放法则与局限性"></a>模型生态、缩放法则与局限性</h3><p>在为智能体选择“大脑”时，必须深刻理解当前模型体系的发展规律及其物理与逻辑层面的局限。</p><h4 id="模型选型多维评估"><a href="#模型选型多维评估" class="headerlink" title="模型选型多维评估"></a>模型选型多维评估</h4><ul><li><strong>闭源模型（GPT, Gemini, Claude）：</strong> 提供顶级的通用推理性能和超长上下文，开箱即用，适合高复杂度智能体的云端调用。</li><li><strong>开源模型（Llama, Mistral, Qwen）：</strong> 提供最高的数据隐私隔离、权重微调（Fine-tuning）自由度以及端侧部署能力。</li><li><strong>评估指标：</strong> 逻辑推理性能、推理延迟（TTFT）、上下文窗口容量、综合成本、生态工具链兼容性。</li></ul><h4 id="缩放法则（Scaling-Laws）与能力涌现"><a href="#缩放法则（Scaling-Laws）与能力涌现" class="headerlink" title="缩放法则（Scaling Laws）与能力涌现"></a>缩放法则（Scaling Laws）与能力涌现</h4><ul><li><strong>幂律关系：</strong> 研究证实，模型的交叉熵损失与模型参数规模、训练数据量、计算 FLOPs 之间存在平滑的幂律递减关系。</li><li><strong>Chinchilla 定律修正：</strong> 反驳了早期单纯堆砌参数的做法，指出在给定计算预算下，参数量与训练 Token 数应保持同比例的最优配比增长。</li><li><strong>能力涌现（Emergent Abilities）：</strong> 复杂技能（如隐式代码解释、多步推理）并非随参数增加线性增长，而是在跨越某个规模阈值后突然解锁。这要求高级 MAS 必须依托足够规模的基座模型。</li></ul><h4 id="模型幻觉（Hallucination）的诱因与治理"><a href="#模型幻觉（Hallucination）的诱因与治理" class="headerlink" title="模型幻觉（Hallucination）的诱因与治理"></a>模型幻觉（Hallucination）的诱因与治理</h4><p>模型在过度自信下生成的违背事实或逻辑的虚假内容，是智能体落地的最大阻碍。</p><ul><li><strong>分类：</strong> 事实性幻觉（违背客观现实）、忠实性幻觉（违背给定上下文）、内在幻觉（逻辑自相矛盾）。</li><li><strong>根源剖析：</strong> 训练语料的脏数据污染、知识时效性滞后，以及自回归生成本质上是“概率序列接龙”而非严谨的“关系数据库查询”。</li><li><strong>治理策略：</strong><ul><li><strong>检索增强生成（RAG）：</strong> 利用外部向量数据库提供实时客观上下文，强迫模型基于检索事实生成。</li><li><strong>工程化外挂：</strong> 赋予智能体调用外部工具（计算器、搜索引擎）的能力，将需要确定性的计算交还给符号系统。</li></ul></li></ul><h3 id="课后习题解析-2"><a href="#课后习题解析-2" class="headerlink" title="课后习题解析"></a>课后习题解析</h3><h4 id="统计概率计算与架构跃迁"><a href="#统计概率计算与架构跃迁" class="headerlink" title="统计概率计算与架构跃迁"></a>统计概率计算与架构跃迁</h4><p><strong>【原问题】</strong><br>自然语言处理中，语言模型经历了从统计方法到神经网络的深刻演进。在统计语言模型时代，N-gram 模型通过马尔可夫假设简化了概率计算。<br>假设存在一个微型语料库仅包含两句话：<code>datawhale agent learns</code> 和 <code>datawhale agent works</code>。</p><ol><li>请根据该语料库，手动计算短语 <code>agent works</code> 在 Bigram 模型下的联合概率。</li><li>解释马尔可夫假设的理论含义，并指出 N-gram 模型因此产生的根本性局限。</li><li>从架构机制出发，RNN/LSTM 和 Transformer 分别是如何克服 N-gram 局限的？二者在处理机制上有何核心优劣差异？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>Bigram 概率计算：</strong><br>根据 Bigram 的链式法则近似：<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="45.824ex" height="2.262ex" role="img" focusable="false" viewBox="0 -750 20254 1000"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(751,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1140,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1669,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(2146,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(2612,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(3212,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mtext" transform="translate(3573,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(3823,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(4539,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(5024,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(5475,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g><g data-mml-node="mi" transform="translate(5996,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mo" transform="translate(6465,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(7131.8,0)"><path data-c="2248" d="M55 319Q55 360 72 393T114 444T163 472T205 482Q207 482 213 482T223 483Q262 483 296 468T393 413L443 381Q502 346 553 346Q609 346 649 375T694 454Q694 465 698 474T708 483Q722 483 722 452Q722 386 675 338T555 289Q514 289 468 310T388 357T308 404T224 426Q164 426 125 393T83 318Q81 289 69 289Q55 289 55 319ZM55 85Q55 126 72 159T114 210T163 238T205 248Q207 248 213 248T223 249Q262 249 296 234T393 179L443 147Q502 112 553 112Q609 112 649 141T694 220Q694 249 708 249T722 217Q722 153 675 104T555 55Q514 55 468 76T388 123T308 170T224 192Q164 192 125 159T83 84Q80 55 69 55Q55 55 55 85Z"></path></g><g data-mml-node="mi" transform="translate(8187.6,0)"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(8938.6,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(9327.6,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(9856.6,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(10333.6,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(10799.6,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(11399.6,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(11760.6,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(12371.8,0)"><path data-c="D7" d="M630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29Z"></path></g><g data-mml-node="mi" transform="translate(13372,0)"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(14123,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(14512,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(15228,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(15713,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(16164,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g><g data-mml-node="mi" transform="translate(16685,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mo" transform="translate(17154,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z"></path></g><g data-mml-node="mi" transform="translate(17432,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(17961,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(18438,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(18904,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(19504,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(19865,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container>。<ul><li>语料库总词数 = 6。<code>agent</code> 出现 2 次，故 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.816ex;" xmlns="http://www.w3.org/2000/svg" width="18.589ex" height="2.773ex" role="img" focusable="false" viewBox="0 -864.9 8216.2 1225.5"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(751,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1140,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1669,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(2146,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(2612,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(3212,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(3573,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(4239.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(5295.6,0)"><g data-mml-node="mn" transform="translate(220,394) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g><g data-mml-node="mn" transform="translate(220,-345) scale(0.707)"><path data-c="36" d="M42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397Z"></path></g><rect width="553.6" height="60" x="120" y="220"></rect></g><g data-mml-node="mo" transform="translate(6366.9,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(7422.7,0)"><g data-mml-node="mn" transform="translate(220,394) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g><g data-mml-node="mn" transform="translate(220,-345) scale(0.707)"><path data-c="33" d="M127 463Q100 463 85 480T69 524Q69 579 117 622T233 665Q268 665 277 664Q351 652 390 611T430 522Q430 470 396 421T302 350L299 348Q299 347 308 345T337 336T375 315Q457 262 457 175Q457 96 395 37T238 -22Q158 -22 100 21T42 130Q42 158 60 175T105 193Q133 193 151 175T169 130Q169 119 166 110T159 94T148 82T136 74T126 70T118 67L114 66Q165 21 238 21Q293 21 321 74Q338 107 338 175V195Q338 290 274 322Q259 328 213 329L171 330L168 332Q166 335 166 348Q166 366 174 366Q202 366 232 371Q266 376 294 413T322 525V533Q322 590 287 612Q265 626 240 626Q208 626 181 615T143 592T132 580H135Q138 579 143 578T153 573T165 566T175 555T183 540T186 520Q186 498 172 481T127 463Z"></path></g><rect width="553.6" height="60" x="120" y="220"></rect></g></g></g></svg></mjx-container>。</li><li>在 <code>agent</code> 出现的 2 次中，其后接 <code>works</code> 的次数为 1 次，故最大似然估计下 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.781ex;" xmlns="http://www.w3.org/2000/svg" width="20.383ex" height="2.737ex" role="img" focusable="false" viewBox="0 -864.9 9009.1 1209.9"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(751,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1140,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(1856,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(2341,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(2792,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g><g data-mml-node="mi" transform="translate(3313,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mo" transform="translate(3782,0) translate(0 -0.5)"><path data-c="7C" d="M139 -249H137Q125 -249 119 -235V251L120 737Q130 750 139 750Q152 750 159 735V-235Q151 -249 141 -249H139Z"></path></g><g data-mml-node="mi" transform="translate(4060,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(4589,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(5066,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(5532,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(6132,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(6493,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(7159.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(8215.6,0)"><g data-mml-node="mn" transform="translate(220,394) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g><g data-mml-node="mn" transform="translate(220,-345) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g><rect width="553.6" height="60" x="120" y="220"></rect></g></g></g></svg></mjx-container>。</li><li>联合概率：<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.816ex;" xmlns="http://www.w3.org/2000/svg" width="37.864ex" height="2.773ex" role="img" focusable="false" viewBox="0 -864.9 16735.8 1225.5"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path></g><g data-mml-node="mo" transform="translate(751,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="mi" transform="translate(1140,0)"><path data-c="1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path></g><g data-mml-node="mi" transform="translate(1669,0)"><path data-c="1D454" d="M311 43Q296 30 267 15T206 0Q143 0 105 45T66 160Q66 265 143 353T314 442Q361 442 401 394L404 398Q406 401 409 404T418 412T431 419T447 422Q461 422 470 413T480 394Q480 379 423 152T363 -80Q345 -134 286 -169T151 -205Q10 -205 10 -137Q10 -111 28 -91T74 -71Q89 -71 102 -80T116 -111Q116 -121 114 -130T107 -144T99 -154T92 -162L90 -164H91Q101 -167 151 -167Q189 -167 211 -155Q234 -144 254 -122T282 -75Q288 -56 298 -13Q311 35 311 43ZM384 328L380 339Q377 350 375 354T369 368T359 382T346 393T328 402T306 405Q262 405 221 352Q191 313 171 233T151 117Q151 38 213 38Q269 38 323 108L331 118L384 328Z"></path></g><g data-mml-node="mi" transform="translate(2146,0)"><path data-c="1D452" d="M39 168Q39 225 58 272T107 350T174 402T244 433T307 442H310Q355 442 388 420T421 355Q421 265 310 237Q261 224 176 223Q139 223 138 221Q138 219 132 186T125 128Q125 81 146 54T209 26T302 45T394 111Q403 121 406 121Q410 121 419 112T429 98T420 82T390 55T344 24T281 -1T205 -11Q126 -11 83 42T39 168ZM373 353Q367 405 305 405Q272 405 244 391T199 357T170 316T154 280T149 261Q149 260 169 260Q282 260 327 284T373 353Z"></path></g><g data-mml-node="mi" transform="translate(2612,0)"><path data-c="1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(3212,0)"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mtext" transform="translate(3573,0)"><path data-c="A0" d=""></path></g><g data-mml-node="mi" transform="translate(3823,0)"><path data-c="1D464" d="M580 385Q580 406 599 424T641 443Q659 443 674 425T690 368Q690 339 671 253Q656 197 644 161T609 80T554 12T482 -11Q438 -11 404 5T355 48Q354 47 352 44Q311 -11 252 -11Q226 -11 202 -5T155 14T118 53T104 116Q104 170 138 262T173 379Q173 380 173 381Q173 390 173 393T169 400T158 404H154Q131 404 112 385T82 344T65 302T57 280Q55 278 41 278H27Q21 284 21 287Q21 293 29 315T52 366T96 418T161 441Q204 441 227 416T250 358Q250 340 217 250T184 111Q184 65 205 46T258 26Q301 26 334 87L339 96V119Q339 122 339 128T340 136T341 143T342 152T345 165T348 182T354 206T362 238T373 281Q402 395 406 404Q419 431 449 431Q468 431 475 421T483 402Q483 389 454 274T422 142Q420 131 420 107V100Q420 85 423 71T442 42T487 26Q558 26 600 148Q609 171 620 213T632 273Q632 306 619 325T593 357T580 385Z"></path></g><g data-mml-node="mi" transform="translate(4539,0)"><path data-c="1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path></g><g data-mml-node="mi" transform="translate(5024,0)"><path data-c="1D45F" d="M21 287Q22 290 23 295T28 317T38 348T53 381T73 411T99 433T132 442Q161 442 183 430T214 408T225 388Q227 382 228 382T236 389Q284 441 347 441H350Q398 441 422 400Q430 381 430 363Q430 333 417 315T391 292T366 288Q346 288 334 299T322 328Q322 376 378 392Q356 405 342 405Q286 405 239 331Q229 315 224 298T190 165Q156 25 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 114 189T154 366Q154 405 128 405Q107 405 92 377T68 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></g><g data-mml-node="mi" transform="translate(5475,0)"><path data-c="1D458" d="M121 647Q121 657 125 670T137 683Q138 683 209 688T282 694Q294 694 294 686Q294 679 244 477Q194 279 194 272Q213 282 223 291Q247 309 292 354T362 415Q402 442 438 442Q468 442 485 423T503 369Q503 344 496 327T477 302T456 291T438 288Q418 288 406 299T394 328Q394 353 410 369T442 390L458 393Q446 405 434 405H430Q398 402 367 380T294 316T228 255Q230 254 243 252T267 246T293 238T320 224T342 206T359 180T365 147Q365 130 360 106T354 66Q354 26 381 26Q429 26 459 145Q461 153 479 153H483Q499 153 499 144Q499 139 496 130Q455 -11 378 -11Q333 -11 305 15T277 90Q277 108 280 121T283 145Q283 167 269 183T234 206T200 217T182 220H180Q168 178 159 139T145 81T136 44T129 20T122 7T111 -2Q98 -11 83 -11Q66 -11 57 -1T48 16Q48 26 85 176T158 471L195 616Q196 629 188 632T149 637H144Q134 637 131 637T124 640T121 647Z"></path></g><g data-mml-node="mi" transform="translate(5996,0)"><path data-c="1D460" d="M131 289Q131 321 147 354T203 415T300 442Q362 442 390 415T419 355Q419 323 402 308T364 292Q351 292 340 300T328 326Q328 342 337 354T354 372T367 378Q368 378 368 379Q368 382 361 388T336 399T297 405Q249 405 227 379T204 326Q204 301 223 291T278 274T330 259Q396 230 396 163Q396 135 385 107T352 51T289 7T195 -10Q118 -10 86 19T53 87Q53 126 74 143T118 160Q133 160 146 151T160 120Q160 94 142 76T111 58Q109 57 108 57T107 55Q108 52 115 47T146 34T201 27Q237 27 263 38T301 66T318 97T323 122Q323 150 302 164T254 181T195 196T148 231Q131 256 131 289Z"></path></g><g data-mml-node="mo" transform="translate(6465,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g><g data-mml-node="mo" transform="translate(7131.8,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(8187.6,0)"><g data-mml-node="mn" transform="translate(220,394) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g><g data-mml-node="mn" transform="translate(220,-345) scale(0.707)"><path data-c="33" d="M127 463Q100 463 85 480T69 524Q69 579 117 622T233 665Q268 665 277 664Q351 652 390 611T430 522Q430 470 396 421T302 350L299 348Q299 347 308 345T337 336T375 315Q457 262 457 175Q457 96 395 37T238 -22Q158 -22 100 21T42 130Q42 158 60 175T105 193Q133 193 151 175T169 130Q169 119 166 110T159 94T148 82T136 74T126 70T118 67L114 66Q165 21 238 21Q293 21 321 74Q338 107 338 175V195Q338 290 274 322Q259 328 213 329L171 330L168 332Q166 335 166 348Q166 366 174 366Q202 366 232 371Q266 376 294 413T322 525V533Q322 590 287 612Q265 626 240 626Q208 626 181 615T143 592T132 580H135Q138 579 143 578T153 573T165 566T175 555T183 540T186 520Q186 498 172 481T127 463Z"></path></g><rect width="553.6" height="60" x="120" y="220"></rect></g><g data-mml-node="mo" transform="translate(9203.3,0)"><path data-c="D7" d="M630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29Z"></path></g><g data-mml-node="mfrac" transform="translate(10203.6,0)"><g data-mml-node="mn" transform="translate(220,394) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g><g data-mml-node="mn" transform="translate(220,-345) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g><rect width="553.6" height="60" x="120" y="220"></rect></g><g data-mml-node="mo" transform="translate(11274.9,0)"><path data-c="3D" d="M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z"></path></g><g data-mml-node="mfrac" transform="translate(12330.7,0)"><g data-mml-node="mn" transform="translate(220,394) scale(0.707)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g><g data-mml-node="mn" transform="translate(220,-345) scale(0.707)"><path data-c="36" d="M42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397Z"></path></g><rect width="553.6" height="60" x="120" y="220"></rect></g><g data-mml-node="mo" transform="translate(13402,0)"><path data-c="2248" d="M55 319Q55 360 72 393T114 444T163 472T205 482Q207 482 213 482T223 483Q262 483 296 468T393 413L443 381Q502 346 553 346Q609 346 649 375T694 454Q694 465 698 474T708 483Q722 483 722 452Q722 386 675 338T555 289Q514 289 468 310T388 357T308 404T224 426Q164 426 125 393T83 318Q81 289 69 289Q55 289 55 319ZM55 85Q55 126 72 159T114 210T163 238T205 248Q207 248 213 248T223 249Q262 249 296 234T393 179L443 147Q502 112 553 112Q609 112 649 141T694 220Q694 249 708 249T722 217Q722 153 675 104T555 55Q514 55 468 76T388 123T308 170T224 192Q164 192 125 159T83 84Q80 55 69 55Q55 55 55 85Z"></path></g><g data-mml-node="mn" transform="translate(14457.8,0)"><path data-c="30" d="M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z"></path><path data-c="2E" d="M78 60Q78 84 95 102T138 120Q162 120 180 104T199 61Q199 36 182 18T139 0T96 17T78 60Z" transform="translate(500,0)"></path><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z" transform="translate(778,0)"></path><path data-c="36" d="M42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397Z" transform="translate(1278,0)"></path><path data-c="37" d="M55 458Q56 460 72 567L88 674Q88 676 108 676H128V672Q128 662 143 655T195 646T364 644H485V605L417 512Q408 500 387 472T360 435T339 403T319 367T305 330T292 284T284 230T278 162T275 80Q275 66 275 52T274 28V19Q270 2 255 -10T221 -22Q210 -22 200 -19T179 0T168 40Q168 198 265 368Q285 400 349 489L395 552H302Q128 552 119 546Q113 543 108 522T98 479L95 458V455H55V458Z" transform="translate(1778,0)"></path></g></g></g></svg></mjx-container>。</li></ul></li><li><strong>马尔可夫假设与局限：</strong><ul><li><strong>理论含义：</strong> 将全局序列概率降维为局部条件概率，假设当前状态（词）仅依赖于前 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="5.906ex" height="1.731ex" role="img" focusable="false" viewBox="0 -683 2610.4 765"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D441" d="M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z"></path></g><g data-mml-node="mo" transform="translate(1110.2,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(2110.4,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g></svg></mjx-container> 个历史状态，使得高维概率计算在工程上可行。</li><li><strong>根本局限：</strong> 彻底割裂了长程依赖（无法处理跨越数十个词的语法指代）；采用离散符号统计，导致极高的“数据稀疏性”（未出现的组合概率直接为零），毫无语义泛化能力。</li></ul></li><li><strong>架构机制的克服与对比：</strong><ul><li><strong>克服局限：</strong> RNN 引入了连续向量空间（词嵌入解决泛化问题）和“隐藏状态”（打破固定 N 窗口，隐式传递长程历史）。Transformer 进一步通过“全局自注意力”让任意两个词直接发生信息交互，彻底消除了距离对特征提取的削弱。</li><li><strong>核心优劣差异：</strong> RNN 依赖时序递归（必须等 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.186ex;" xmlns="http://www.w3.org/2000/svg" width="4.714ex" height="1.692ex" role="img" focusable="false" viewBox="0 -666 2083.4 748"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g><g data-mml-node="mo" transform="translate(583.2,0)"><path data-c="2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path></g><g data-mml-node="mn" transform="translate(1583.4,0)"><path data-c="31" d="M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z"></path></g></g></g></svg></mjx-container> 步算完才能算 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.025ex;" xmlns="http://www.w3.org/2000/svg" width="0.817ex" height="1.441ex" role="img" focusable="false" viewBox="0 -626 361 637"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path></g></g></g></svg></mjx-container> 步），难以并行训练，且存在梯度衰减；Transformer 采用矩阵一次性并发计算，极大地提升了训练效率，但计算复杂度随序列长度呈平方级增长（<mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.566ex;" xmlns="http://www.w3.org/2000/svg" width="6.015ex" height="2.452ex" role="img" focusable="false" viewBox="0 -833.9 2658.6 1083.9"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D442" d="M740 435Q740 320 676 213T511 42T304 -22Q207 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435ZM637 476Q637 565 591 615T476 665Q396 665 322 605Q242 542 200 428T157 216Q157 126 200 73T314 19Q404 19 485 98T608 313Q637 408 637 476Z"></path></g><g data-mml-node="mo" transform="translate(763,0)"><path data-c="28" d="M94 250Q94 319 104 381T127 488T164 576T202 643T244 695T277 729T302 750H315H319Q333 750 333 741Q333 738 316 720T275 667T226 581T184 443T167 250T184 58T225 -81T274 -167T316 -220T333 -241Q333 -250 318 -250H315H302L274 -226Q180 -141 137 -14T94 250Z"></path></g><g data-mml-node="msup" transform="translate(1152,0)"><g data-mml-node="mi"><path data-c="1D43F" d="M228 637Q194 637 192 641Q191 643 191 649Q191 673 202 682Q204 683 217 683Q271 680 344 680Q485 680 506 683H518Q524 677 524 674T522 656Q517 641 513 637H475Q406 636 394 628Q387 624 380 600T313 336Q297 271 279 198T252 88L243 52Q243 48 252 48T311 46H328Q360 46 379 47T428 54T478 72T522 106T564 161Q580 191 594 228T611 270Q616 273 628 273H641Q647 264 647 262T627 203T583 83T557 9Q555 4 553 3T537 0T494 -1Q483 -1 418 -1T294 0H116Q32 0 32 10Q32 17 34 24Q39 43 44 45Q48 46 59 46H65Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Q285 635 228 637Z"></path></g><g data-mml-node="mn" transform="translate(714,363) scale(0.707)"><path data-c="32" d="M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z"></path></g></g><g data-mml-node="mo" transform="translate(2269.6,0)"><path data-c="29" d="M60 749L64 750Q69 750 74 750H86L114 726Q208 641 251 514T294 250Q294 182 284 119T261 12T224 -76T186 -143T145 -194T113 -227T90 -246Q87 -249 86 -250H74Q66 -250 63 -250T58 -247T55 -238Q56 -237 66 -225Q221 -64 221 250T66 725Q56 737 55 738Q55 746 60 749Z"></path></g></g></g></svg></mjx-container>）。</li></ul></li></ol><h4 id="Transformer-架构与-Decoder-Only-演进"><a href="#Transformer-架构与-Decoder-Only-演进" class="headerlink" title="Transformer 架构与 Decoder-Only 演进"></a>Transformer 架构与 Decoder-Only 演进</h4><p><strong>【原问题】</strong><br>Transformer 是支撑现代大语言模型的基础架构。在其内部机制与后续演进中存在诸多精妙设计。</p><ol><li>剥离掉复杂的矩阵公式，请阐述自注意力机制（Self-Attention）的核心认知思想是什么？</li><li>为什么 Transformer 能够颠覆 RNN 实现大规模并行处理序列？在彻底抛弃循环结构后，位置编码（Positional Encoding）为何成为必不可少的组件？</li><li>既然 Transformer 最初是包含 Encoder 和 Decoder 的完整架构，为什么当前主流的生成式大语言模型（如 GPT、Llama 系列）却集体转向了 Decoder-Only 架构？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>自注意力的核心思想：</strong><br>本质上是一种“基于内容的动态信息检索与聚合”机制。对于句子中的每个词，它主动发出查询（Query），去评估序列中所有其他词的特征标签（Key）。根据相关性得分，为当前词动态地吸纳相关度高的内容（Value）。它实现了词义的“语境化（Contextualized）”重构。</li><li><strong>并行计算与位置编码的必然性：</strong><ul><li><strong>并行原理：</strong> RNN 的计算图是线性依赖的。Transformer 的自注意力计算依赖的是 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: -0.439ex;" xmlns="http://www.w3.org/2000/svg" width="1.79ex" height="2.032ex" role="img" focusable="false" viewBox="0 -704 791 898"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mi"><path data-c="1D444" d="M399 -80Q399 -47 400 -30T402 -11V-7L387 -11Q341 -22 303 -22Q208 -22 138 35T51 201Q50 209 50 244Q50 346 98 438T227 601Q351 704 476 704Q514 704 524 703Q621 689 680 617T740 435Q740 255 592 107Q529 47 461 16L444 8V3Q444 2 449 -24T470 -66T516 -82Q551 -82 583 -60T625 -3Q631 11 638 11Q647 11 649 2Q649 -6 639 -34T611 -100T557 -165T481 -194Q399 -194 399 -87V-80ZM636 468Q636 523 621 564T580 625T530 655T477 665Q429 665 379 640Q277 591 215 464T153 216Q153 110 207 59Q231 38 236 38V46Q236 86 269 120T347 155Q372 155 390 144T417 114T429 82T435 55L448 64Q512 108 557 185T619 334T636 468ZM314 18Q362 18 404 39L403 49Q399 104 366 115Q354 117 347 117Q344 117 341 117T337 118Q317 118 296 98T274 52Q274 18 314 18Z"></path></g></g></g></svg></mjx-container> 与 <mjx-container class="MathJax" jax="SVG"><svg style="vertical-align: 0;" xmlns="http://www.w3.org/2000/svg" width="3.443ex" height="1.904ex" role="img" focusable="false" viewBox="0 -841.7 1521.8 841.7"><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="msup"><g data-mml-node="mi"><path data-c="1D43E" d="M285 628Q285 635 228 637Q205 637 198 638T191 647Q191 649 193 661Q199 681 203 682Q205 683 214 683H219Q260 681 355 681Q389 681 418 681T463 682T483 682Q500 682 500 674Q500 669 497 660Q496 658 496 654T495 648T493 644T490 641T486 639T479 638T470 637T456 637Q416 636 405 634T387 623L306 305Q307 305 490 449T678 597Q692 611 692 620Q692 635 667 637Q651 637 651 648Q651 650 654 662T659 677Q662 682 676 682Q680 682 711 681T791 680Q814 680 839 681T869 682Q889 682 889 672Q889 650 881 642Q878 637 862 637Q787 632 726 586Q710 576 656 534T556 455L509 418L518 396Q527 374 546 329T581 244Q656 67 661 61Q663 59 666 57Q680 47 717 46H738Q744 38 744 37T741 19Q737 6 731 0H720Q680 3 625 3Q503 3 488 0H478Q472 6 472 9T474 27Q478 40 480 43T491 46H494Q544 46 544 71Q544 75 517 141T485 216L427 354L359 301L291 248L268 155Q245 63 245 58Q245 51 253 49T303 46H334Q340 37 340 35Q340 19 333 5Q328 0 317 0Q314 0 280 1T180 2Q118 2 85 2T49 1Q31 1 31 11Q31 13 34 25Q38 41 42 43T65 46Q92 46 125 49Q139 52 144 61Q147 65 216 339T285 628Z"></path></g><g data-mml-node="mi" transform="translate(974,363) scale(0.707)"><path data-c="1D447" d="M40 437Q21 437 21 445Q21 450 37 501T71 602L88 651Q93 669 101 677H569H659Q691 677 697 676T704 667Q704 661 687 553T668 444Q668 437 649 437Q640 437 637 437T631 442L629 445Q629 451 635 490T641 551Q641 586 628 604T573 629Q568 630 515 631Q469 631 457 630T439 622Q438 621 368 343T298 60Q298 48 386 46Q418 46 427 45T436 36Q436 31 433 22Q429 4 424 1L422 0Q419 0 415 0Q410 0 363 1T228 2Q99 2 64 0H49Q43 6 43 9T45 27Q49 40 55 46H83H94Q174 46 189 55Q190 56 191 56Q196 59 201 76T241 233Q258 301 269 344Q339 619 339 625Q339 630 310 630H279Q212 630 191 624Q146 614 121 583T67 467Q60 445 57 441T43 437H40Z"></path></g></g></g></g></svg></mjx-container> 的全矩阵乘法，由于不需要等待历史状态，输入序列的所有特征提取可以被丢进 GPU 的 Tensor Core 中一次性并行计算完毕。</li><li><strong>位置编码的必要性：</strong> 矩阵乘法本身具有“置换不变性”。如果没有位置编码，打乱句子的语序，模型计算出的注意力权重分配毫无变化，沦为纯粹的“词袋模型”。通过注入位置向量（三角函数或旋转位置编码），强制让模型在向量空间中感知到词与词的相对物理距离。</li></ul></li><li><strong>转向 Decoder-Only 的理论动机：</strong><ul><li><strong>训练范式的统一：</strong> Encoder-Decoder 适用于输入输出不对等的任务（翻译、摘要）。但 Decoder-Only 将一切自然语言任务统一抽象为了单一的自回归目标：“预测下一个 Token”。</li><li><strong>规模化（Scaling）的工程便利：</strong> Decoder-Only 移除了负责交叉注意力的组件，网络结构高度单一且同质化，极大地降低了超大参数规模下的流水线并行和显存分配难度，完美契合缩放法则（Scaling Laws）的暴力美学。</li></ul></li></ol><h4 id="分词算法（Tokenization）的工程约束"><a href="#分词算法（Tokenization）的工程约束" class="headerlink" title="分词算法（Tokenization）的工程约束"></a>分词算法（Tokenization）的工程约束</h4><p><strong>【原问题】</strong><br>在将文本输入大语言模型之前，必须经过文本子词分词算法（如 BPE）的切分。</p><ol><li>为什么在现代模型设计中，不能简单粗暴地以英文字母（字符级）或按空格划分的单词（词级）作为底层输入单元？</li><li>BPE（Byte Pair Encoding）算法是如何在字符级与词级之间取得平衡，并解决上述痛点的？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>双重绝境：</strong><ul><li><strong>按字符分词：</strong> 单个字母毫无独立语义（如字母 <code>a</code> 必须组合才能表意），这导致模型的输入序列极度冗长，迅速耗尽昂贵的 Transformer 上下文窗口限制。</li><li><strong>按单词分词：</strong> 人类语言存在无限的词法变体（时态、复数形式）。这会导致模型词表（Vocabulary）呈指数级爆炸，使得最后一层分类器参数过大；且一旦遇到未在训练集中出现的专业词汇，模型只能输出无意义的 <code>[UNK]</code> 标记，即未登录词（OOV）问题。</li></ul></li><li><strong>BPE 算法的平衡策略：</strong><br>BPE 基于统计学贪心压缩。它从单字符词表开始，不断统计语料中最高频相邻的字符组合并进行合并压缩。<ul><li><strong>平衡点：</strong> 高频的、具有独立语义的基础词汇（如 <code>the</code>, <code>agent</code>）会在合并中成为一个完整的 Token，保留词汇级语义；而低频词或罕见新词（如 <code>Tokenization</code>）则被保留为多个高频的词根、词缀片段（如 <code>Token</code> + <code>ization</code>）。</li><li><strong>解决痛点：</strong> 彻底消灭了 OOV 问题（任何生僻词都能回退到单字符拼凑）；同时将词表大小严格限制在工程可控范围（如 10 万以内），兼顾了序列长度压缩率与底层语义丰富度。</li></ul></li></ol><h4 id="提示工程与企业级智能体选型"><a href="#提示工程与企业级智能体选型" class="headerlink" title="提示工程与企业级智能体选型"></a>提示工程与企业级智能体选型</h4><p><strong>【原问题】</strong><br>假设你需要为一家跨国企业构建一个核心的“退换货政策解答与流程引导客服智能体”。</p><ol><li>按照本章对开源与闭源大语言模型的阐述，从性能、成本、可控性、隐私等维度进行对比。针对该企业级客服场景，你会倾向于选择哪种模型部署路线？</li><li>在模型推理时，如果你希望模型在解答企业制度时严谨死板，而在安抚客户情绪时自然流畅，你应如何干预 Temperature 等采样参数？</li><li>在具体的任务执行中，如果模型在处理复杂的退货退款计算逻辑时频繁出错，你将如何利用 Few-shot 与 Chain-of-Thought（CoT）策略重构你的 Prompt 以提升其准确率？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>企业场景选型决策：</strong><ul><li><strong>对比：</strong> 闭源 API（如 GPT-4）具备最强性能和极低前期接入成本，但按 Token 计费长期成本高，且存在将客户聊天数据传至外部服务器的严重隐私合规风险；开源模型（如 Qwen-72B/Llama-3）前期需要高昂的算力集群采购成本，但单次调用边际成本趋零，数据绝对物理隔离，且支持深度的垂直知识微调（SFT）。</li><li><strong>决策倾向：</strong> 针对企业客服场景，<strong>优先选择开源模型本地私有化部署</strong>。因为退换货政策涉及企业内部规定与客户订单隐私，数据安全是硬性红线。且客服场景逻辑相对固定，通过注入企业知识库（RAG）配合开源模型足以达到业务标准。</li></ul></li><li><strong>采样参数的分层控制：</strong><ul><li>针对“政策解答”节点，将 <strong>Temperature 降至趋近于 0，Top-p 设为 0.1</strong>。此时模型采取贪心解码策略，输出确定性极高，杜绝在制度问题上“自由发挥”产生幻觉。</li><li>针对“情绪安抚”节点，将 <strong>Temperature 调高至 0.6 - 0.7</strong>。引入概率平滑，使得模型生成的问候语、安抚话术更具多样性和“人情味”。</li></ul></li><li><strong>Prompt 策略重构（Few-shot + CoT）：</strong><ul><li>仅仅通过 Zero-shot 抛出问题会导致计算跳步。需要在 Prompt 中前置注入 2-3 个标准的历史退款计算案例（Few-shot）。</li><li>在这些案例中，不仅要给出最终退款金额，必须<strong>显式地写出计算过程（CoT）</strong>。例如加入引导语：“在得出结果前，请逐步思考：第一步，判断商品是否在7天内；第二步，计算商品原价减去已使用的优惠券金额；第三步，核对是否需要扣除运费”。通过强制模型输出中间逻辑链，降低复杂计算的误差。</li></ul></li></ol><h4 id="治理模型幻觉：学术辅助智能体设计"><a href="#治理模型幻觉：学术辅助智能体设计" class="headerlink" title="治理模型幻觉：学术辅助智能体设计"></a>治理模型幻觉：学术辅助智能体设计</h4><p><strong>【原问题】</strong><br>模型幻觉（Hallucination）是大语言模型落地的最大阻碍。假设你需要设计一个“学术论文辅助阅读智能体”，帮助科研人员快速总结数十页的论文并对比多篇文献的观点。由于科研场景极度严谨，不容忍任何事实偏差。</p><ol><li>针对学术论文普遍较长，极易突破模型上下文窗口限制的问题，在系统架构上你将采取何种工程手段解决？</li><li>除了本章提到的检索增强生成（RAG），结合前沿的大模型研究，还有哪些机制可以被引入到该智能体中以进一步压制幻觉？请说明其工作原理。</li><li>为了确保智能体生成的摘要和对比严格忠于原文，在系统的工作流设计中，你将如何部署审查约束机制？</li></ol><p><strong>【解析】</strong></p><ol><li><strong>突破超长上下文约束的架构：</strong><ul><li>传统的直接输入会导致 Token 溢出。可采用 <strong>基于向量检索的 RAG 机制</strong>：将长论文切割为按段落划分的文本块（Chunks），向量化后存入知识库。当用户询问特定细节时，仅检索 Top-K 相关的文本块喂给模型。</li><li>针对需要通读全文的“全局总结”任务，采用 <strong>Map-Reduce 范式</strong>：让模型并行对论文的各个章节生成局部摘要（Map），最后将局部摘要汇总给模型进行二次提炼（Reduce）。</li></ul></li><li><strong>前沿缓解幻觉的对抗机制：</strong><ul><li><strong>多智能体交叉验证（Multi-Agent Debate/Verification）：</strong> 引入独立的“审查者 Agent（Reviewer）”。当生成者 Agent 输出文献对比后，强制流转给审查者。审查者被赋予“挑刺”的 System Prompt，专门比对生成文本与原文检索块，若发现过度引申即打回重写。</li><li><strong>知识图谱增强（GraphRAG）：</strong> 纯语义向量检索（Dense Retrieval）容易丢失学术论文中严密的实体逻辑关系。引入知识图谱提取论文中的实体关系（如“算法A -&gt; 改进了 -&gt; 缺陷B”），结合图谱路径进行事实性约束。</li><li><strong>验证链（Chain-of-Verification, CoVe）：</strong> 强制模型在生成初步答案后，自行提取出答案中的关键事实陈述，自我生成核查问题，并重新独立查询上下文进行验证，最后输出修正后的答案。</li></ul></li><li><strong>严格忠实原文的工作流部署：</strong><ul><li><strong>强制溯源（Attribution Requirement）：</strong> 在 Prompt 中下达强指令：“你的每一句学术总结，必须附带原文的段落引用”。通过代码层面的后处理正则校验，拒绝任何没有对应 <code>[Reference: Section X]</code> 的输出。</li><li><strong>忠实度置信度阈值：</strong> 引入自然语言推理（NLI）判别模型作为最后一道防线，计算生成的摘要与检索到的原文块之间的蕴含关系得分。若得分低于设定阈值，则判定为发生内在幻觉（Intrinsic Hallucination），向用户抛出警告而非错误答案。</li></ul></li></ol>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8Ahello%20agents%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%99%BA%E8%83%BD%E4%BD%93%E4%B8%8E%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80/</id>
    <link href="https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8Ahello%20agents%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%99%BA%E8%83%BD%E4%BD%93%E4%B8%8E%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E5%9F%BA%E7%A1%80/"/>
    <published>2026-03-05T13:21:27.000Z</published>
    <summary>
      <![CDATA[<h2 id="第一章-初识智能体"><a href="#第一章-初识智能体" class="headerlink" title="第一章 - 初识智能体"></a>第一章 - 初识智能体</h2><p>本章从人工智能的发展脉络出发，系统阐述了智能体（Agent）的本质定义、演进]]>
    </summary>
    <title>《hello agents》读书笔记 - 智能体与语言模型基础</title>
    <updated>2026-03-05T13:21:27.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="《深入理解Go语言》" scheme="https://cooooing.github.io/tags/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这本书前四章篇幅超过三分之一<br>五到十二章共八章 和 十三到二十一共九章 大约各占三分之一<br>这章节内容量差的有点大</p><p>第三部分是关于 Zinx 框架的，跳过。 </p><h2 id="第五章-有关Goroutine⽆限创建的分析"><a href="#第五章-有关Goroutine⽆限创建的分析" class="headerlink" title="第五章 - 有关Goroutine⽆限创建的分析"></a>第五章 - 有关Goroutine⽆限创建的分析</h2><h3 id="进程、线程与协程的本质区别"><a href="#进程、线程与协程的本质区别" class="headerlink" title="进程、线程与协程的本质区别"></a>进程、线程与协程的本质区别</h3><h4 id="进程-Process-——-资源分配的最小单位"><a href="#进程-Process-——-资源分配的最小单位" class="headerlink" title="进程 (Process) —— 资源分配的最小单位"></a>进程 (Process) —— 资源分配的最小单位</h4><ul><li>内存空间**：每个进程拥有独立的虚拟地址空间（32位系统为 4GB，64位更大）。</li><li>组成部分**：拥有独立的堆、栈、全局变量区、代码区等。</li><li>调度机制**：直接由操作系统内核调度，通过 PCB（进程控制块）管理。</li><li>特点**：隔离性强，一个进程崩溃不影响其他进程；但资源消耗最重。</li></ul><h4 id="线程-Thread-——-CPU-调度的最小单位"><a href="#线程-Thread-——-CPU-调度的最小单位" class="headerlink" title="线程 (Thread) —— CPU 调度的最小单位"></a>线程 (Thread) —— CPU 调度的最小单位</h4><ul><li>轻量级进程 (Light Weight Process，LWP)**：线程是“寄生”在进程之上的执行流。</li><li>资源共享**：线程拥有<strong>独立栈空间</strong>，但共享所属进程的堆区、全局区等内存。</li><li>通信与风险**：通信简单（加锁访问共享内存），但由于关联性强，一个线程的非法操作可能导致整个进程崩溃。</li></ul><h4 id="执行单元与-CPU-调度原理"><a href="#执行单元与-CPU-调度原理" class="headerlink" title="执行单元与 CPU 调度原理"></a>执行单元与 CPU 调度原理</h4><ul><li>调度策略**：Linux 内核不严格区分进程和线程，统一视为执行单元。CPU 会平均分配时间片。</li><li>性能欺骗**：增加线程数可以获得更多 CPU 时间片比例（如进程 A 1个线程，进程 B 3个线程，B 获得的 CPU 资源更多），但这并非无限制。<br>切换内核栈和切换硬件上下⽂都会触发性能的开销，切换时会保存寄存器中的内容，将之前的执⾏流程状态保存，也会导致CPU⾼速缓存失效。</li></ul><h3 id="切换成本"><a href="#切换成本" class="headerlink" title="切换成本"></a>切换成本</h3><p>协程（Goroutine）之所以在并发表现上远超传统线程，核心原因在于其<strong>切换效率</strong>极高。其快的主要原因可归纳为两点：</p><ol><li><strong>空间差异</strong>：协程切换完全在<strong>用户空间</strong>完成；而线程切换涉及<strong>特权模式切换</strong>，必须进入内核空间。</li><li><strong>工作量差异</strong>：协程切换需要保存和恢复的数据量极小。</li></ol><h4 id="协程切换成本"><a href="#协程切换成本" class="headerlink" title="协程切换成本"></a>协程切换成本</h4><p>协程的上下文切换过程非常精简，仅涉及最核心的寄存器操作：</p><ul><li>切换机制**：<ol><li>保存当前协程的 <strong>CPU 寄存器状态</strong>。</li><li>将即将执行的协程的寄存器状态加载到 CPU 中。</li></ol></li><li>执行环境**：完全在用户态进行，不触发系统调用（System Call）。</li><li>性能表现**：一次典型的协程上下文切换耗时仅需 <strong>几十纳秒 (ns)</strong>。</li></ul><h4 id="线程切换成本"><a href="#线程切换成本" class="headerlink" title="线程切换成本"></a>线程切换成本</h4><p>线程是操作系统内核调度的基本单元，其切换过程沉重得多：</p><ul><li>权限切换**：线程调度由具有最高权限的<strong>内核空间</strong>完成。因此，线程切换必然涉及<strong>用户态与内核态的切换</strong>（即特权模式切换）。</li><li>复杂流程**：<ol><li>触发系统调用过程。</li><li>由操作系统调度模块介入。</li></ol></li><li>上下文负载**：除了和协程相同的 CPU 寄存器状态外，还包含<strong>线程私有的栈</strong>、硬件上下文以及更多内核管理数据。</li><li>间接影响**：频繁的内核切换会导致 CPU 高速缓存（Cache）失效，页表查找变慢，从而拖慢程序整体运行速度。</li></ul><h3 id="为什么不能无限创建-Goroutine？"><a href="#为什么不能无限创建-Goroutine？" class="headerlink" title="为什么不能无限创建 Goroutine？"></a>为什么不能无限创建 Goroutine？</h3><p>尽管 Goroutine 轻量，但“无限”创建会导致：</p><ol><li><strong>CPU 飙升</strong>：大量协程即使在切换时开销小，但由于基数过大，调度器压力激增，导致 CPU 满负荷。</li><li><strong>内存占用上涨</strong>：虽然每个协程占用内存很小，但协程量大后容易触发 OOM（内存溢出）。</li><li><strong>主进程崩溃</strong>：操作系统感知到资源异常，发送 <code>kill</code> 信号，或触发 Go 运行时 Panic。</li></ol><h4 id="控制-Goroutine-数量的方案"><a href="#控制-Goroutine-数量的方案" class="headerlink" title="控制 Goroutine 数量的方案"></a>控制 Goroutine 数量的方案</h4><h5 id="使用带缓冲的-Channel"><a href="#使用带缓冲的-Channel" class="headerlink" title="使用带缓冲的 Channel"></a>使用带缓冲的 Channel</h5><ul><li>逻辑**：创建容量为 N 的 Channel，开启协程前向其写入，结束后读取。</li><li>优点**：能利用 Channel 的阻塞特性天然限制并发速率。</li><li>缺点**：若主进程退出太快，未完成的协程会随之销毁，导致结果不准确。</li></ul><h5 id="使用-sync-WaitGroup"><a href="#使用-sync-WaitGroup" class="headerlink" title="使用 sync.WaitGroup"></a>使用 <code>sync.WaitGroup</code></h5><ul><li>逻辑**：<code>Add()</code> 增加计数，<code>Done()</code> 减少。</li><li>失败分析**：虽然能保证所有协程执行完，但<strong>无法限制创建瞬间的并发量</strong>。如果任务生产极快，系统会瞬间因创建过多协程而崩溃。</li></ul><h5 id="Channel-sync-WaitGroup-组合"><a href="#Channel-sync-WaitGroup-组合" class="headerlink" title="Channel + sync.WaitGroup 组合"></a>Channel + <code>sync.WaitGroup</code> 组合</h5><ul><li>逻辑**：<ol><li>用缓冲 Channel 限制最大并发数。</li><li>用 <code>sync.WaitGroup</code> 确保主进程在所有任务完成后才退出。</li></ol></li><li>结论**：这是最常用的基础并发控制手段。</li></ul><h5 id="发送-执行分离方式"><a href="#发送-执行分离方式" class="headerlink" title="发送&#x2F;执行分离方式"></a>发送&#x2F;执行分离方式</h5><ul><li>逻辑**：将“生产任务”和“消费任务”解耦。</li><li>结构**：<ol><li>固定 M 个 Worker 协程长期运行。</li><li>任务通过无缓冲 Channel 发送。</li><li>当 M 个 Worker 全忙时，发送端自然阻塞。</li></ol></li><li>优点**：灵活性最高，是<strong>Worker 工作池</strong>的设计蓝本。</li></ul><h3 id="小节"><a href="#小节" class="headerlink" title="小节"></a>小节</h3><ul><li>Go 的垃圾回收只能回收<strong>不再被引用</strong>的内存，无法回收<strong>正在阻塞运行</strong>或<strong>逻辑泄漏</strong>的 Goroutine。</li><li>Goroutine 泄漏是内存泄漏的一种严重表现形式。<strong>必须从代码逻辑上确保协程能正常退出</strong>。</li></ul><h2 id="第六章-Go-语言中的逃逸现象：变量“何时在栈、何时在堆”"><a href="#第六章-Go-语言中的逃逸现象：变量“何时在栈、何时在堆”" class="headerlink" title="第六章 - Go 语言中的逃逸现象：变量“何时在栈、何时在堆”"></a>第六章 - Go 语言中的逃逸现象：变量“何时在栈、何时在堆”</h2><h3 id="Go-语言中的逃逸现象"><a href="#Go-语言中的逃逸现象" class="headerlink" title="Go 语言中的逃逸现象"></a>Go 语言中的逃逸现象</h3><h4 id="Go-语言中访问子函数的局部变量"><a href="#Go-语言中访问子函数的局部变量" class="headerlink" title="Go 语言中访问子函数的局部变量"></a>Go 语言中访问子函数的局部变量</h4><p>在 Go 语言中，函数可以安全地返回局部变量的地址（指针），供外部函数使用。<br>程序能正常运行并输出正确值，不会报编译错误或运行错误。</p><p>Go 编译器会自动检测变量生命周期，如果发现局部变量在函数退出后仍被引用，会将其从“栈”转移到“堆”上。</p><h4 id="C-C-中访问子函数的局部变量"><a href="#C-C-中访问子函数的局部变量" class="headerlink" title="C&#x2F;C++ 中访问子函数的局部变量"></a>C&#x2F;C++ 中访问子函数的局部变量</h4><p>在 C&#x2F;C++ 中，局部变量分配在栈上，函数退出后栈空间被回收。如果返回局部变量地址，外部访问将指向已销毁的内存。<br>编译器会发出警告（Returning address of local variable），运行后通常会导致 <strong>段错误（Segmentation Fault）</strong> 或获取到随机垃圾值。<br>Go 通过“逃逸分析”机制，消除了开发者手动管理堆栈内存的负担。</p><h3 id="逃逸分析过程示例"><a href="#逃逸分析过程示例" class="headerlink" title="逃逸分析过程示例"></a>逃逸分析过程示例</h3><h4 id="示例过程"><a href="#示例过程" class="headerlink" title="示例过程"></a>示例过程</h4><ul><li>逃逸分析定义**：Go编译器在编译阶段通过静态分析，判断变量的作用域。若变量生命周期超过函数范围，则产生“逃逸”。</li><li>检测工具**：<ol><li><strong>编译分析</strong>：使用 <code>go tool compile -m pro.go</code>。若看到 <code>moved to heap: x</code>，说明变量发生了逃逸。</li><li><strong>汇编分析</strong>：使用 <code>go tool compile -S pro.go</code>。搜索 <code>runtime.newobject</code> 关键字。在堆上开辟空间必须调用此内核函数，而栈空间分配通常只是简单的地址偏移。</li></ol></li><li>成本考量**：分配在堆上的变量需要由 GC（垃圾回收）进行跟踪、标记和回收，会消耗额外的计算资源。</li></ul><h4 id="new-的变量在栈还是堆"><a href="#new-的变量在栈还是堆" class="headerlink" title="new 的变量在栈还是堆"></a>new 的变量在栈还是堆</h4><p>在C++中 <code>new</code> 一定在堆上，但在Go中，<strong><code>new</code> 出来的变量不一定在堆上</strong>。</p><ul><li>判断准则**：<ul><li>即使使用 <code>new</code> 申请内存，如果变量<strong>没有逃逸</strong>（仅在函数内部使用且未返回地址），编译器仍会将其分配在<strong>栈</strong>上。</li><li>只有当编译器分析出该变量被外部引用时，才会将其分配到堆。</li></ul></li></ul><h3 id="普遍的逃逸规则"><a href="#普遍的逃逸规则" class="headerlink" title="普遍的逃逸规则"></a>普遍的逃逸规则</h3><p>逃逸的普遍的规则就是如果变量需要使⽤堆空间，就应该进⾏逃逸。</p><p>多级间接赋值（引用对象中的成员又是引用类型）极易导致逃逸。<br>引用类型包括 <code>func</code>、<code>interface</code>、<code>slice</code>、<code>map</code>、<code>channel</code> 和 <code>指针</code> 等。</p><p>常见逃逸范例清单：</p><ol><li><strong>[]any 类型</strong>：给切片元素赋值必定逃逸（如 <code>data[0] = 100</code>）。</li><li><strong>map[string]any 类型</strong>：赋值时对应的 value 必定逃逸。</li><li><strong>map[any]any 类型</strong>：赋值时 key 和 value 均会发生逃逸。</li><li><strong>map[string][]string 类型</strong>：赋值时内部的切片 <code>[]string</code> 会发生逃逸。</li><li><strong>[]*int 类型</strong>：切片内存储的是指针，赋值时该指针指向的原始变量会发生逃逸。</li><li><strong>func(*int) 类型</strong>：作为函数参数传递指针时，形参对应的实参会逃逸。</li><li><strong>func([]string) 类型</strong>：作为函数参数传递切片时，实参切片会逃逸。</li><li><strong>chan []string 类型</strong>：向管道中发送切片数据，该切片会发生逃逸。</li></ol><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><ul><li>变量在堆还是栈，完全由<strong>编译器</strong>根据逃逸分析决定，与变量是否通过 <code>new</code> 创建无关。<ol><li><strong>减少指针使用</strong>：多级间接访问（指针嵌套）会增加逃逸概率。在高性能场景下，应有意识地减少不必要的指针传递。</li><li><strong>关注引用类型</strong>：<code>map</code>、<code>slice</code>、<code>any</code> 等类型本身就是引用类型，结合赋值操作时要留意其逃逸行为。</li></ol></li><li>逃逸分析并非完美。有时编译器会“保守”地将本可以留在栈上的变量移动到堆中。</li><li>深入理解逃逸分析，可以写出更高效的代码，减轻GC压力，提升程序整体性能。</li></ul><h2 id="第七章-interface-剖析与-Go-语言面向对象思想"><a href="#第七章-interface-剖析与-Go-语言面向对象思想" class="headerlink" title="第七章 - interface 剖析与 Go 语言面向对象思想"></a>第七章 - interface 剖析与 Go 语言面向对象思想</h2><h3 id="interface-的基本概念与赋值问题"><a href="#interface-的基本概念与赋值问题" class="headerlink" title="interface 的基本概念与赋值问题"></a>interface 的基本概念与赋值问题</h3><ol><li>interface 的核心特征<ul><li>方法集合**：interface 是一组方法声明的集合。</li><li>隐式实现**：无需 <code>implements</code> 关键字。只要一个类型实现了接口要求的所有方法，就自动实现了该接口（Duck Typing 思想）。</li><li>多态性**：接口变量可以存储任何实现了该接口的实例。</li></ul></li><li>interface 赋值中的多态要素，实现多态需满足：<ol><li>定义 <strong>interface 接口</strong>及方法。</li><li><strong>子类（结构体）重写</strong>接口中的所有方法。</li><li><strong>父类指针（接口变量）指向子类对象</strong>。</li></ol></li></ol><h3 id="接口的内部构造"><a href="#接口的内部构造" class="headerlink" title="接口的内部构造"></a>接口的内部构造</h3><p>Go 根据接口是否包含方法，将其底层结构分为两种：<code>eface</code> 和 <code>iface</code>。</p><h4 id="空接口-eface"><a href="#空接口-eface" class="headerlink" title="空接口 eface"></a>空接口 eface</h4><p>空接口 <code>interface{}</code> 不包含任何方法。</p><ul><li>结构定义：<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> eface <span class="keyword">struct</span> &#123;</span><br><span class="line">    _type *_type          <span class="comment">// 类型信息</span></span><br><span class="line">    data  unsafe.Pointer  <span class="comment">// 指向具体实例数据的指针</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>可以指向任何类型。<code>_type</code> 决定了如何解释 <code>data</code> 里的内容。</li></ul><h4 id="非空接口-iface"><a href="#非空接口-iface" class="headerlink" title="非空接口 iface"></a>非空接口 iface</h4><p>带有一组方法的接口。</p><ul><li>结构定义**：<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> iface <span class="keyword">struct</span> &#123;</span><br><span class="line">    tab  *itab            <span class="comment">// 核心：存放接口类型、具体类型及方法地址</span></span><br><span class="line">    data unsafe.Pointer   <span class="comment">// 具体实例数据的指针</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ITab <span class="keyword">struct</span> &#123;</span><br><span class="line">    Inter *InterfaceType  <span class="comment">// 接口自身元数据信息</span></span><br><span class="line">    Type  *Type</span><br><span class="line">    Hash  <span class="type">uint32</span>     <span class="comment">// copy of Type.Hash. Used for type switches.</span></span><br><span class="line">    Fun   [<span class="number">1</span>]<span class="type">uintptr</span> <span class="comment">// variable sized. fun[0]==0 means Type does not implement Inter.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>itab 结构**（占32字节）：<ul><li><code>interfacetype</code>：接口本身的定义信息。</li><li><code>type</code>：具体实现类的类型信息。</li><li><code>hash</code>：具体类型的哈希值，用于快速判断类型是否一致。Go 语⾔的 interface 的 Duck-typing 机制也依赖这个字段实现。</li><li><code>fun</code>：<strong>函数指针数组</strong>。保存具体实现方法的地址。是⼀个动态⼤⼩的数组，虽然声明时固定⼤⼩为 1，但在使⽤时会直接通过 fun 指针获取其中的数据，并且不会检查数组的边界，所以该数组中保存的元素数量是不确定的。</li></ul></li></ul><h4 id="接口为-nil-的判定陷阱"><a href="#接口为-nil-的判定陷阱" class="headerlink" title="接口为 nil 的判定陷阱"></a>接口为 nil 的判定陷阱</h4><p><strong>一个接口变量只有在 <code>类型(type/tab)</code> 和 <code>数据(data)</code> 同时为 <code>nil</code> 时，才等于 <code>nil</code>。</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> People1 <span class="keyword">interface</span> &#123;</span><br><span class="line">Name() <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> People2 <span class="keyword">interface</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Student <span class="keyword">struct</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(s *Student)</span></span> Name() <span class="type">string</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="string">&quot;Student&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">live1</span><span class="params">()</span></span> People1 &#123;</span><br><span class="line"><span class="keyword">var</span> s *Student</span><br><span class="line"><span class="keyword">return</span> s</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">live2</span><span class="params">()</span></span> People2 &#123;</span><br><span class="line"><span class="keyword">var</span> s People2</span><br><span class="line"><span class="keyword">return</span> s</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestNil</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> live1() == <span class="literal">nil</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;live1() is nil&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;live1() is not nil&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> live2() == <span class="literal">nil</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;live2() is nil&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">t.Log(<span class="string">&quot;live2() is not nil&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestNil</span><br><span class="line">    test_test.go:32: live1() is not nil</span><br><span class="line">    test_test.go:35: live2() is nil</span><br><span class="line">--- PASS: TestNil (0.00s)</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure><h4 id="interface-与-interface"><a href="#interface-与-interface" class="headerlink" title="interface{} 与 *interface{}"></a>interface{} 与 *interface{}</h4><ul><li>interface{}**：万能类型，可以接收任何值。</li><li>*interface{}**：指向接口变量的 <strong>指针</strong>。它不再是万能类型，只能接收 <code>*interface{}</code> 类型的地址。在开发中极少使用，通常是逻辑错误。</li></ul><h3 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h3><ul><li>区分 <code>eface</code> 和 <code>iface</code> 是理解 Go 内存管理和类型系统的基础。</li><li>警惕“接口不为 nil 但底层指向 nil 指针”的问题。</li><li>尽量<strong>面向抽象编程</strong>。</li><li>遵循<strong>开闭原则</strong>（通过扩展而非修改来应对需求变化）。</li><li>遵循<strong>依赖倒转原则</strong>（高层不依赖低层，两者都依赖抽象）。</li></ul><h2 id="第八章-defer-践行中必备的要领"><a href="#第八章-defer-践行中必备的要领" class="headerlink" title="第八章 - defer 践行中必备的要领"></a>第八章 - defer 践行中必备的要领</h2><p><code>defer</code> 是 Go 语言中极具特色的关键字，用于确保资源回收、状态重置或业务闭环在函数结束前执行。<br>虽然语法简单，但在复合场景（如配合 <code>return</code>、<code>panic</code>、有名返回值）下存在特定逻辑。</p><h3 id="defer-的执行顺序：栈结构"><a href="#defer-的执行顺序：栈结构" class="headerlink" title="defer 的执行顺序：栈结构"></a>defer 的执行顺序：栈结构</h3><p>多个 <code>defer</code> 语句遵循 <strong>后进先出 (LIFO)</strong> 的原则。</p><ol><li>遇到 <code>defer</code>：将表达式压入栈中（不立即执行）。</li><li>函数结束（遇到 <code>return</code> 或执行完毕）：从栈顶弹出 <code>defer</code> 表达式并依次执行。</li></ol><h3 id="defer-与-return-的顺序关系"><a href="#defer-与-return-的顺序关系" class="headerlink" title="defer 与 return 的顺序关系"></a>defer 与 return 的顺序关系</h3><p><strong><code>return</code> 后的表达式先执行，<code>defer</code> 后面的语句后执行。</strong></p><ul><li>触发时机：<code>defer</code> 触发的出栈时机是函数作用域结束。由于 <code>return</code> 语句是函数内部的最后一条指令，它必须先于函数销毁前完成计算和赋值。</li><li>简单理解：<code>return</code> 负责“设定返回值”，<code>defer</code> 负责“清理工作”，最后函数彻底退出。</li></ul><h3 id="函数返回值的初始化"><a href="#函数返回值的初始化" class="headerlink" title="函数返回值的初始化"></a>函数返回值的初始化</h3><p>Go 允许在函数定义时为返回值命名（有名返回值）。</p><ul><li>初始化时机：有名返回值（如 <code>t int</code>）会在函数起始处被自动初始化为对应类型的零值（如 0）。</li><li>作用域：该变量的生命周期贯穿整个函数，并在函数结束时作为最终结果返回。</li></ul><h3 id="有名函数返回值遇见-defer"><a href="#有名函数返回值遇见-defer" class="headerlink" title="有名函数返回值遇见 defer"></a>有名函数返回值遇见 defer</h3><p>由于有名返回值的变量作用域覆盖了整个函数，且 <code>defer</code> 执行晚于 <code>return</code> 的赋值动作，因此 <strong><code>defer</code> 里的逻辑可以直接修改已设定的返回值内容。</strong></p><p><strong>示例分析</strong>：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">test</span><span class="params">()</span></span> (t <span class="type">int</span>) &#123;</span><br><span class="line">    <span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">        t *= <span class="number">10</span> </span><br><span class="line">    &#125;()</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span> </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>t 初始化为 0</li><li>先 return，t 赋值为 1</li><li>再 defer ，t 赋值为 10</li><li>最后函数返回 10</li></ol><h3 id="defer-遇见-panic"><a href="#defer-遇见-panic" class="headerlink" title="defer 遇见 panic"></a>defer 遇见 panic</h3><p><code>panic</code> 会触发当前协程已压栈的所有 <code>defer</code>。</p><ul><li>处理流程：<ol><li>发生 <code>panic</code>。</li><li>逆序遍历并执行本协程的 <code>defer</code> 链表。</li><li><strong>捕获</strong>：若某个 <code>defer</code> 包含 <code>recover()</code>，则 <code>panic</code> 停止，程序恢复。</li><li><strong>未捕获</strong>：遍历完所有 <code>defer</code> 后，向 stderr 抛出异常信息并崩溃。</li></ol></li><li>关键点**：在 <code>panic</code> 语句之后定义的 <code>defer</code> 不会被压栈，因此也就不会执行。</li></ul><h3 id="defer-中包含-panic"><a href="#defer-中包含-panic" class="headerlink" title="defer 中包含 panic"></a>defer 中包含 panic</h3><ul><li>冲突规则**：如果在执行 <code>defer</code> 过程中产生新的 <code>panic</code>，它会<strong>覆盖</strong>之前的 <code>panic</code>。</li><li>捕获原则**：<code>recover()</code> 仅能捕获到最后一个发生的 <code>panic</code>。</li></ul><h3 id="defer-下的函数参数包含子函数"><a href="#defer-下的函数参数包含子函数" class="headerlink" title="defer 下的函数参数包含子函数"></a>defer 下的函数参数包含子函数</h3><ul><li>求值规则**：<strong><code>defer</code> 函数的参数在压栈时就会被立即求值</strong>，而不是在函数最终执行时。</li><li>示例流程**：<code>defer func1(arg1, func2())</code><ol><li>计算 <code>func2()</code> 的值（立即执行）。</li><li>将结果和 <code>func1</code> 的地址压栈。</li><li>函数结束时再弹出并执行 <code>func1</code>。</li></ol></li></ul><h3 id="章节小结"><a href="#章节小结" class="headerlink" title="章节小结"></a>章节小结</h3><ol><li><strong>栈序</strong>：后进先出。</li><li><strong>先赋值，后 defer</strong>：<code>return</code> 设置返回值的动作早于 <code>defer</code> 执行。</li><li><strong>参数立即求值</strong>：传给 <code>defer</code> 函数的参数在压栈那一刻就确定了。</li><li><strong>保活功能</strong>：<code>defer</code> 配合 <code>recover</code> 是防范程序因 <code>panic</code> 崩溃的最后防线。</li><li><strong>变量影响</strong>：能否修改返回值，取决于该返回值是否有名字（有名返回值）以及 <code>defer</code> 是如何引用该变量的（传参还是闭包）。</li></ol><h2 id="第九章-Go-语言常用问题及性能调试实践方法"><a href="#第九章-Go-语言常用问题及性能调试实践方法" class="headerlink" title="第九章 - Go 语言常用问题及性能调试实践方法"></a>第九章 - Go 语言常用问题及性能调试实践方法</h2><h3 id="如何分析程序运行时间与-CPU-利用率"><a href="#如何分析程序运行时间与-CPU-利用率" class="headerlink" title="如何分析程序运行时间与 CPU 利用率"></a>如何分析程序运行时间与 CPU 利用率</h3><h4 id="Shell-内置-time-指令"><a href="#Shell-内置-time-指令" class="headerlink" title="Shell 内置 time 指令"></a>Shell 内置 time 指令</h4><p>在 Linux&#x2F;Unix 中直接使用 <code>time</code> 命令运行程序。</p><p>指标说明：</p><ol><li><strong>real</strong>：实际消耗时间（从开始到结束）。</li><li><strong>user</strong>：程序在用户态消耗的 CPU 时间。</li><li><strong>sys</strong>：程序在内核态消耗的 CPU 时间（系统调用等）。</li></ol><p><strong>通常 <code>real &gt;= user + sys</code>，差值部分是系统调度其他进程的时间。</strong></p><h4 id="usr-bin-time-指令"><a href="#usr-bin-time-指令" class="headerlink" title="/usr/bin/time 指令"></a><code>/usr/bin/time</code> 指令</h4><p>使用绝对路径调用可以获得更丰富的信息，建议配合 <code>-v</code> 参数。<br>除了三种时间外，还提供 <strong>CPU 占用率</strong>、<strong>内存使用峰值</strong>、<strong>页错误 (Page Fault)</strong>、<strong>进程切换次数</strong>、<strong>文件 I&#x2F;O</strong> 和 <strong>Socket 使用情况</strong>等。</p><h3 id="如何分析-Go-语言内存使用情况"><a href="#如何分析-Go-语言内存使用情况" class="headerlink" title="如何分析 Go 语言内存使用情况"></a>如何分析 Go 语言内存使用情况</h3><h4 id="系统层查看：top-命令"><a href="#系统层查看：top-命令" class="headerlink" title="系统层查看：top 命令"></a>系统层查看：top 命令</h4><p>通过 <code>top -p $(pidof 程序名)</code> 查看进程的内存占用。<br>有时程序逻辑执行完，<code>top</code> 显示内存依然很高。这不一定是泄露，可能是 Go 垃圾回收后尚未将内存归还给操作系统。</p><h4 id="GODEBUG-与-gctrace（追踪-GC）"><a href="#GODEBUG-与-gctrace（追踪-GC）" class="headerlink" title="GODEBUG 与 gctrace（追踪 GC）"></a>GODEBUG 与 gctrace（追踪 GC）</h4><p>通过设置环境变量启动程序：<code>GODEBUG=&#39;gctrace=1&#39;</code>。<br>设置<code>gctrace=1</code>会使垃圾回收器在每次回收时汇总所回收内存的⼤⼩及耗时，并将这些内容汇总成单⾏内容打印到标准错误输出中。</p><p>输出：<code>gc 1 @0.010s 1%: 0+0.53+0 ms clock, 0+1.0/2.6/0+0 ms cpu, 3-&gt;4-&gt;1 MB, 4 MB goal, 0 MB stacks, 0 MB globals, 20 P</code></p><p>输出格式解读：</p><ul><li><code>gc 1</code>：GC 编号。</li><li><code>@0.010s</code>：程序开始后的时间。</li><li><code>1%</code>：GC 占用的时间比例。</li><li><code>3-&gt;4-&gt;1MB</code>：GC 开始前堆大小 -&gt; GC 结束后堆大小 -&gt; 当前活跃堆大小。</li></ul><p>如果 <code>top</code> 很高但 <code>gctrace</code> 显示活跃堆很小，说明内存已在应用层回收，但仍被 Go 进程持有。</p><h4 id="runtime-ReadMemStats"><a href="#runtime-ReadMemStats" class="headerlink" title="runtime.ReadMemStats"></a>runtime.ReadMemStats</h4><p>在代码中主动读取内存状态，适合自动化监控。</p><ol><li>基础统计 (General Statistics) 这部分反映了程序内存分配的宏观数据。<ul><li><strong>Alloc &#x2F; HeapAlloc</strong>: 当前堆上<strong>活跃对象</strong>占用的字节数。该值会随 GC 释放对象而减小。<strong>反映业务数据量</strong></li><li>TotalAlloc: 累计分配的堆内存字节数。<strong>只增不减</strong>，即使对象被释放，该值也记录总量。</li><li><strong>Sys</strong>: 从操作系统获取的<strong>虚拟内存总额</strong>。包含堆、栈、及其它内部数据结构。注意：这是虚拟地址空间，不一定全部对应物理内存。</li><li>Mallocs: 累计分配的对象总数。</li><li>Frees: 累计释放的对象总数。</li><li>活跃对象数** &#x3D; <code>Mallocs - Frees</code>。</li></ul></li><li>堆内存详情 (Heap Statistics) Go 将堆内存划分为 <strong>Span</strong>（8K 或更大的连续区域），Span 分为：<code>空闲(Idle)</code>、<code>使用中(In-use)</code>、<code>栈(Stack)</code>。<ul><li>HeapSys: 为堆申请的虚拟内存字节数。包括已使用、未使用（Idle）以及已归还给系统的内存。</li><li><strong>HeapInuse</strong>: 正在使用的 Span 占用的字节数（至少包含一个对象）。<ul><li><em>碎片估算</em>：<code>HeapInuse - HeapAlloc</code> 是已预留给特定大小对象但当前未使用的字节数（内部碎片）。</li></ul></li><li>HeapIdle: 空闲 Span 占用的字节数。可以被重新用于分配对象、转为栈内存，或归还给 OS。</li><li><strong>HeapReleased</strong>: <strong>已归还给操作系统的物理内存</strong>。这些内存属于空闲 Span，OS 可以回收其物理页面。<ul><li><em>待回收内存估算</em>：<code>HeapIdle - HeapReleased</code> 表示运行时保留的、用于未来扩展堆的空闲内存，尚未归还给 OS。</li></ul></li><li>HeapObjects: 当前堆上的对象总数。</li></ul></li><li>栈与内部结构 (Stack &amp; Off-heap)<ul><li>StackInuse: 协程栈（Goroutine Stacks）占用的字节数。</li><li>StackSys: 从 OS 获取的用于栈的内存（通常等于 <code>StackInuse</code>，但在 CGO 环境下包含线程栈）。</li><li>MSpanInuse &#x2F; MSpanSys: 用于存放 <code>mspan</code> 结构体（元数据）的内存。</li><li>MCacheInuse &#x2F; MCacheSys: 用于存放 <code>mcache</code> 结构体（元数据）的内存。</li><li>GCSys: 垃圾回收元数据（如位图、标记位）占用的内存。</li><li>OtherSys: 运行时内部其它分配（如 profiling 记录）占用的内存。</li></ul></li><li>垃圾回收统计 (GC Statistics)<ul><li>NextGC: 下一次触发 GC 的目标堆大小。目标是保持 <code>HeapAlloc ≤ NextGC</code>。</li><li>LastGC: 上次 GC 完成的时间戳（纳秒）。</li><li>PauseTotalNs: 程序启动以来 GC 累计导致的 STW（Stop-The-World）总时间。</li><li><strong>PauseNs</strong> [256]: 循环缓冲区，存储最近 256 次 GC 的 STW 停顿时间。<strong>衡量程序卡顿（抖动）的关键</strong></li><li>PauseEnd [256]: 循环缓冲区，存储最近 256 次 GC 结束的时间戳。</li><li>NumGC: 已完成的 GC 次数。</li><li>NumForcedGC: 用户手动调用 <code>runtime.GC()</code> 触发的次数。</li><li><strong>GCCPUFraction</strong>: GC 消耗的 CPU 时间占总可用 CPU 时间的比例（0.0 到 1.0）。<strong>反映 GC 对业务性能的侵占程度</strong></li></ul></li><li>分级分配统计 (Size Class Statistics)<ul><li>BySize [61]: 一个数组，记录了 Go 内部 61 种不同“大小级别”（Size Class）的分配情况。<ul><li><code>Size</code>: 该级别对象的最大字节数。</li><li><code>Mallocs / Frees</code>: 该特定大小级别累计的分配&#x2F;释放次数。</li></ul></li></ul></li></ol><h4 id="pprof-工具（Web-界面查看内存）"><a href="#pprof-工具（Web-界面查看内存）" class="headerlink" title="pprof 工具（Web 界面查看内存）"></a>pprof 工具（Web 界面查看内存）</h4><p>在代码中引入 <code>_ &quot;net/http/pprof&quot;</code> 并启动一个 HTTP 监听。<br>可以直观展示当前的堆内存分配详情。</p><h3 id="如何获取-CPU-性能情况"><a href="#如何获取-CPU-性能情况" class="headerlink" title="如何获取 CPU 性能情况"></a>如何获取 CPU 性能情况</h3><p>性能分析的前置条件（环境稳定性）</p><ol><li>机器必须闲置，关闭省电和过热模式。</li><li>避免在虚拟机或共享云主机上进行高精度的测试。</li><li>多次测试以取相对一致的结果。</li></ol><h4 id="使用-go-tool-pprof-分析数据"><a href="#使用-go-tool-pprof-分析数据" class="headerlink" title="使用 go tool pprof 分析数据"></a>使用 go tool pprof 分析数据</h4><p>两种方式获取 Profile 文件：</p><ol><li>访问 <code>http://127.0.0.1:端口/debug/pprof/profile</code>，默认等待 30s 后会下载一个采样文件。</li><li><code>go tool pprof http://localhost:端口/debug/pprof/profile?seconds=60</code>（采样 60 秒）。</li></ol><p>命令行格式：<code>go tool pprof [二进制文件] [profile文件]</code>。</p><ul><li>常用内部指令：</li><li>top：列出最耗 CPU 的函数。<ul><li><code>flat</code>：当前函数本身的耗时。</li><li><code>cum</code>：当前函数及其调用的子函数总耗时。</li></ul></li><li>list [函数名]**：定位到代码具体的某一行。</li></ul><h2 id="第十章-make-和-new-的原理性区别"><a href="#第十章-make-和-new-的原理性区别" class="headerlink" title="第十章 - make 和 new 的原理性区别"></a>第十章 - make 和 new 的原理性区别</h2><h3 id="变量的声明与初始化基础"><a href="#变量的声明与初始化基础" class="headerlink" title="变量的声明与初始化基础"></a>变量的声明与初始化基础</h3><p>零值机制：使用 <code>var</code> 声明变量而不指定初始值时，Go 会自动赋予其“零值”。</p><ul><li><code>int</code> -&gt; <code>0</code></li><li><code>string</code> -&gt; <code>&quot;&quot;</code></li><li><strong>引用类型&#x2F;指针</strong> -&gt; <code>nil</code></li></ul><p>对于引用类型（指针、切片、映射、通道），仅声明而不分配内存就进行操作，会导致 <code>panic: runtime error: invalid memory address or nil pointer dereference</code>。<br>值类型声明即分配空间；引用类型必须经过<strong>显式内存分配</strong>才能使用。</p><h3 id="new-与-make-的深度区别"><a href="#new-与-make-的深度区别" class="headerlink" title="new 与 make 的深度区别"></a>new 与 make 的深度区别</h3><h4 id="new-函数"><a href="#new-函数" class="headerlink" title="new 函数"></a>new 函数</h4><ul><li><strong>函数原型</strong>：<code>func new(Type) *Type</code><ol><li><strong>参数</strong>：只接受一个参数，即类型。</li><li><strong>动作</strong>：在堆上分配一块该类型的内存，并将内存<strong>置零</strong>（设为该类型的零值）。</li><li><strong>返回值</strong>：返回指向该内存地址的<strong>指针</strong>（即 <code>*Type</code>）。</li></ol></li><li><strong>应用场景</strong>：常用于结构体。由于 <code>new</code> 会自动初始化零值，结构体中的同步锁（如 <code>sync.Mutex</code>）或其它字段无需额外初始化即可直接使用，不会出现无效引用的异常。</li></ul><h4 id="make-函数"><a href="#make-函数" class="headerlink" title="make 函数"></a>make 函数</h4><ul><li><strong>函数原型</strong>：<code>func make(t Type, size ...IntegerType) Type</code><ol><li><strong>限定类型</strong>：<strong>仅用于</strong> <code>slice</code>（切片）、<code>map</code>（映射）和 <code>chan</code>（通道）。</li><li><strong>动作</strong>：内存分配并进行<strong>初始化</strong>（分配底层数据结构，如设置切片的长度、容量，创建 map 的哈希桶等）。</li><li><strong>返回值</strong>：返回<strong>类型本身</strong>（<code>Type</code>），而不是指针。</li></ol></li><li><strong>为什么不返回指针？</strong> 因为这三种类型本身就是引用类型（内部封装了指针），返回指针没有意义。</li></ul><h4 id="扩展：Map-使用中的经典坑点"><a href="#扩展：Map-使用中的经典坑点" class="headerlink" title="扩展：Map 使用中的经典坑点"></a>扩展：Map 使用中的经典坑点</h4><ol><li><strong>Value 赋值问题</strong>：<ul><li>如果 map 的值是结构体（如 <code>map[string]Student</code>），不能直接修改 <code>map[&quot;key&quot;].Name</code>。因为 map 的元素不可寻址（只读引用）。</li><li><strong>解决方案</strong>：将 map 定义为指针类型 <code>map[string]*Student</code>，这样修改指针指向的结构体内容是合法的。</li></ul></li><li><strong>遍历赋值问题</strong>：<ul><li>在 <code>for range</code> 循环中，循环变量（如 <code>stu</code>）是一个副本。</li><li>如果在循环内取 <code>&amp;stu</code> 存入 map，会导致 map 中所有的 key 最后都指向同一个地址（即循环变量的地址，其值为遍历的最后一个元素）。</li><li><strong>解决方案</strong>：使用索引直接取原数组元素的地址，如 <code>&amp;students[i]</code>。</li></ul></li></ol><h3 id="make-与-new-的异同点总结"><a href="#make-与-new-的异同点总结" class="headerlink" title="make 与 new 的异同点总结"></a>make 与 new 的异同点总结</h3><table><thead><tr><th align="left">特性</th><th align="left">new</th><th align="left">make</th></tr></thead><tbody><tr><td align="left"><strong>适用范围</strong></td><td align="left">任意类型</td><td align="left"><strong>仅限</strong> slice, map, channel</td></tr><tr><td align="left"><strong>分配空间</strong></td><td align="left">堆空间</td><td align="left">堆空间</td></tr><tr><td align="left"><strong>内存状态</strong></td><td align="left">置零（Zeroed）</td><td align="left">初始化（Initialized）</td></tr><tr><td align="left"><strong>返回值</strong></td><td align="left">指针 (<code>*T</code>)</td><td align="left">类型本身 (<code>T</code>)</td></tr><tr><td align="left"><strong>常用替代</strong></td><td align="left">短变量声明 <code>i := 0</code> 或字面量 <code>u := user{}</code></td><td align="left">必须使用 <code>make</code> 初始化这三类引用类型</td></tr></tbody></table><h3 id="特殊情况：用-new-初始化切片"><a href="#特殊情况：用-new-初始化切片" class="headerlink" title="特殊情况：用 new 初始化切片"></a>特殊情况：用 new 初始化切片</h3><ul><li>代码现象：执行 <code>list := new([]int)</code> 得到的是一个<strong>切片指针</strong>（指向 <code>nil</code> 切片的指针）。</li><li>编译错误：不能直接 <code>append(list, 1)</code>，因为 <code>append</code> 的第一个参数必须是切片而非指针。</li><li>解决建议：除非业务明确需要切片指针，否则应统一使用 <code>make([]int, 0)</code>。</li></ul><h3 id="小结-2"><a href="#小结-2" class="headerlink" title="小结"></a>小结</h3><ul><li><strong>new</strong> 是为了“分配内存并置零”，返回指针。虽然它能分配任何类型的内存，但在实际编程中，对于普通变量或结构体，更倾向于使用字面量初始化。</li><li><strong>make</strong> 是为了“创建并初始化”引用类型，返回对象。它是使用切片、映射和通道前的<strong>强制性步骤</strong>。</li></ul><h2 id="第十一章-精通-Go-Modules-项目依赖管理（过时，可略）"><a href="#第十一章-精通-Go-Modules-项目依赖管理（过时，可略）" class="headerlink" title="第十一章 - 精通 Go Modules 项目依赖管理（过时，可略）"></a>第十一章 - 精通 Go Modules 项目依赖管理（过时，可略）</h2><h3 id="GOPATH-工作模式"><a href="#GOPATH-工作模式" class="headerlink" title="GOPATH 工作模式"></a>GOPATH 工作模式</h3><h4 id="什么是-GOPATH"><a href="#什么是-GOPATH" class="headerlink" title="什么是 GOPATH"></a>什么是 GOPATH</h4><p>在 Go 1.11 之前，所有的开发工作必须在 <code>GOPATH</code> 环境变量指定的目录下进行。其目录结构包含：</p><ul><li><strong>bin</strong>：存放编译后的二进制可执行文件。</li><li><strong>pkg</strong>：存放预编译的目标文件（<code>.a</code> 文件），加速编译。</li><li><strong>src</strong>：存放源代码（项目必须存放在 <code>$GOPATH/src</code> 下）。</li></ul><h4 id="GOPATH-模式的弊端"><a href="#GOPATH-模式的弊端" class="headerlink" title="GOPATH 模式的弊端"></a>GOPATH 模式的弊端</h4><ol><li><strong>无版本控制概念</strong>：执行 <code>go get</code> 总是拉取最新代码，无法指定特定版本。</li><li><strong>无法同步第三方库版本</strong>：不同开发者的本地环境可能依赖了不同版本的库，导致编译结果不一致。</li><li><strong>引用路径冲突</strong>：无法在同一环境中处理同一个库的不同大版本（如 v1 和 v2）。</li></ol><h3 id="Go-Modules-核心配置"><a href="#Go-Modules-核心配置" class="headerlink" title="Go Modules 核心配置"></a>Go Modules 核心配置</h3><h4 id="常用命令-go-mod"><a href="#常用命令-go-mod" class="headerlink" title="常用命令 (go mod)"></a>常用命令 (go mod)</h4><table><thead><tr><th align="left">命令</th><th align="left">作用</th></tr></thead><tbody><tr><td align="left"><strong>go mod init</strong></td><td align="left">初始化当前文件夹，创建 <code>go.mod</code> 文件</td></tr><tr><td align="left"><strong>go mod download</strong></td><td align="left">下载依赖包到本地缓存</td></tr><tr><td align="left"><strong>go mod tidy</strong></td><td align="left">增加缺失的模块，移除未使用的模块（<strong>常用</strong>）</td></tr><tr><td align="left"><strong>go mod edit</strong></td><td align="left">编辑 <code>go.mod</code> 文件（如替换版本）</td></tr><tr><td align="left"><strong>go mod graph</strong></td><td align="left">打印模块依赖图</td></tr><tr><td align="left"><strong>go mod vendor</strong></td><td align="left">创建 <code>vendor</code> 目录，将所有依赖包复制到该目录下</td></tr><tr><td align="left"><strong>go mod verify</strong></td><td align="left">校验依赖是否被篡改</td></tr><tr><td align="left"><strong>go mod why</strong></td><td align="left">解释为什么需要依赖某个模块</td></tr></tbody></table><h4 id="关键环境变量"><a href="#关键环境变量" class="headerlink" title="关键环境变量"></a>关键环境变量</h4><ol><li><strong>GO111MODULE</strong>：<ul><li><code>off</code>：禁用模块支持，沿用 GOPATH。</li><li><code>on</code>：强制开启模块支持（推荐）。</li><li><code>auto</code>：默认值，根据目录下是否有 <code>go.mod</code> 自动决定。</li></ul></li><li><strong>GOPROXY</strong>：<ul><li>设置代理以加速依赖下载。</li><li>推荐：<code>https://goproxy.cn,direct</code>。</li><li><strong>direct</strong>：特殊指示符，当代理返回 404 等错误时，强制回源（如 GitHub）抓取。</li></ul></li><li><strong>GOSUMDB</strong>：<ul><li>校验和数据库，确保拉取的代码未被篡改。</li></ul></li><li><strong>GOPRIVATE</strong>：<ul><li>设置私有仓库路径（如公司 GitLab）。</li><li>设置后，匹配的路径将不经过代理和校验数据库。</li></ul></li></ol><h3 id="项目初始化与管理"><a href="#项目初始化与管理" class="headerlink" title="项目初始化与管理"></a>项目初始化与管理</h3><h4 id="初始化流程"><a href="#初始化流程" class="headerlink" title="初始化流程"></a>初始化流程</h4><ol><li>开启模块：<code>go env -w GO111MODULE=on</code></li><li>创建项目并进入：<code>mkdir project &amp;&amp; cd project</code></li><li>初始化模块：<code>go mod init &lt;模块路径&gt;</code> (如 <code>github.com/user/repo</code>)</li></ol><h4 id="go-mod-文件结构"><a href="#go-mod-文件结构" class="headerlink" title="go.mod 文件结构"></a>go.mod 文件结构</h4><ul><li><strong>module</strong>：定义当前模块的路径。</li><li><strong>go</strong>：标识初始化时的 Go 版本。</li><li><strong>require</strong>：列出项目依赖的具体模块及版本。</li><li><strong>&#x2F;&#x2F;indirect</strong>：间接依赖（当前项目未直接 import，但依赖的库引用了它）。</li></ul><h4 id="go-sum-文件的作用"><a href="#go-sum-文件的作用" class="headerlink" title="go.sum 文件的作用"></a>go.sum 文件的作用</h4><ul><li>详细罗列所有直接和间接依赖的 <strong>SHA-256 哈希值</strong>。</li><li><strong>h1 hash</strong>：包内所有文件的总哈希，用于检测代码是否被篡改。</li><li><strong>go.mod hash</strong>：仅对 <code>go.mod</code> 文件进行哈希。</li></ul><h3 id="版本修改与替换-replace"><a href="#版本修改与替换-replace" class="headerlink" title="版本修改与替换 (replace)"></a>版本修改与替换 (replace)</h3><h4 id="更新依赖"><a href="#更新依赖" class="headerlink" title="更新依赖"></a>更新依赖</h4><p>使用 <code>go get</code> 可以拉取或更新版本：</p><ul><li><code>go get &lt;path&gt;@v1.2.3</code>：指定特定版本。</li><li><code>go get &lt;path&gt;@latest</code>：拉取最新版本。</li></ul><h4 id="使用-replace-关键字"><a href="#使用-replace-关键字" class="headerlink" title="使用 replace 关键字"></a>使用 replace 关键字</h4><p>在某些特殊情况下（如无法访问某个库，或需要调试本地库），可以使用 <code>replace</code>：</p><ul><li><strong>语法</strong>：<code>go mod edit -replace=旧模块@版本=新模块/路径@版本</code></li><li><strong>场景</strong>：<ol><li>替换因网络问题无法下载的模块。</li><li>强制锁定某个具体的补丁版本。</li><li>将依赖指向本地磁盘的开发代码。</li></ol></li></ul><h2 id="第十二章-ACID、CAP、BASE-分布式理论推进"><a href="#第十二章-ACID、CAP、BASE-分布式理论推进" class="headerlink" title="第十二章 - ACID、CAP、BASE 分布式理论推进"></a>第十二章 - ACID、CAP、BASE 分布式理论推进</h2><h3 id="事务的基本概念"><a href="#事务的基本概念" class="headerlink" title="事务的基本概念"></a>事务的基本概念</h3><p>分布式研究的核心起点是<strong>事务（Transaction）</strong>。</p><ul><li><strong>定义</strong>：将一组操作纳入一个不可分割的执行单元。</li><li><strong>机制</strong>：<strong>“要么全做，要么全不做” (All or Nothing)</strong>。</li><li><strong>回滚</strong>：任一操作失败，整个单元的操作全部撤销，恢复到初始状态。</li></ul><h3 id="ACID-理论（本地事务的基石）"><a href="#ACID-理论（本地事务的基石）" class="headerlink" title="ACID 理论（本地事务的基石）"></a>ACID 理论（本地事务的基石）</h3><p>数据库事务正确执行的四个基本特性：</p><ol><li><strong>原子性 (Atomicity)</strong>：事务中的操作是不可分割的整体，要么全部成功，要么全部失败。</li><li><strong>一致性 (Consistency)</strong>：事务前后，数据库的完整性约束不被破坏（如转账前后总金额不变）。</li><li><strong>隔离性 (Isolation)</strong>：并发事务之间互不干扰，未提交的修改对其他事务不可见。</li><li><strong>持久性 (Durability)</strong>：一旦事务提交，对数据的修改是永久性的，即使系统故障也不会丢失。</li></ol><h3 id="CAP-理论（分布式系统的抉择）"><a href="#CAP-理论（分布式系统的抉择）" class="headerlink" title="CAP 理论（分布式系统的抉择）"></a>CAP 理论（分布式系统的抉择）</h3><p>在大规模分布式环境下，三个特性无法同时满足，只能<strong>三选二</strong>。</p><h4 id="三大特性定义"><a href="#三大特性定义" class="headerlink" title="三大特性定义"></a>三大特性定义</h4><ol><li><strong>一致性 (Consistency)</strong>：所有节点在同一时间看到相同的数据。写操作后，任何节点读取到的都是最新值。</li><li><strong>可用性 (Availability)</strong>：每个请求都能在合理时间内获得正常响应（不是错误或超时）。</li><li><strong>分区容错性 (Partition Tolerance)</strong>：当网络分区（节点间通信失败）发生时，系统仍能继续运行。</li></ol><h4 id="“三选二”的必然性"><a href="#“三选二”的必然性" class="headerlink" title="“三选二”的必然性"></a>“三选二”的必然性</h4><p>在分布式系统中，<strong>P（分区容错性）是必须保证的</strong>基础，因此设计者通常在 C 和 A 之间权衡：</p><ul><li><strong>CA (放弃 P)</strong>：不拆分分区。传统的单机关系型数据库。不是严格意义上的分布式系统。</li><li><strong>CP (放弃 A)</strong>：追求强一致性。网络故障时，为了保证数据一致，系统会拒绝服务或阻塞。<strong>典型应用：Redis、HBase、Zookeeper。</strong></li><li><strong>AP (放弃 C)</strong>：追求高可用性。网络故障时，节点使用本地旧数据提供服务。<strong>典型应用：12306 买票、淘宝订单、多数 NoSQL。</strong></li></ul><h3 id="分布式-BASE-理论"><a href="#分布式-BASE-理论" class="headerlink" title="分布式 BASE 理论"></a>分布式 BASE 理论</h3><p>BASE 是对 CAP 中一致性和可用性权衡的结果，是大型互联网分布式实践的总结。</p><h4 id="核心内容"><a href="#核心内容" class="headerlink" title="核心内容"></a>核心内容</h4><ol><li><strong>基本可用 (Basically Available)</strong>：在故障发生时，损失部分性能或功能。<ul><li><em>响应时间妥协</em>：由 0.5s 增加到 2s。</li><li><em>功能损失妥协</em>：高峰期引导至“降级页面”。</li></ul></li><li><strong>软状态 (Soft State)</strong>：允许数据存在中间状态（数据同步延迟），且不影响系统整体可用性。</li><li><strong>最终一致性 (Eventually Consistent)</strong>：经过一段时间后，所有副本数据最终都能达到一致状态。</li></ol><h4 id="ACID-vs-BASE"><a href="#ACID-vs-BASE" class="headerlink" title="ACID vs BASE"></a>ACID vs BASE</h4><ul><li><strong>ACID</strong>：追求<strong>强一致性</strong>模型，适合传统金融交易。</li><li><strong>BASE</strong>：通过牺牲强一致性获得<strong>高可用性</strong>，适合互联网高并发大规模系统。</li></ul><h3 id="综合案例：电商系统的-CAP-设计"><a href="#综合案例：电商系统的-CAP-设计" class="headerlink" title="综合案例：电商系统的 CAP 设计"></a>综合案例：电商系统的 CAP 设计</h3><p>电商系统不同模块对 CAP 的要求不同：</p><table><thead><tr><th align="left">模块</th><th align="left">CAP 选择</th><th align="left">原因</th></tr></thead><tbody><tr><td align="left"><strong>用户&#x2F;搜索&#x2F;收藏夹</strong></td><td align="left"><strong>AP</strong></td><td align="left">短时间数据不一致不影响核心体验，高可用更重要。</td></tr><tr><td align="left"><strong>订单&#x2F;扣减库存</strong></td><td align="left"><strong>CP &#x2F; CA</strong></td><td align="left">核心业务，必须保证数据准确，极端下可牺牲可用性。</td></tr><tr><td align="left"><strong>商品上下架</strong></td><td align="left"><strong>CP</strong></td><td align="left">保证库存管理和状态的同步准确。</td></tr><tr><td align="left"><strong>支付模块</strong></td><td align="left"><strong>C 必选</strong></td><td align="left">金钱相关必须强一致，AP 中 A 也很重要，通常由第三方保证。</td></tr></tbody></table><h3 id="小结-3"><a href="#小结-3" class="headerlink" title="小结"></a>小结</h3><ul><li><strong>分布式核心</strong>：在不可避免的网络分区（P）下，通过 BASE 理论牺牲强一致性（C），追求基本可用（BA）和最终一致性（E）。</li><li><strong>架构建议</strong>：不要盲目追求全系统强一致性，应根据业务场景（如电商不同模块）灵活配置不同的分布式策略。</li></ul>]]>
    </content>
    <id>https://cooooing.github.io/uncategorized/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E7%9A%84%E8%BF%9B%E9%98%B6%E7%9F%A5%E8%AF%86/</id>
    <link href="https://cooooing.github.io/uncategorized/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E7%9A%84%E8%BF%9B%E9%98%B6%E7%9F%A5%E8%AF%86/"/>
    <published>2026-01-08T14:27:01.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>这本书前四章篇幅超过三分之一<br>五到十二章共八章 和 十三到二十一共九章 大约各占三分之一<br>这章节内容量差的有点大</p>
<p>]]>
    </summary>
    <title>《深入理解Go语言》读书笔记 - Go 语言特性的进阶知识</title>
    <updated>2026-01-08T14:27:01.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="读书笔记" scheme="https://cooooing.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="《深入理解Go语言》" scheme="https://cooooing.github.io/tags/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B/"/>
    <category term="Linux" scheme="https://cooooing.github.io/tags/Linux/"/>
    <category term="I/O复用" scheme="https://cooooing.github.io/tags/I-O%E5%A4%8D%E7%94%A8/"/>
    <content>
      <![CDATA[<h2 id="第四章-深入理解-Linux-网络-I-O-复用并发模型"><a href="#第四章-深入理解-Linux-网络-I-O-复用并发模型" class="headerlink" title="第四章 深入理解 Linux 网络 I&#x2F;O 复用并发模型"></a>第四章 深入理解 Linux 网络 I&#x2F;O 复用并发模型</h2><h3 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h3><h4 id="流-Stream"><a href="#流-Stream" class="headerlink" title="流 (Stream)"></a>流 (Stream)</h4><p>在开发中,流通常具有以下三个特征:</p><ol><li>可以进行 I&#x2F;O 操作的<strong>内核对象</strong>。</li><li>数据传输的载体,如文件、管道、套接字(Socket)等。</li><li>数据的入口通过<strong>文件描述符 (fd)</strong> 来进行描述和识别。</li></ol><h4 id="I-O-操作"><a href="#I-O-操作" class="headerlink" title="I&#x2F;O 操作"></a>I&#x2F;O 操作</h4><p>所有对“流”的读写行为都称为 I&#x2F;O 操作。</p><ul><li><strong>写阻塞</strong>:当传输媒介已满(如缓冲区满)时,继续写入会导致阻塞。</li><li><strong>读阻塞</strong>:当传输媒介为空(如没有新数据)时,尝试读取会导致阻塞。</li></ul><h4 id="阻塞等待-Blocking"><a href="#阻塞等待-Blocking" class="headerlink" title="阻塞等待 (Blocking)"></a>阻塞等待 (Blocking)</h4><p><strong>在等待某个资源(如数据到达)时,程序执行逻辑完全暂停。</strong></p><h4 id="非阻塞忙轮询-Non-blocking-Busy-Polling"><a href="#非阻塞忙轮询-Non-blocking-Busy-Polling" class="headerlink" title="非阻塞忙轮询 (Non-blocking Busy Polling)"></a>非阻塞忙轮询 (Non-blocking Busy Polling)</h4><p><strong>不间断地询问资源是否就绪。</strong></p><h4 id="阻塞与非阻塞对比"><a href="#阻塞与非阻塞对比" class="headerlink" title="阻塞与非阻塞对比"></a>阻塞与非阻塞对比</h4><ol><li><strong>阻塞等待</strong>:<ul><li><strong>优点</strong>:不占用 CPU 时间片,节省系统资源(小 G 可以去睡觉)。</li><li><strong>缺点</strong>:无法同一时刻处理多个 I&#x2F;O 请求(同一时间只能等一个快递)。</li></ul></li><li><strong>非阻塞忙轮询</strong>:<ul><li><strong>优点</strong>:宏观上能同时监控多个 I&#x2F;O。</li><li><strong>缺点</strong>:极度浪费 CPU 资源和通信成本(反复打电话)。</li></ul></li></ol><h3 id="解决阻塞等待缺点的办法"><a href="#解决阻塞等待缺点的办法" class="headerlink" title="解决阻塞等待缺点的办法"></a>解决阻塞等待缺点的办法</h3><h4 id="阻塞死等待的缺点"><a href="#阻塞死等待的缺点" class="headerlink" title="阻塞死等待的缺点"></a>阻塞死等待的缺点</h4><p>主要矛盾在于:<strong>无法在同一时刻解决多个 I&#x2F;O 的读写请求</strong>。当处理一个请求时,其他请求会被拒绝或等待。</p><h4 id="办法-1-多线程-多进程"><a href="#办法-1-多线程-多进程" class="headerlink" title="办法 1:多线程 &#x2F; 多进程"></a>办法 1:多线程 &#x2F; 多进程</h4><ul><li><strong>策略</strong>:通过增加资源,为每个 I&#x2F;O 请求开启一个独立的线程或进程。</li><li><strong>局限性</strong>:<ul><li>资源消耗大(进程占内存高)。</li><li>系统线程&#x2F;进程数量有上限,无法应对超高并发。</li></ul></li></ul><h4 id="办法-2-非阻塞忙轮询-伪代码实现"><a href="#办法-2-非阻塞忙轮询-伪代码实现" class="headerlink" title="办法 2:非阻塞忙轮询(伪代码实现)"></a>办法 2:非阻塞忙轮询(伪代码实现)</h4><ul><li><strong>策略</strong>:通过 <code>while(true)</code> 循环不断遍历所有流。</li><li><strong>伪代码逻辑</strong>:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (stream : all_streams) &#123;</span><br><span class="line">        <span class="keyword">if</span> (stream has data) &#123;</span><br><span class="line">            process(stream);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><strong>缺点</strong>:即便没有任何流有数据,CPU 也会不停地执行循环判断,导致 <strong>CPU 占用率过高</strong>。</li></ul><h4 id="办法-3-select-I-O-多路复用"><a href="#办法-3-select-I-O-多路复用" class="headerlink" title="办法 3:select (I&#x2F;O 多路复用)"></a>办法 3:select (I&#x2F;O 多路复用)</h4><ul><li><strong>原理</strong>:引入一个代理(管理员 select)。select 会阻塞等待,直到监听的多个流中<strong>至少有一个</strong>变得可读写。</li><li><strong>伪代码逻辑</strong>:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">    select(all_streams); <span class="comment">// 阻塞点:直到有流就绪才返回</span></span><br><span class="line">    <span class="keyword">for</span> (stream : all_streams) &#123; <span class="comment">// 遍历全量流集合</span></span><br><span class="line">        <span class="keyword">if</span> (stream has data) &#123;</span><br><span class="line">            process(stream);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><strong>缺点</strong>:<ol><li><strong>全量返回</strong>:select 只告诉你有流就绪,但不告诉你是哪一个。开发者必须手动 <code>for</code> 循环<strong>遍历所有流</strong>来确认。</li><li><strong>数量限制</strong>:select 能够监听的 fd 数量通常较小且固定(如 1024)。</li></ol></li></ul><h4 id="办法-4-epoll-进阶版-I-O-多路复用"><a href="#办法-4-epoll-进阶版-I-O-多路复用" class="headerlink" title="办法 4:epoll (进阶版 I&#x2F;O 多路复用)"></a>办法 4:epoll (进阶版 I&#x2F;O 多路复用)</h4><ul><li><strong>原理</strong>:升级版的代理模式。它不仅通知有数据到达,还会<strong>明确告知哪些流</strong>是可以操作的。</li><li><strong>伪代码逻辑</strong>:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">    active_streams = epoll_wait(all_streams); <span class="comment">// 阻塞点</span></span><br><span class="line">    <span class="keyword">for</span> (stream : active_streams) &#123; <span class="comment">// 只遍历就绪的流</span></span><br><span class="line">        process(stream);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><strong>优点</strong>:<ol><li><strong>高效率</strong>:只返回就绪的流,减少了无效遍历。</li><li><strong>高性能</strong>:监听的 I&#x2F;O 数量上限由操作系统内存决定(非常大)。</li><li><strong>系统开销小</strong>:在大规模并发下,性能优势极其明显。</li></ol></li></ul><h3 id="epoll"><a href="#epoll" class="headerlink" title="epoll"></a>epoll</h3><p><code>epoll</code> 是 Linux 下高性能的 I&#x2F;O 多路复用技术,其核心优势在于<strong>只关心活跃连接</strong>,无需像 <code>select/poll</code> 那样遍历整个描述符集合。</p><h4 id="epoll-的三步开发流程与内核原理"><a href="#epoll-的三步开发流程与内核原理" class="headerlink" title="epoll 的三步开发流程与内核原理"></a>epoll 的三步开发流程与内核原理</h4><ol><li><strong>创建 epoll (<code>epoll_create</code>)</strong><ul><li><strong>接口</strong>:<code>int epoll_create(int size);</code></li><li><strong>内核动作</strong>:在内核中创建一棵<strong>红黑树 (Red-Black Tree)</strong>。</li><li><strong>作用</strong>:红黑树的根节点对应 <code>epfd</code>,用于高效管理(增删改)数以万计的文件描述符 (fd)。</li></ul></li><li><strong>控制 epoll (<code>epoll_ctl</code>)</strong><ul><li><strong>接口</strong>:<code>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);</code></li><li><strong>内核动作</strong>:向红黑树中添加、删除或修改 fd 及其对应的事件(如 <code>EPOLLIN</code> 读事件)。</li></ul></li><li><strong>等待 epoll (<code>epoll_wait</code>)</strong><ul><li><strong>接口</strong>:<code>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);</code></li><li><strong>机制</strong>:这是一个阻塞调用。当内核检测到红黑树中有 fd 触发了 I&#x2F;O 事件,会将其移入一个<strong>就绪链表</strong>,并抛给用户态。</li><li><strong>结果</strong>:开发者直接得到一个“活跃事件集合”,只需遍历这个集合即可处理,效率极高。</li></ul></li></ol><h4 id="epoll-的触发模式"><a href="#epoll-的触发模式" class="headerlink" title="epoll 的触发模式"></a>epoll 的触发模式</h4><h4 id="水平触发-Level-Triggered-LT-——-默认模式"><a href="#水平触发-Level-Triggered-LT-——-默认模式" class="headerlink" title="水平触发 (Level Triggered, LT) —— 默认模式"></a>水平触发 (Level Triggered, LT) —— 默认模式</h4><ul><li><strong>机制</strong>:只要内核缓冲区中有数据,<code>epoll_wait</code> 就会不断通知用户。</li><li><strong>优点</strong>:<strong>安全</strong>。即使一次没读完,下次还会提醒,不容易丢数据。</li><li><strong>缺点</strong>:若数据未及时处理,会产生大量重复的内核到用户态的切换,影响性能。</li></ul><h4 id="边缘触发-Edge-Triggered-ET-——-高性能模式"><a href="#边缘触发-Edge-Triggered-ET-——-高性能模式" class="headerlink" title="边缘触发 (Edge Triggered, ET) —— 高性能模式"></a>边缘触发 (Edge Triggered, ET) —— 高性能模式</h4><ul><li><strong>机制</strong>:仅在状态发生变化时(如数据从无到有)通知一次。</li><li><strong>优点</strong>:<strong>高性能</strong>。减少了 <code>epoll_wait</code> 的触发次数。</li><li><strong>缺点</strong>:<strong>风险高</strong>。开发者必须一次性将缓冲区数据读完(通常配合非阻塞 I&#x2F;O 使用循环读取),否则未读完的数据可能永远不会再通知。</li></ul><h3 id="Linux-常见网络-I-O-复用并发模型"><a href="#Linux-常见网络-I-O-复用并发模型" class="headerlink" title="Linux 常见网络 I&#x2F;O 复用并发模型"></a>Linux 常见网络 I&#x2F;O 复用并发模型</h3><h3 id="模型-1-单线程-Accept-无-I-O-复用"><a href="#模型-1-单线程-Accept-无-I-O-复用" class="headerlink" title="模型 1:单线程 Accept(无 I&#x2F;O 复用)"></a>模型 1:单线程 Accept(无 I&#x2F;O 复用)</h3><p>这是最原始的服务器模型,所有操作都在一个线程内线性执行。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>初始化</strong>:主线程创建 <code>ListenFd</code>,绑定(Bind)IP和端口,调用 <code>Listen</code> 进入监听状态。</li><li><strong>阻塞监听</strong>:主线程调用 <code>accept()</code> 函数,线程进入阻塞状态,死等客户端连接。</li><li><strong>建立连接</strong>:客户端 Connect 请求到达,<code>accept()</code> 返回一个新的 <code>ConnFd</code>。</li><li><strong>业务处理</strong>:主线程继续执行 <code>read()</code> 读取数据,进行逻辑计算,最后 <code>write()</code> 返回结果。</li><li><strong>循环</strong>:业务处理完后,主线程回到步骤 2,处理下一个请求。</li></ol></li><li><strong>致命伤</strong>:<strong>串行化</strong>。如果 Client 1 正在处理业务,Client 2 的连接请求会被挂起,无法响应。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B1:%E5%8D%95%E7%BA%BF%E7%A8%8BAccept.png"                        alt="网络并发模型1:单线程Accept.png"                 ></p><h3 id="模型-2-单线程-Accept-多线程读写-无-I-O-复用"><a href="#模型-2-单线程-Accept-多线程读写-无-I-O-复用" class="headerlink" title="模型 2:单线程 Accept + 多线程读写(无 I&#x2F;O 复用)"></a>模型 2:单线程 Accept + 多线程读写(无 I&#x2F;O 复用)</h3><p>为了解决模型 1 的串行问题,引入了多线程。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>初始化</strong>:主线程创建 <code>ListenFd</code> 并监听。</li><li><strong>主线程阻塞</strong>:主线程调用 <code>accept()</code> 等待连接。</li><li><strong>分发任务</strong>:一旦 <code>accept()</code> 返回 <code>ConnFd</code>,主线程立即 <code>pthread_create</code> <strong>创建一个新线程</strong>(或从池中取),并将 <code>ConnFd</code> 交给新线程。</li><li><strong>并发执行</strong>:<ul><li><strong>子线程</strong>:负责该 <code>ConnFd</code> 的全部生命周期(Read -&gt; 业务 -&gt; Write -&gt; Close)。</li><li><strong>主线程</strong>:立即回到步骤 2,继续等待下一个客户端连接。</li></ul></li></ol></li><li><strong>优缺点</strong>:实现了并发,但 <strong>1:1 的线程开销</strong>在面对成千上万连接时,会因内存耗尽和 CPU 上下文切换频繁而崩溃。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B2:%E5%8D%95%E7%BA%BF%E7%A8%8BAccept+%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%BB%E5%86%991.png"                        alt="网络并发模型2:单线程Accept+多线程读写1.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B2:%E5%8D%95%E7%BA%BF%E7%A8%8BAccept+%E5%A4%9A%E7%BA%BF%E7%A8%8B%E8%AF%BB%E5%86%992.png"                        alt="网络并发模型2:单线程Accept+多线程读写2.png"                 ></p><h3 id="模型-3-单线程多路-I-O-复用"><a href="#模型-3-单线程多路-I-O-复用" class="headerlink" title="模型 3:单线程多路 I&#x2F;O 复用"></a>模型 3:单线程多路 I&#x2F;O 复用</h3><p>不使用多线程,而是利用内核提供的 <code>select/epoll</code> 同时监控多个连接。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>初始化</strong>:创建 <code>ListenFd</code>,并将其加入 <code>epoll</code> 监听集合。</li><li><strong>事件阻塞</strong>:调用 <code>epoll_wait()</code>,线程进入阻塞,等待内核通知。</li><li><strong>响应分发</strong>:当 <code>epoll_wait</code> 返回活跃 fd 集合时:<ul><li><strong>如果是 ListenFd</strong>:执行 <code>accept()</code>,并将新生成的 <code>ConnFd</code> 也加入 <code>epoll</code> 集合。</li><li><strong>如果是 ConnFd</strong>:在当前线程执行 Read -&gt; 业务处理 -&gt; Write。</li></ul></li><li><strong>循环</strong>:处理完当前所有活跃 fd 后,回到步骤 2。</li></ol></li><li><strong>局限</strong>:虽然能监听万级连接,但<strong>业务逻辑仍在主线程</strong>。如果某个业务逻辑(如复杂计算)太慢,会导致所有其他连接的 I&#x2F;O 被阻塞。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B3:%E5%8D%95%E7%BA%BF%E7%A8%8BAccept%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A81.png"                        alt="网络并发模型3:单线程Accept多路IO复用1.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B3:%E5%8D%95%E7%BA%BF%E7%A8%8BAccept%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A82.png"                        alt="网络并发模型3:单线程Accept多路IO复用2.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B3:%E5%8D%95%E7%BA%BF%E7%A8%8BAccept%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A83.png"                        alt="网络并发模型3:单线程Accept多路IO复用3.png"                 ></p><h3 id="模型-4-单线程多路-I-O-复用-业务工作池"><a href="#模型-4-单线程多路-I-O-复用-业务工作池" class="headerlink" title="模型 4:单线程多路 I&#x2F;O 复用 + 业务工作池"></a>模型 4:单线程多路 I&#x2F;O 复用 + 业务工作池</h3><p>将模型 3 的业务处理部分剥离出来,交给专门的线程池。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>初始化</strong>:主线程创建 <code>epoll</code>,并启动一个 <strong>Worker Pool(工作线程池)</strong>。</li><li><strong>I&#x2F;O 监听</strong>:主线程通过 <code>epoll_wait</code> 监控所有 fd。</li><li><strong>读写分发</strong>:<ul><li><strong>连接事件</strong>:主线程 <code>accept</code>。</li><li><strong>读事件</strong>:主线程 <code>read</code> 完整的数据包,封装成一个“任务(Task)”,丢进 <strong>Worker Pool</strong> 的任务队列。</li></ul></li><li><strong>并行计算</strong>:Worker 线程池中的某个空闲线程取出 Task,执行业务逻辑,计算出结果。</li><li><strong>回写数据</strong>:Worker 线程处理完后,通知主线程(或直接)通过 <code>ConnFd</code> 将结果 <code>write</code> 给客户端。</li></ol></li><li><strong>进步点</strong>:业务逻辑不再阻塞 I&#x2F;O 监控。但<strong>所有读写操作</strong>仍由主线程一人承担,读写带宽存在瓶颈。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B4:%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A8+%E4%B8%9A%E5%8A%A1%E5%B7%A5%E4%BD%9C%E6%B1%A01.png"                        alt="网络并发模型4:单线程多路IO复用+业务工作池1.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B4:%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A8+%E4%B8%9A%E5%8A%A1%E5%B7%A5%E4%BD%9C%E6%B1%A02.png"                        alt="网络并发模型4:单线程多路IO复用+业务工作池2.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B4:%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A8+%E4%B8%9A%E5%8A%A1%E5%B7%A5%E4%BD%9C%E6%B1%A03.png"                        alt="网络并发模型4:单线程多路IO复用+业务工作池3.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B4:%E5%8D%95%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%B7%AFIO%E5%A4%8D%E7%94%A8+%E4%B8%9A%E5%8A%A1%E5%B7%A5%E4%BD%9C%E6%B1%A04.png"                        alt="网络并发模型4:单线程多路IO复用+业务工作池4.png"                 ></p><h3 id="模型-5-单线程-I-O-复用-多线程-I-O-复用-连接线程池-——-主流方案"><a href="#模型-5-单线程-I-O-复用-多线程-I-O-复用-连接线程池-——-主流方案" class="headerlink" title="模型 5:单线程 I&#x2F;O 复用 + 多线程 I&#x2F;O 复用(连接线程池)—— 主流方案"></a>模型 5:单线程 I&#x2F;O 复用 + 多线程 I&#x2F;O 复用(连接线程池)—— 主流方案</h3><p>这就是著名的 <strong>Reactor 多线程模型</strong>(如 Netty 的 Boss&#x2F;Worker 机制)。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>初始化</strong>:主线程(MainReactor)建立,同时开启 <strong>线程池(SubReactors)</strong>,每个子线程都有自己的 <code>epoll</code> 实例。</li><li><strong>主线程监听</strong>:主线程只负责监听 <code>ListenFd</code>,执行 <code>accept</code>。</li><li><strong>连接分发</strong>:主线程将 <code>accept</code> 得到的 <code>ConnFd</code> 轮询分发给某个子线程(SubThread)。</li><li><strong>子线程监听</strong>:子线程将分配到的 <code>ConnFd</code> 加入自己的 <code>epoll</code> 集合,负责该连接后续的<strong>所有</strong>读、写、业务。</li></ol></li><li><strong>核心优势</strong>:读写并行通道从 1 扩展到了 N(线程数),极大地提升了吞吐量。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B5:%E5%8D%95%E7%BA%BF%E7%A8%8BIO%E5%A4%8D%E7%94%A8+%E5%A4%9A%E7%BA%BF%E7%A8%8BIO%E5%A4%8D%E7%94%A81.png"                        alt="网络并发模型5:单线程IO复用+多线程IO复用1.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B5:%E5%8D%95%E7%BA%BF%E7%A8%8BIO%E5%A4%8D%E7%94%A8+%E5%A4%9A%E7%BA%BF%E7%A8%8BIO%E5%A4%8D%E7%94%A82.png"                        alt="网络并发模型5:单线程IO复用+多线程IO复用2.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B5:%E5%8D%95%E7%BA%BF%E7%A8%8BIO%E5%A4%8D%E7%94%A8+%E5%A4%9A%E7%BA%BF%E7%A8%8BIO%E5%A4%8D%E7%94%A83.png"                        alt="网络并发模型5:单线程IO复用+多线程IO复用3.png"                 ></p><h3 id="模型-5-进程版-多进程-I-O-复用-——-稳定方案"><a href="#模型-5-进程版-多进程-I-O-复用-——-稳定方案" class="headerlink" title="模型 5(进程版):多进程 I&#x2F;O 复用 —— 稳定方案"></a>模型 5(进程版):多进程 I&#x2F;O 复用 —— 稳定方案</h3><p>典型代表:<strong>Nginx</strong>。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>Master 初始化</strong>:主进程创建 <code>ListenFd</code>,但不 <code>accept</code>。</li><li><strong>Fork 进程池</strong>:创建多个 Worker 进程。</li><li><strong>竞争 Accept</strong>:<ul><li>所有 Worker 进程通过 <strong>Accept_Mutex(互斥锁)</strong> 或内核的 <code>SO_REUSEPORT</code> 竞争处理新连接。</li><li>胜出的 Worker 执行 <code>accept</code> 得到 <code>ConnFd</code>。</li></ul></li><li><strong>独立监听</strong>:该 Worker 进程将 <code>ConnFd</code> 加入自己的 <code>epoll</code> 集合,独立处理读写业务。</li></ol></li><li><strong>特点</strong>:进程间内存隔离,稳定性极强,一个 Worker 挂了不影响整体。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B6:1.png"                        alt="网络并发模型6:1.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B6:2.png"                        alt="网络并发模型6:2.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/%E7%BD%91%E7%BB%9C%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B6:3.png"                        alt="网络并发模型6:3.png"                 ></p><h3 id="模型-6-单线程-I-O-复用-多线程-I-O-复用-临时多线程"><a href="#模型-6-单线程-I-O-复用-多线程-I-O-复用-临时多线程" class="headerlink" title="模型 6:单线程 I&#x2F;O 复用 + 多线程 I&#x2F;O 复用 + 临时多线程"></a>模型 6:单线程 I&#x2F;O 复用 + 多线程 I&#x2F;O 复用 + 临时多线程</h3><p>这是模型 5 的极端增强版。</p><ul><li><strong>详细步骤:</strong><ol><li><strong>前三步同模型 5</strong>(主线程分发给子线程)。</li><li><strong>动态触发</strong>:子线程在 <code>epoll_wait</code> 收到某个 <code>ConnFd</code> 的读写事件。</li><li><strong>即时开辟</strong>:子线程<strong>不亲自读写</strong>,而是立即开启(或调度)一个<strong>临时线程</strong>来专门处理这次读写。</li><li><strong>任务结束</strong>:读写和业务完成后,该临时线程销毁或回池。</li></ol></li><li><strong>评价</strong>:这是理论上的“最大并发”,但在目前的硬件下,<strong>频繁创建&#x2F;调度线程的开销</strong>远大于其带来的并发收益,容易导致系统负载过高,属于“过度设计”。</li></ul><h3 id="总结对比表"><a href="#总结对比表" class="headerlink" title="总结对比表"></a>总结对比表</h3><table><thead><tr><th align="left">模型</th><th align="left">并发点</th><th align="left">核心角色</th><th align="left">适用场景</th></tr></thead><tbody><tr><td align="left"><strong>模型 1</strong></td><td align="left">无</td><td align="left">主线程</td><td align="left">教学示例</td></tr><tr><td align="left"><strong>模型 2</strong></td><td align="left">业务+读写</td><td align="left">每连接一线程</td><td align="left">低频长连接</td></tr><tr><td align="left"><strong>模型 3</strong></td><td align="left">I&#x2F;O 监听</td><td align="left">单 epoll</td><td align="left">简单请求(如 Redis)</td></tr><tr><td align="left"><strong>模型 4</strong></td><td align="left">业务逻辑</td><td align="left">epoll + Worker池</td><td align="left">逻辑复杂的计算型服务</td></tr><tr><td align="left"><strong>模型 5</strong></td><td align="left"><strong>I&#x2F;O 读写 + 业务</strong></td><td align="left"><strong>多 epoll (Reactor)</strong></td><td align="left"><strong>主流高性能服务器</strong></td></tr><tr><td align="left"><strong>模型 6</strong></td><td align="left">全流程极致并行</td><td align="left">动态线程</td><td align="left">极高性能的特殊硬件环境</td></tr></tbody></table>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/</id>
    <link href="https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Linux%E7%BD%91%E7%BB%9CIO%E5%A4%8D%E7%94%A8%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B/"/>
    <published>2026-01-08T12:22:26.000Z</published>
    <summary>
      <![CDATA[<h2 id="第四章-深入理解-Linux-网络-I-O-复用并发模型"><a href="#第四章-深入理解-Linux-网络-I-O-复用并发模型" class="headerlink" title="第四章 深入理解 Linux 网络 I&#x2F;O 复用并发模型"><]]>
    </summary>
    <title>《深入理解Go语言》读书笔记 - 深入理解 Linux 网络 I/O 复用并发模型</title>
    <updated>2026-01-08T12:22:26.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="读书笔记" scheme="https://cooooing.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="《深入理解Go语言》" scheme="https://cooooing.github.io/tags/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B/"/>
    <category term="内存管理" scheme="https://cooooing.github.io/tags/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"/>
    <content>
      <![CDATA[<h2 id="第三章-Go-语言内存管理洗髓经"><a href="#第三章-Go-语言内存管理洗髓经" class="headerlink" title="第三章 Go 语言内存管理洗髓经"></a>第三章 Go 语言内存管理洗髓经</h2><h3 id="内存为什么需要管理"><a href="#内存为什么需要管理" class="headerlink" title="内存为什么需要管理"></a>内存为什么需要管理</h3><p>当存储的东⻄越来越多,也就发现物理内存的容量依然不够用,提高对物理内存的利用率和合理地分配内存,管理就变得非常重要了。</p><ol><li>操作系统会对内存进行非常详细的管理。</li><li>基于操作系统的基础上,不同语言的内存管理机制也应运而生,有一些语言并没有提供自动的内存管理模式,有的语言却提供了自身程序的内存管理模式。</li></ol><p>为了降低内存管理的难度,像 C、C++ 这样的编程语言会完全将分配和回收内存的权限交给开发者,而Rust则通过生命周期限定开发者对非法权限内存的访问并以此来自动回收,因而并没有提供自动管理的一套机制。<br>但是像 Go、Java、Python 这类为了完全让开发者关注代码逻辑本身, 语言层提供了一套管理模式。</p><p>在理解 Go 语言层内存管理之前,应该先了解操作系统针对物理内存提供了哪些管理方式。</p><h3 id="操作系统是如何管理内存的"><a href="#操作系统是如何管理内存的" class="headerlink" title="操作系统是如何管理内存的"></a>操作系统是如何管理内存的</h3><p>对计算机来讲内存真正的载体是物理内存条,这个是实打实的物理硬件容量,所以在操作系统中定义的这部分容量叫物理内存。</p><p><strong>物理内存的布局实际上就是一个内存大数组</strong></p><p>每个元素都会对应一个地址,称为物理内存地址。CPU 在运算的过程中,如果需要从内存中取1字节的数据,就需要基于这个数据的物理内存地址去运算,而且物理内存的地址是连续的,可以根据一个基准地址进行偏移来取得相应的连续内存数据。<br>一个操作系统是不可能只运行一个程序的,这个大数组物理内存势必要被多个程序分成多份,供每个程序使用,但是程序是活的,一个程序可能一会需要 1MB 的内存,一会又需要 1GB 的内存。<br>操作系统只能取这个程序允许的最大内存极限来将内存分配给这个进程,但这样会导致每个进程都会多要去一部分内存,而这些多要的内存却大概率不会被使用。<br>当 N 个程序同时使用同一块内存时,产生读写的冲突也在所难免。这样就会导致这些昂贵的 物理内存条,几乎运行不了几个程序,内存的利用率也就提高不上来。</p><p>这就引出了操作系统的内存管理方式,操作系统提供了虚拟内存来解决这件事。</p><h4 id="虚拟内存"><a href="#虚拟内存" class="headerlink" title="虚拟内存"></a>虚拟内存</h4><p><strong>虚拟内存地址是基于物理内存地址之上凭空而造的一个新的逻辑地址,而操作系统暴露给用户进程的只是虚拟内存地址,操作系统内部会对虚拟内存地址和真实的物理内存地址建立映射关系,来管理地址的分配,从而使物理内存的利用率提高。</strong></p><p>这样用户程序(进程)只能使用虚拟的内存地址获取数据,系统会将这个虚拟地址翻译成实际的物理地址。<br>这里每个程序统一使用一套连续虚拟地址,例如0x00000000~0xffffffff。从程序的⻆度来看,它觉得自己独享了一整块内存,并且不用考虑访问冲突的问题。系统会将虚拟地址翻译成物理地址,从内存上加载数据。</p><p>但如果仅仅把虚拟内存直接理解为地址的映射关系,那就低估虚拟内存的作用了。</p><p>虚拟内存的目的是解决以下几件事:</p><ol><li><strong>物理内存无法被最大化利用</strong>。此外还实现了“读时共享,写时复制”的机制,“写时复制”(Copy-on-Write)多见于 fork() 子进程或加载动态链接库时。它确实让多个虚拟地址指向同一物理地址,只有在数据被修改时才分配新物理页,极大节省了空间。</li><li>程序逻辑内存空间使用独立。<strong>进程隔离</strong>。每个进程都以为自己拥有连续的地址空间,互不干扰,提高了系统的安全性。</li><li>内存不够,继续虚拟磁盘空间。<strong>Swap(交换分区)</strong>。虚拟内存将暂时不用的数据置换到磁盘上,让有限的物理内存能运行更大的程序。</li></ol><h4 id="MMU-内存管理单元"><a href="#MMU-内存管理单元" class="headerlink" title="MMU 内存管理单元"></a>MMU 内存管理单元</h4><p>虚拟地址是如何映射到物理内存地址上的呢?<br>如果采用简单的线性固定映射,物理内存的碎片化将导致无法为每个进程分配连续的地址空间。<br>为了解决这个问题,操作系统引入了<strong>页表映射机制</strong>,并由硬件层面的 <strong>MMU(Memory Management Unit,内存管理单元)</strong> 负责具体执行。<br>MMU 集成在 CPU 内部,其核心逻辑如下:</p><ul><li><strong>地址翻译:</strong> 当 CPU 尝试访问某个虚拟地址时,MMU 会自动截获该地址,并根据操作系统的 <strong>页表(Page Table)</strong> 将其翻译成真实的物理地址。</li><li><strong>硬件加速:</strong> MMU 内部拥有高速缓存 <strong>TLB(快表)</strong>,能够极大地降低地址转换的延迟。</li><li><strong>权限检查:</strong> 在映射的同时,MMU 还会检查进程是否有权读、写或执行该段内存,从而保障了系统的安全性。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MMU%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E5%8D%95%E5%85%83.png"                        alt="MMU内存管理单元.png"                 ></p><p>这种设计使得进程可以拥有独立且连续的虚拟空间,而底层的物理内存则可以被离散、动态地高效利用。</p><h4 id="虚拟内存本身怎么存放"><a href="#虚拟内存本身怎么存放" class="headerlink" title="虚拟内存本身怎么存放"></a>虚拟内存本身怎么存放</h4><p>虚拟内存本身是通过一个叫⻚表(Page Table)的东⻄实现的。</p><ol><li>⻚<br>⻚是操作系统中用来描述内存大小的一个单位名称。一个⻚的含义是大小为 4KB(1024×4&#x3D;4096字节)的内存空间。操作系统对虚拟内存空间是按照这个单位来管理的。</li><li>⻚表<br>⻚表实际上就是⻚的集合,即基于⻚的一个数组。⻚只表示内存的大小,而⻚表条目(PTE)才是⻚表数组中的一个元素。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E9%A1%B5%E3%80%81%E9%A1%B5%E8%A1%A8%E3%80%81PTE%E7%9B%B4%E6%8E%A5%E7%9A%84%E5%85%B3%E7%B3%BB.png"                        alt="页、页表、PTE直接的关系.png"                 ></p><p>虚拟内存的实现方式,大多数是通过⻚表实现的,实则是一个数组。<br>操作系统虚拟内存空间被分成一⻚一⻚来管理,每⻚的大小为 4KB(当然这是可以配置的,不同操作系统不一样)。<br><strong>磁盘和主内存之间的置换也是以⻚为单位来操作的。4KB 算是通过实践折中出来的通用值,太小了会出现频繁置换,太大了又会浪费内存。</strong></p><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/PTE%E5%86%85%E9%83%A8%E6%9E%84%E9%80%A0.png"                        alt="PTE内部构造.png"                 ></p><p>⻚是一次读取的内存单元,但是真正到虚拟内存寻址的是PTE,也就是⻚表中的一个元素。<br>可以看出每个 PTE 是由一个有效位和一个物理⻚号或者磁盘地址组成,有效位表示当前虚拟⻚是否已经被缓存在主内存中(或者CPU的高速缓存Cache中)。</p><p>虚拟⻚为何会有是否已经被缓存在主内存中一说?<br>虚拟⻚表(简称⻚表)虽然作为虚拟内存与物理内存的映射关系,但是本身也需要存放在某个位置上,所以自身也占用一定内存,所以⻚表本身也被操作系统放在物理内存的指定位置。<br>CPU 把虚拟地址给 MMU,MMU 去物理内存中查询⻚表,得到实际的物理地址。当然 MMU 不会每次都去查询,它自己也有一份缓存,叫作 快表 Translation Lookaside Buffer(TLB),是为了加速地址翻译。</p><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/CPU%E3%80%81MMU%E3%80%81TLB%E4%BA%A4%E4%BA%92%E5%85%B3%E7%B3%BB.png"                        alt="CPU、MMU、TLB交互关系.png"                 ></p><p>内存访问完整流程:当 CPU 需要读取一个虚拟地址的数据时,遵循以下逻辑路径:</p><ol><li><strong>请求阶段</strong>:CPU 发出虚拟地址请求给 <strong>MMU</strong>。</li><li><strong>一级缓存查找 (TLB)</strong>:MMU 首先在 <strong>TLB</strong> 中查找。<ul><li><strong>命中 (Hit)</strong>:直接获取物理地址,返回给 CPU。</li><li><strong>缺失 (Miss)</strong>:进入下一步,查询主存页表。</li></ul></li><li><strong>主存查找 (Page Table)</strong>:MMU 访问物理内存中的<strong>页表</strong>。<ul><li><strong>有效位 &#x3D; 1</strong>:查询成功,获取物理地址,并更新到 TLB 中备用。</li><li><strong>有效位 &#x3D; 0</strong>:触发 <strong>缺页异常 (Page Fault)</strong>,此时操作系统介入,从磁盘加载数据到内存。</li></ul></li></ol><p>根据 PTE 的状态,虚拟内存空间中的页可以归纳为以下三类:</p><table><thead><tr><th>状态</th><th>有效位</th><th>描述</th></tr></thead><tbody><tr><td><strong>未分配 (Unallocated)</strong></td><td>0</td><td>进程未申请该段地址,不占用内存,也不占用磁盘。</td></tr><tr><td><strong>已分配未缓存 (Uncached)</strong></td><td>0</td><td>进程已申请,数据在磁盘上,但尚未加载到物理内存。</td></tr><tr><td><strong>已缓存 (Cached)</strong></td><td>1</td><td>数据已在物理内存中,虚拟地址已映射到具体的物理页。</td></tr></tbody></table><h4 id="CPU-内存访问过程"><a href="#CPU-内存访问过程" class="headerlink" title="CPU 内存访问过程"></a>CPU 内存访问过程</h4><p>在访问开始前,CPU 生成的<strong>虚拟地址 (VA)</strong> 被拆分为两个部分:</p><ul><li><strong>VPN (Virtual Page Number)</strong>:虚拟页号,用于定位页表中的条目(索引)。</li><li><strong>VPO (Virtual Page Offset)</strong>:虚拟页偏移量,表示数据在页内的具体位置。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/%E4%B8%80%E6%AC%A1CPU%E8%AE%BF%E9%97%AE%E5%86%85%E5%AD%98%E8%AF%A6%E7%BB%86%E6%B5%81%E7%A8%8B.png"                        alt="一次CPU访问内存详细流程.png"                 ></p><p><strong>正常访问流程(命中路径:Steps 1-9)</strong></p><p>这是最快的一条路径,完全由硬件(CPU, MMU, TLB)完成:</p><ol><li><strong>指令发出</strong>:CPU 寄存器加载指令,生成虚拟地址。</li><li><strong>TLB 快速查找</strong>:MMU 提取 VPN,首先在 <strong>TLB (快表)</strong> 中查询对应的 <strong>PTE (页表项)</strong>。</li><li><strong>地址翻译</strong>:<ul><li>MMU 获取 PTE 中的 <strong>PPN (Physical Page Number,物理页号)</strong>。</li><li><strong>关键组合逻辑</strong>:物理地址 (直接串联,不需要加法运算)。</li></ul></li><li><strong>内存访问</strong>:CPU 通过生成的物理地址 (PA) 直接访问主存并获取数据。</li></ol><p><strong>异常处理流程(缺页路径:Steps 10-14)</strong></p><p>当 <strong>PTE 有效位为 0</strong> 时,说明数据不在内存中,触发“缺页异常”:</p><ul><li><strong>触发异常</strong>:MMU 发出异常信号,CPU 挂起当前进程,控制权转交给操作系统的<strong>缺页处理程序</strong>。</li><li><strong>调入页面</strong>:<ul><li><strong>选择牺牲页</strong>:如果内存已满,根据算法(如 LRU)选择一个物理页换出到磁盘(若有修改需写回)。</li><li><strong>载入数据</strong>:将缺失的数据从磁盘读入选定的物理内存页。</li></ul></li><li><strong>更新状态</strong>:更新页表中的 PTE,写入新的 PPN,并将<strong>有效位设为 1</strong>。</li><li><strong>指令重试</strong>:处理程序返回,CPU <strong>重新执行</strong>刚才出错的指令。由于此时有效位已为 1,将走上述“正常访问流程”并最终命中。</li></ul><p>性能核心指标:命中率 (Hit Rate),内存访问的效率极大程度上取决于命中率。</p><table><thead><tr><th>场景</th><th>耗时级别</th><th>影响因素</th></tr></thead><tbody><tr><td><strong>TLB 命中</strong></td><td>纳秒 (ns)</td><td>程序局部性(数据的连续性)</td></tr><tr><td><strong>主存页表命中</strong></td><td>几十纳秒</td><td>内存访问延迟</td></tr><tr><td><strong>缺页异常</strong></td><td><strong>毫秒 (ms)</strong></td><td>磁盘 I&#x2F;O 速度、物理内存容量</td></tr></tbody></table><blockquote><p>缺页处理涉及磁盘访问,其速度比内存慢 <strong>10,000 倍</strong>以上。如果物理内存不足,系统会频繁在内存与磁盘间交换数据,这种现象称为 <strong>“内存颠簸” (Thrashing)</strong>,会导致程序性能断崖式下跌。</p></blockquote><h4 id="内存的局部性"><a href="#内存的局部性" class="headerlink" title="内存的局部性"></a>内存的局部性</h4><p>什么是局部性?</p><p>局部性是指程序在执行时,往往倾向于引用最近引用过的数据,或者引用与最近引用过的数据在<strong>空间地址上相邻</strong>的数据。</p><ul><li><strong>时间局部性 (Temporal Locality)</strong>:如果一个内存位置被引用,那么在不久的将来它很可能再次被引用(例如:循环中的变量 <code>i</code>)。</li><li><strong>空间局部性 (Spatial Locality)</strong>:如果一个内存位置被引用,那么硬件往往预判其附近的内存位置也会被引用(例如:数组的顺序遍历)。</li></ul><p>局部性与系统性能的关系</p><p>现代计算机体系结构利用局部性特征设计了多级缓存(Cache)和缓冲层:</p><ul><li><strong>硬件层面</strong>:L1&#x2F;L2&#x2F;L3 缓存、TLB(快表)。</li><li><strong>系统层面</strong>:虚拟内存的分页机制、预读机制。</li></ul><blockquote><p>缓存的有效性完全依赖于程序的局部性。局部性越好,缓存命中率越高,程序运行越快;反之则会导致频繁的缺页或缓存失效。<br>所以在设计程序或者业务的时候应该多考虑增强程序局部性的特征,这样的程序执行起来会更快。</p></blockquote><hr><h4 id="实验分析-步长-Step-对性能的影响"><a href="#实验分析-步长-Step-对性能的影响" class="headerlink" title="实验分析:步长 (Step) 对性能的影响"></a>实验分析:步长 (Step) 对性能的影响</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;testing&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CreateSource</span><span class="params">(<span class="built_in">len</span> <span class="type">int</span>)</span></span> []<span class="type">int</span> &#123;</span><br><span class="line">nums := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">0</span>, <span class="built_in">len</span>)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="built_in">len</span>; i++ &#123;</span><br><span class="line">nums = <span class="built_in">append</span>(nums, i)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> nums</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 遍历数组,将每个值都设置为4,但使用 step 规定每次遍历的跨度</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Loop1</span><span class="params">(nums []<span class="type">int</span>, step <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">l := <span class="built_in">len</span>(nums)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; step; i++ &#123;</span><br><span class="line"><span class="keyword">for</span> j := i; j &lt; l; j += step &#123;</span><br><span class="line">nums[j] = <span class="number">4</span> <span class="comment">// 访问内存 并写入值</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Loop1 step 取1时的等价代码</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Loop2</span><span class="params">(nums []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">l := <span class="built_in">len</span>(nums)</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; l; i++ &#123;</span><br><span class="line">nums[i] = <span class="number">4</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// step 跨度越小,则表示访问 nums 相邻内存的局部性越好,step 越大则相反。</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkLoopStep1</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">source := CreateSource(<span class="number">10000</span>)</span><br><span class="line">b.ResetTimer()</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">Loop1(source, <span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkLoopStep2</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">source := CreateSource(<span class="number">10000</span>)</span><br><span class="line">b.ResetTimer()</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">Loop1(source, <span class="number">2</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkLoopStep4</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">source := CreateSource(<span class="number">10000</span>)</span><br><span class="line">b.ResetTimer()</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">Loop1(source, <span class="number">4</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkLoopStep8</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">source := CreateSource(<span class="number">10000</span>)</span><br><span class="line">b.ResetTimer()</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">Loop1(source, <span class="number">8</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">BenchmarkLoopStep16</span><span class="params">(b *testing.B)</span></span> &#123;</span><br><span class="line">source := CreateSource(<span class="number">10000</span>)</span><br><span class="line">b.ResetTimer()</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; b.N; i++ &#123;</span><br><span class="line">Loop1(source, <span class="number">16</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">goos: windows</span><br><span class="line">goarch: amd64</span><br><span class="line">pkg: script/test</span><br><span class="line">BenchmarkLoopStep1</span><br><span class="line">BenchmarkLoopStep1-20       831439      1291 ns/op</span><br><span class="line">BenchmarkLoopStep1-20       972572      1225 ns/op</span><br><span class="line">BenchmarkLoopStep1-20       974499      1227 ns/op</span><br><span class="line">BenchmarkLoopStep2</span><br><span class="line">BenchmarkLoopStep2-20       557046      2130 ns/op</span><br><span class="line">BenchmarkLoopStep2-20       555843      2160 ns/op</span><br><span class="line">BenchmarkLoopStep2-20       572308      2096 ns/op</span><br><span class="line">BenchmarkLoopStep4</span><br><span class="line">BenchmarkLoopStep4-20       309943      4200 ns/op</span><br><span class="line">BenchmarkLoopStep4-20       293374      4277 ns/op</span><br><span class="line">BenchmarkLoopStep4-20       301840      4022 ns/op</span><br><span class="line">BenchmarkLoopStep8</span><br><span class="line">BenchmarkLoopStep8-20       148774      7666 ns/op</span><br><span class="line">BenchmarkLoopStep8-20       156615      7872 ns/op</span><br><span class="line">BenchmarkLoopStep8-20       155821      7639 ns/op</span><br><span class="line">BenchmarkLoopStep16</span><br><span class="line">BenchmarkLoopStep16-20      118401     10422 ns/op</span><br><span class="line">BenchmarkLoopStep16-20      110496     10108 ns/op</span><br><span class="line">BenchmarkLoopStep16-20      121336     10222 ns/op</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure><ol><li>BenchmarkLoopStep1-20 中的 20 表示 GOMAXPROCS(线程数)为 20,这个在此处不需要过度关心。</li><li>831439 表示一共执行了 831439 次,即代码中 b.N 的值,这个值不是固定不变的。实际上是通过循环调用 831439 次 Loop() 函数得到的最后性能结果。</li><li>1291 ns&#x2F;op 表示平均每次执行 Loop() 函数所消耗的时间是 1291 ns。</li></ol><p>通过上述结果可以看出,随着 Step 参数的增加,内存访问的局部性就越差,执行 Loop() 的性能也就越差。<br>如果要设计出一个更加高效的程序,则提高代码的局部性访问是非 常有效的程序性能优化手段之一。</p><ul><li>当 <code>Step=1</code> 时,CPU 加载一个缓存行(通常是 64 字节)时,后续的几个元素已经顺便被加载到了 L1 Cache 中。</li><li>当 <code>Step</code> 很大时,每次访问都可能错过当前缓存行,迫使 CPU 不断从主存甚至触发页表查询来获取数据,极大增加了延迟。</li></ul><hr><p><strong>思考题:Go GPM 调度器中的局部性:为什么一个 G (Goroutine) 开辟的子 G 优先放在当前的本地 P 队列中,而不是其他 P 队列?</strong></p><p>这是 <strong>“调度局部性”</strong> 的体现,因为:</p><ol><li><strong>数据局部性</strong>:子 G 往往会继承或共享父 G 的数据(闭包捕获、Channel 通讯)。如果它们在同一个 P(即同一个 M&#x2F;线程)上运行,这些数据更有可能已经存在于该 CPU 的 <strong>L1&#x2F;L2 缓存</strong>中,避免了跨核心同步的开销。</li><li><strong>减少竞争</strong>:每个 P 拥有本地队列,M 访问本地队列不需要加全局锁。如果新 G 总是随机分配,会产生大量的锁竞争和上下文切换。</li><li><strong>栈与状态预热</strong>:同一个 P 上的任务切换,可以最大限度地保持 CPU 流水线和分支预测器的“热度”。</li></ol><p><strong>日常开发中如何优化程序性能?</strong></p><ul><li><strong>优先使用数组&#x2F;切片</strong>:它们在物理内存上是连续的,空间局部性最好。尽量避免使用大量指针嵌套的复杂结构(如 <code>List</code> 或离散的 <code>Map</code> 节点)。</li><li><strong>顺序读写</strong>:在处理大数据量时,尽量按顺序访问,利用硬件的预取机制。</li><li><strong>关注缓存行对齐</strong>:在高性能并发编程中,注意防止“伪共享” (False Sharing)。</li></ul><blockquote><p><strong>伪共享</strong>是多核编程中一种隐蔽的性能杀手。它发生在多个 CPU 核心同时修改同一个缓存行 (Cache Line) 中的不同变量时。虽然从逻辑上看,多个线程操作的是互相独立的变量,但在底层硬件看来,它们在争夺同一块缓存区域,导致缓存系统不断失效,性能急剧下降。</p><p>为了提高效率,CPU 并不是按字节从内存读取数据的,而是以<strong>缓存行</strong>为单位。在现代 x86 和 ARM 架构中,一个缓存行通常是 <strong>64 字节</strong>。<br>当你读取一个 int64 变量时,CPU 会顺便把它相邻的另外 7 个 int64 变量也加载进缓存行。<br><strong>缓存一致性协议 (MESI)</strong>:如果核心 A 修改了某个缓存行的数据,核心 B 缓存中对应的该行就会被标记为“无效”。核心 B 下次访问时必须重新从主存(或 L3 缓存)加载。</p><p>假设有两个全局变量 A 和 B(例如两个计数器),它们在内存中是紧挨着的,正好被加载到了同一个 64 字节的缓存行中:<br>核心 1 正在频繁修改变量 A。<br>核心 2 正在频繁修改变量 B。<br>由于硬件以缓存行为单位管理一致性,即使核心 2 根本没动过 A,但因为核心 1 修改了该行,核心 2 的整个缓存行都会失效。<br>两个核心就像在玩一场“接力赛”,不断地让对方的缓存失效,迫使数据在核心之间来回“颠簸”(Ping-ponging)。</p><p>真共享:多个线程真的需要访问同一个变量,锁竞争是不可避免的。<br>伪共享:多个线程访问的是<strong>完全无关</strong>的变量,仅仅是因为它们在内存位置上靠得太近,被“误杀”了。</p></blockquote><h3 id="如何用-Go-语言实现手动内存管理与内存池设计-简略-不带书中-CGO-代码"><a href="#如何用-Go-语言实现手动内存管理与内存池设计-简略-不带书中-CGO-代码" class="headerlink" title="如何用 Go 语言实现手动内存管理与内存池设计(简略,不带书中 CGO 代码)"></a>如何用 Go 语言实现手动内存管理与内存池设计(简略,不带书中 CGO 代码)</h3><p>在高性能场景(如网关、游戏服务器)中,Go 原生的 GC(垃圾回收)扫描可能会带来延迟抖动。为了解决这个问题,我们可以手动管理内存。</p><h4 id="Cgo-与指针逻辑"><a href="#Cgo-与指针逻辑" class="headerlink" title="Cgo 与指针逻辑"></a>Cgo 与指针逻辑</h4><p>手动管理的核心是绕过 Go Runtime,直接与内核对话。</p><ul><li><strong>避开 GC</strong>:利用 <code>import &quot;C&quot;</code> 调用 C 的 <code>malloc</code>。这部分内存分配在系统堆(System Heap)上,Go 的垃圾回收器完全“看不见”它,因此不会产生扫描开销。</li><li><strong>指针算术 (Pointer Arithmetic)</strong>:Go 的指针不支持加减运算。为了定位 Buf 内部的具体地址,必须进行类型转换:<code>目标地址 = unsafe.Pointer(uintptr(基地址)+uintptr(偏移量))</code></li></ul><blockquote><p>这是所有高性能内存库(包括 Go 源码本身)处理连续内存块偏移的唯一标准方式。</p></blockquote><p>明白,这节内容确实需要适度的深度,因为它不仅是代码实现,更是理解 Go 内存管理(TCMalloc)的<strong>逻辑跳板</strong>。</p><p>以下是为您重新整理的 <strong>3.4 节:手动内存管理与内存池设计</strong> 深度精简笔记。我们跳过语法细节,重点拆解<strong>地址运算、状态转换和池化架构</strong>。</p><h4 id="Buf-内存缓冲单元"><a href="#Buf-内存缓冲单元" class="headerlink" title="Buf (内存缓冲单元)"></a>Buf (内存缓冲单元)</h4><p><code>Buf</code> 是对原始内存块的封装。它的精髓在于<strong>三个核心索引</strong>对地址空间的划分。</p><ul><li><strong><code>Data</code></strong>:<code>malloc</code> 申请到的物理内存首地址。</li><li><strong><code>Head</code></strong>:有效数据的起始偏移量。</li><li><strong><code>Length</code></strong>:有效数据的末尾偏移量(已写入数据的边界)。</li><li><strong><code>Capacity</code></strong>:该内存块的最大物理容量上限。</li></ul><p>关键内存操作</p><ol><li><strong>逻辑弹出 (Pop)</strong>:<br>不涉及数据移动。只需 <code>head += n</code>。这意味着前 <code>n</code> 个字节在逻辑上被“删除了”,极大地提高了处理速度。</li><li><strong>内存平移 (Adjust)</strong>:<ul><li>由于频繁 <code>Pop</code>,<code>head</code> 会不断右移,导致前端出现大量“过期数据”占据空间。</li><li>调用 <code>memmove</code>(支持内存重叠的拷贝),将 <code>[head : length]</code> 之间的数据整体搬运回 <code>0</code> 偏移位置,并将 <code>head</code> 重置为 <code>0</code>。</li></ul></li></ol><blockquote><p>这是解决内存碎片、提高空间利用率的典型手段。</p></blockquote><h4 id="BufPool-内存池管理"><a href="#BufPool-内存池管理" class="headerlink" title="BufPool (内存池管理)"></a>BufPool (内存池管理)</h4><p>如果每次 <code>Buf</code> 申请都找内核要(<code>malloc</code>),系统调用的成本太高。</p><h5 id="分级管理-Size-Classing"><a href="#分级管理-Size-Classing" class="headerlink" title="分级管理 (Size-Classing)"></a>分级管理 (Size-Classing)</h5><p>内存池不是一个大仓库,而是按规格分成的多个“专柜”:</p><ul><li><strong>Hash 映射</strong>:使用 <code>Map[int]*Buf</code>。Key 是固定规格(如 4KB, 16KB, 64KB),Value 是该规格下<strong>空闲 Buf 的单向链表</strong>。</li><li><strong>优势</strong>:申请 5KB 时,直接去 16KB 的专柜取,避免了频繁调整内存大小的开销。</li></ul><h5 id="借与还-Alloc-Revert"><a href="#借与还-Alloc-Revert" class="headerlink" title="借与还 (Alloc &amp; Revert)"></a>借与还 (Alloc &amp; Revert)</h5><ol><li><strong>Alloc (分配)</strong>:<ul><li>根据所需大小计算出所属的 Bucket(桶)。</li><li>从链表头部摘除一个 Buf (<code>LIFO</code> 原则)。如果链表为空,则触发 <code>malloc</code> 补充。</li></ul></li><li><strong>Revert (回收)</strong>:<ul><li>调用 <code>Clear()</code>:仅仅是将 <code>head</code> 和 <code>length</code> 归零,不释放物理内存。</li><li><strong>挂回链表</strong>:将 Buf 插回对应 Bucket 链表的头部,等待下一次分配。</li></ul></li></ol><h4 id="Zbuf-安全适配层"><a href="#Zbuf-安全适配层" class="headerlink" title="Zbuf (安全适配层)"></a>Zbuf (安全适配层)</h4><p>为了防止业务代码直接操作指针,最后封一层 <code>Zbuf</code>:</p><ul><li><strong>自动扩容</strong>:写入数据超过当前 Buf 容量时,<code>Zbuf</code> 会自动去 <code>Pool</code> 换一个更大的 Buf,并完成数据迁移,对用户透明。</li><li><strong>I&#x2F;O 绑定</strong>:可以直接将 <code>Socket</code> 读取的数据填充进 <code>Zbuf</code>。业务层只需面向 <code>Zbuf</code> 获取 <code>[]byte</code>,无需感知底层是哪块内存、是否来自池子。</li></ul><h3 id="TCMalloc-内存管理"><a href="#TCMalloc-内存管理" class="headerlink" title="TCMalloc 内存管理"></a>TCMalloc 内存管理</h3><p>TCMalloc (Thread Cache Malloc) 是 Go 内存管理设计的逻辑蓝图。</p><h4 id="TCMalloc-核心理念"><a href="#TCMalloc-核心理念" class="headerlink" title="TCMalloc 核心理念"></a>TCMalloc 核心理念</h4><p>全局共享内存池的缺陷<br>传统的内存池(如之前实现的 BufPool)通常是所有线程共享的。</p><p><strong>缺点</strong>:所有线程申请内存都要与全局池交互,为了保证并发安全,必须加<strong>互斥锁</strong>。在高并发下,锁竞争会成为严重的性能瓶颈。</p><p>TCMalloc 的改进:ThreadCache<br>TCMalloc 为每个线程预分配独立缓存。其结构包含三层:</p><ol><li><strong>ThreadCache (线程缓存)</strong>:每个线程独立维护,申请内存时无需加锁,性能极高。</li><li><strong>CentralCache (中心缓存)</strong>:所有线程共享。当 ThreadCache 内存不足时,从这里补给;内存过多时,退还给这里。由于共享,访问需加锁。</li><li><strong>PageHeap (页堆)</strong>:最底层的全局共享缓存,负责中大对象分配及向操作系统申请内存。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E4%B8%ADThreadCache%E3%80%81CentralCache%E3%80%81PageHeap%E7%BB%93%E6%9E%84%E5%85%B3%E7%B3%BB.png"                        alt="TCMalloc中ThreadCache、CentralCache、PageHeap结构关系.png"                 ></p><h4 id="TCMalloc-基础结构"><a href="#TCMalloc-基础结构" class="headerlink" title="TCMalloc 基础结构"></a>TCMalloc 基础结构</h4><p>在深入流程前,必须理解三个基本名词:</p><ol><li><strong>Page (页)</strong>:TCMalloc 的最小存储单元。默认为 <strong>8KB</strong>。整个虚拟内存被划分为数万个同等大小的 Page。<ul><li><strong>优点</strong>:通过内存地址和固定算法,可以快速算出该地址属于哪个 Page ID。</li></ul></li><li><strong>Span (内存块)</strong>:<strong>多个连续 Page 的集合</strong>。<ul><li>TCMalloc 以 Span 为单位向系统申请内存。</li><li>Span 记录了起始 Page 编号 (<code>Start</code>) 和连续 Page 的数量 (<code>Length</code>)。</li><li>多个 Span 以<strong>双向链表</strong>形式组织,方便管理。</li></ul></li><li><strong>Size Class (尺寸等级)</strong>:<ul><li>对于小对象,TCMalloc 将其划分为多个刻度(如 8B, 16B…)。</li><li>每个 Size Class 对应一个由多个等空间 Page 组成的 Span。</li></ul></li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E4%B8%ADPage%E3%80%81Span%E3%80%81SizeClass%E7%BB%93%E6%9E%84%E5%85%B3%E7%B3%BB.png"                        alt="TCMalloc中Page、Span、SizeClass结构关系.png"                 ></p><h4 id="TCMalloc-缓存结构"><a href="#TCMalloc-缓存结构" class="headerlink" title="TCMalloc 缓存结构"></a>TCMalloc 缓存结构</h4><h5 id="ThreadCache"><a href="#ThreadCache" class="headerlink" title="ThreadCache"></a>ThreadCache</h5><ul><li><strong>结构</strong>:内部为每个 Size Class 维护一个 <code>FreeList</code>。</li><li><strong>分配&#x2F;回收</strong>:直接从对应的 <code>FreeList</code> 返回或插入对象。</li><li><strong>特性</strong>:无锁操作。只有当 <code>FreeList</code> 为空时,才向 CentralCache 申请。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E4%B8%AD%E7%9A%84ThreadCache.png"                        alt="TCMalloc中的ThreadCache.png"                 ></p><h5 id="CentralCache"><a href="#CentralCache" class="headerlink" title="CentralCache"></a>CentralCache</h5><ul><li><strong>结构</strong>:维护与 ThreadCache 相同的 Size Class 刻度。</li><li><strong>职责</strong>:作为 ThreadCache 的二级缓存。</li><li><strong>特性</strong>:全局共享,访问需加锁。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E4%B8%AD%E7%9A%84CentralCache.png"                        alt="TCMalloc中的CentralCache.png"                 ></p><h5 id="PageHeap"><a href="#PageHeap" class="headerlink" title="PageHeap"></a>PageHeap</h5><ul><li><strong>职责</strong>:CentralCache 的三级缓存,也是中&#x2F;大对象的分配源。直接衔接操作系统虚拟内存。</li><li><strong>管理方式</strong>:</li><li><strong>128 个 Page 以内</strong>:按 Page 刻度(1, 2, 3…128)使用链表缓存。</li><li><strong>128 个 Page 以上</strong>:使用<strong>有序集合 (Set)</strong> 存放。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E4%B8%AD%E7%9A%84PageHeap.png"                        alt="TCMalloc中的PageHeap.png"                 ></p><h4 id="对象分配流程"><a href="#对象分配流程" class="headerlink" title="对象分配流程"></a>对象分配流程</h4><h5 id="小对象分配流程-≤-256KB"><a href="#小对象分配流程-≤-256KB" class="headerlink" title="小对象分配流程 (≤ 256KB)"></a>小对象分配流程 (≤ 256KB)</h5><ol><li>计算申请内存对应的 <code>SizeClass</code>。</li><li>访问 <code>ThreadCache</code> 的 <code>FreeList</code>。</li><li><strong>命中</strong>:返回第一个空闲对象,流程结束。</li><li><strong>未命中</strong>:加锁请求 <code>CentralCache</code>。</li><li><code>CentralCache</code> 若有空闲,返回一批对象给 <code>ThreadCache</code>,并将其中一个返回给线程。</li><li><code>CentralCache</code> 若无空闲,向 <code>PageHeap</code> 申请 Span,拆分成对应规格填入 <code>CentralFreeList</code>。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E5%B0%8F%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E6%B5%81%E7%A8%8B.png"                        alt="TCMalloc小对象分配流程.png"                 ></p><h5 id="中对象分配流程-256KB-1MB"><a href="#中对象分配流程-256KB-1MB" class="headerlink" title="中对象分配流程 (256KB ~ 1MB)"></a>中对象分配流程 (256KB ~ 1MB)</h5><ol><li>绕过 <code>ThreadCache</code> 和 <code>CentralCache</code>,直接请求 <code>PageHeap</code>。</li><li><code>PageHeap</code> 在 128 个 Page 以内的<strong>小 Span 链表</strong>中向上取整查找。</li><li>若对应的 个 Page 链表为空,则继续向下遍历(最多到 128),找到第一个非空链表。</li><li><strong>切分</strong>:将找到的 个 Page 拆分为 (返回用户)和 (退还给 PageHeap 对应链表)。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/TCMalloc%E4%B8%AD%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E6%B5%81%E7%A8%8B.png"                        alt="TCMalloc中对象分配流程.png"                 ></p><h5 id="大对象分配流程-1MB"><a href="#大对象分配流程-1MB" class="headerlink" title="大对象分配流程 (&gt; 1MB)"></a>大对象分配流程 (&gt; 1MB)</h5><ol><li>直接向 <code>PageHeap</code> 发起申请。</li><li>计算所需 Page 数量 。</li><li>在 <strong>Large Span Set</strong> 中通过最佳适配算法找到不小于 的最小 Span 。</li><li>若找不到,向系统申请新内存并重试。</li><li><strong>切分</strong>:返回 个 Page,剩余的 退还。若 退回 Large Set,否则退回小 Span 链表。</li></ol><h4 id="一些问题"><a href="#一些问题" class="headerlink" title="一些问题"></a>一些问题</h4><h5 id="中对象和大对象不会在-ThreadCache-和-CentralCache-中缓存吗"><a href="#中对象和大对象不会在-ThreadCache-和-CentralCache-中缓存吗" class="headerlink" title="中对象和大对象不会在 ThreadCache 和 CentralCache 中缓存吗"></a>中对象和大对象不会在 ThreadCache 和 CentralCache 中缓存吗</h5><p>在 TCMalloc 的设计中,<strong>ThreadCache 和 CentralCache 仅服务于小对象(≤ 256KB)</strong>。<br><strong>小对象靠“缓存”换取并发性能,中大对象靠“页管理”换取空间利用率。</strong></p><h5 id="为什么中-大对象不进-Cache"><a href="#为什么中-大对象不进-Cache" class="headerlink" title="为什么中&#x2F;大对象不进 Cache?"></a>为什么中&#x2F;大对象不进 Cache?</h5><ol><li>内存碎片与利用率<br>ThreadCache 和 CentralCache 的核心是 <strong>Size Class(尺寸分级)</strong>。<ul><li><strong>小对象</strong>:规格固定(如 8B, 16B),在一个 Span 里可以切分出成百上千个小格子。即便某个格子空闲,浪费的空间也极小。</li><li><strong>中&#x2F;大对象</strong>:如果也按固定规格缓存(比如专门开辟一个 1MB 的缓存位),一旦这个位置长期不被使用,就会造成严重的<strong>内部碎片</strong>。对于大额内存,直接按需向 <code>PageHeap</code> 申请“页(Page)”级别的内存块(Span)更加灵活高效。</li></ul></li><li>缓存命中率与锁开销<ul><li><strong>小对象</strong>:申请频率极高。如果每次都找 <code>PageHeap</code> 加锁申请,性能会因锁竞争崩溃,所以必须要在 ThreadCache 里搞“私产”。</li><li><strong>大对象</strong>:申请频率相对较低。直接去 <code>PageHeap</code> 加锁申请带来的性能损耗,相比于其庞大的内存分配动作,是可以接受的。</li></ul></li></ol><p>内存分配路径对比</p><table><thead><tr><th>对象类别</th><th>大小区间</th><th>第一站</th><th>第二站</th><th>终点站 (数据源)</th></tr></thead><tbody><tr><td><strong>小对象</strong></td><td></td><td><strong>ThreadCache</strong> (无锁)</td><td>CentralCache (有锁)</td><td>PageHeap (有锁)</td></tr><tr><td><strong>中对象</strong></td><td></td><td><strong>PageHeap</strong> (小 Span 链表)</td><td>-</td><td>PageHeap &#x2F; OS</td></tr><tr><td><strong>大对象</strong></td><td></td><td><strong>PageHeap</strong> (Large Set)</td><td>-</td><td>PageHeap &#x2F; OS</td></tr></tbody></table><h5 id="PageHeap-是如何处理中-大对象的"><a href="#PageHeap-是如何处理中-大对象的" class="headerlink" title="PageHeap 是如何处理中&#x2F;大对象的?"></a>PageHeap 是如何处理中&#x2F;大对象的?</h5><p>由于中&#x2F;大对象不经过前两层,<code>PageHeap</code> 承担了全部的重任,但它的处理逻辑也有精细的区别。</p><h6 id="中对象-利用“页刻度”链表"><a href="#中对象-利用“页刻度”链表" class="headerlink" title="中对象:利用“页刻度”链表"></a>中对象:利用“页刻度”链表</h6><p>PageHeap 内部维护了 <strong>128 个单向链表</strong>。</p><ul><li>第 1 个链表挂载 1 个 Page 大小的 Span。</li><li>第 2 个链表挂载 2 个 Page 大小的 Span。</li><li>…以此类推,直到 128 个 Page(即 1MB)。</li></ul><p>对于中对象,PageHeap 会尝试从最匹配的链表中直接摘取一个 Span。</p><h6 id="大对象-利用“有序集合”-Search"><a href="#大对象-利用“有序集合”-Search" class="headerlink" title="大对象:利用“有序集合” (Search)"></a>大对象:利用“有序集合” (Search)</h6><p>当对象超过 128 个 Page 时,链表就不够用了。</p><ul><li>PageHeap 使用一个 <strong>按 Span 大小排序的集合(如红黑树或 Set)</strong> 来存储这些巨型 Span。</li><li>申请时,它会执行 <strong>Best-fit(最佳适配)</strong> 算法:在集合里找一个比你需求大、且最接近需求的 Span 进行拆分。</li></ul><h3 id="Go-语言堆内存管理"><a href="#Go-语言堆内存管理" class="headerlink" title="Go 语言堆内存管理"></a>Go 语言堆内存管理</h3><p>Go 的内存管理在逻辑上分为三层,与 TCMalloc 极其相似,但与 Go 的调度模型(GPM)进行了深度耦合。</p><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97%E5%85%B3%E7%B3%BB.png"                        alt="Go语言内存管理模块关系.png"                 ></p><h4 id="核心概念与单位"><a href="#核心概念与单位" class="headerlink" title="核心概念与单位"></a>核心概念与单位</h4><p>Go 延续并细化了 TCMalloc 的 Page、Span 等概念。</p><ol><li><strong>Page (页)</strong>:与 TCMalloc 的 Page 一致。Go 与虚拟内存交互的最小单位,固定为 <strong>8KB</strong>。</li><li><strong>mSpan</strong>:与 TCMalloc 中的 Span 一致。内存管理的基本单元,由一组<strong>连续的 Page</strong> 组成。</li><li><strong>Object (对象)</strong>:<ul><li><strong>定义</strong>:mSpan 初始化时会被切割成多个 Object。它是内存分配给应用的最终基本单元。</li><li><strong>关系</strong>:NumOfObject &#x3D; mSpanSize &#x2F; ObjectSize。<br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/ObjectSize%E4%B8%8ESpan%E7%9A%84%E5%85%B3%E7%B3%BB.png"                        alt="ObjectSize与Span的关系.png"                 ></li></ul></li><li><strong>Size Class</strong>:Object 大小的级别(如等级 1 对应 8B,等级 2 对应 16B …)。</li><li><strong>Span Class (Go 额外定义)</strong>:<ul><li>每个 Size Class 对应两个 Span Class。<br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/SpanClass%E4%B8%8ESizeClass%E7%9A%84%E9%80%BB%E8%BE%91%E7%BB%93%E6%9E%84%E5%85%B3%E7%B3%BB.png"                        alt="SpanClass与SizeClass的逻辑结构关系.png"                 ></li><li><strong>scan</strong>:存放包含指针的对象(GC 需扫描)。</li><li><strong>noscan</strong>:存放不含指针的对象(GC 无需扫描,提速)。</li><li><strong>计算公式</strong>:<code>SpanClass = SizeClass &lt;&lt; 1 | (noscan ? 1 : 0)</code>。</li></ul></li><li><strong>Size Class 明细 (66个级别)</strong>:<br>Go 固定划分了 66 种规格。其中 <strong>Max Waste (最大浪费比)</strong> 的计算是优化的关键。<ul><li><strong>浪费来源</strong>:Object 尺寸对齐产生的浪费 + mSpan 末尾无法整除产生的浪费(tail waste)。</li></ul></li></ol><p>Todo</p><h4 id="MCache-本地缓存"><a href="#MCache-本地缓存" class="headerlink" title="MCache (本地缓存)"></a>MCache (本地缓存)</h4><ul><li><strong>绑定关系</strong>:与 TCMalloc 绑定线程不同,Go 的 MCache 与 <strong>P (Processor)</strong> 绑定。<br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/ThreadCache%E4%B8%8EMCache%E7%9A%84%E7%BB%91%E5%AE%9A%E5%85%B3%E7%B3%BB%E5%8C%BA%E5%88%AB.png"                        alt="ThreadCache与MCache的绑定关系区别.png"                 ></li><li><strong>无锁分配</strong>:同一时间一个 P 只运行一个 M,因此 Goroutine 在 MCache 申请内存<strong>无须加锁</strong>。</li><li><strong>内部构造</strong>:包含 134 个 Span Class 对应的 mSpan 指针(67个级别 2)。<br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MCache%E5%86%85%E9%83%A8%E6%9E%84%E9%80%A0.png"                        alt="MCache内部构造.png"                 ></li><li><strong>ZeroBase</strong>:申请 0 字节内存(如 <code>struct{}</code>)时,Go 直接返回 <code>zerobase</code> 地址,不走正常分配逻辑。 <figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">    <span class="string">&quot;fmt&quot;</span></span><br><span class="line">    <span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestZeroMalloc</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> (</span><br><span class="line">        a <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">        b [<span class="number">0</span>]<span class="type">int</span></span><br><span class="line">        c [<span class="number">100</span>]<span class="keyword">struct</span>&#123;&#125;</span><br><span class="line">        d = <span class="built_in">make</span>([]<span class="keyword">struct</span>&#123;&#125;, <span class="number">100</span>)</span><br><span class="line">    )</span><br><span class="line">    fmt.Printf(<span class="string">&quot;%p\n&quot;</span>, &amp;a)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;%p\n&quot;</span>, &amp;b)</span><br><span class="line">    fmt.Printf(<span class="string">&quot;%p\n&quot;</span>, &amp;c[<span class="number">10</span>])</span><br><span class="line">    fmt.Printf(<span class="string">&quot;%p\n&quot;</span>, &amp;(d[<span class="number">10</span>]))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestZeroMalloc</span><br><span class="line">0x7ff6b59c7740</span><br><span class="line">0x7ff6b59c7740</span><br><span class="line">0x7ff6b59c7740</span><br><span class="line">0x7ff6b59c7740</span><br><span class="line">--- PASS: TestZeroMalloc (0.00s)</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure></li></ul><h4 id="MCentral-中心缓存"><a href="#MCentral-中心缓存" class="headerlink" title="MCentral (中心缓存)"></a>MCentral (中心缓存)</h4><ul><li><strong>地位</strong>:所有 P 共享,访问需<strong>加锁</strong>。</li><li><strong>内存交换单位</strong>:<br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E5%90%84%E5%B1%82%E7%BA%A7%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2%E5%8D%95%E4%BD%8D.png"                        alt="Go语言内存管理各层级内存交换单位.png"                 ><ul><li>与协程层交换 <strong>Object</strong>。</li><li>与 MCache 交换 <strong>mSpan</strong>。</li><li>与 MHeap 交换 <strong>Page</strong>。</li></ul></li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MCentral%E5%86%85%E9%83%A8%E6%9E%84%E9%80%A0.png"                        alt="MCentral内部构造.png"                 ></p><ul><li><strong>内部构造</strong>:每个 Span Class 对应一个 MCentral。</li><li><strong>NonEmpty (Partial Set)</strong>:有空闲 Object 的 mSpan 集合。</li><li><strong>Empty (Full Set)</strong>:无空闲 Object 的 mSpan 集合。</li></ul><h4 id="MHeap-全局堆"><a href="#MHeap-全局堆" class="headerlink" title="MHeap (全局堆)"></a>MHeap (全局堆)</h4><ul><li><strong>地位</strong>:进程全局唯一,访问需<strong>加锁</strong>。</li><li><strong>管理单位</strong>:<strong>HeapArena</strong>(每个 64MB)。包含 <code>bitmap</code>(记录内存使用及 GC 标记情况)和 <code>ArenaHint</code>。</li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MHeap%E5%86%85%E9%83%A8%E9%80%BB%E8%BE%91%E5%B1%82%E6%9E%84%E9%80%A0.png"                        alt="MHeap内部逻辑层构造.png"                 ><br><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MHeap%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%BC%95%E7%94%A8%E4%BE%9D%E8%B5%96.png"                        alt="MHeap数据结构引用依赖.png"                 ></p><h4 id="对象分配流程-1"><a href="#对象分配流程-1" class="headerlink" title="对象分配流程"></a>对象分配流程</h4><h5 id="Tiny-微小对象分配流程-≤-16B"><a href="#Tiny-微小对象分配流程-≤-16B" class="headerlink" title="Tiny 微小对象分配流程 ( ≤ 16B )"></a>Tiny 微小对象分配流程 ( ≤ 16B )</h5><p>为了解决极小对象(如 <code>bool</code>)频繁申请导致的浪费,Go 引入了 Tiny 分配器。</p><ul><li><p><strong>机制</strong>:从 Size Class 2 中取一个 16B 的 Object 作为 Tiny 缓冲区。</p></li><li><p><strong>流程</strong>:</p><ol><li>判断申请是否 小于 16B,且对象不包含指针 <strong>noscan</strong>。</li><li>若 Tiny 空间不足,向 MCache 申请新的 16B Object 置于 Tiny 缓冲区中。</li><li>按字节对齐方式将微小对象塞入 Tiny 空间。</li></ol></li><li><p><strong>收益</strong>:相比传统对齐方式,平均节省约 <strong>20%</strong> 的内存。</p></li></ul><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MCache%E4%B8%ADTiny%E5%BE%AE%E5%B0%8F%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E6%B5%81%E7%A8%8B.png"                        alt="MCache中Tiny微小对象分配流程.png"                 ></p><h5 id="小对象分配流程-16B-32KB"><a href="#小对象分配流程-16B-32KB" class="headerlink" title="小对象分配流程 ( 16B ~ 32KB )"></a>小对象分配流程 ( 16B ~ 32KB )</h5><ol><li>计算对象 Size,匹配对应的 Size Class。</li><li>定位 Span Class(考虑是否有指针)。</li><li>尝试从 MCache 提取 Object。</li><li>若 MCache 耗尽,向 MCentral 申请一个 mSpan。</li><li>若 MCentral 耗尽,向 MHeap 申请 Page 组成新的 mSpan。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MCache%E4%B8%AD%E5%B0%8F%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E6%B5%81%E7%A8%8B.png"                        alt="MCache中小对象分配流程.png"                 ></p><h5 id="大对象分配流程-32KB"><a href="#大对象分配流程-32KB" class="headerlink" title="大对象分配流程 ( &gt; 32KB )"></a>大对象分配流程 ( &gt; 32KB )</h5><p>大对象不经过 MCache 和 MCentral,直接由 MHeap 分配。</p><ol><li>计算所需 Page 数量。</li><li>绕过缓存,直接向 <strong>MHeap 的 Arenas</strong> 申请。</li><li>若 Arenas 不足,向操作系统(OS)申请新的虚拟内存。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/../../images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/MCache%E4%B8%AD%E5%A4%A7%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D%E6%B5%81%E7%A8%8B.png"                        alt="MCache中大对象分配流程.png"                 ></p>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/</id>
    <link href="https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"/>
    <published>2026-01-04T12:09:23.000Z</published>
    <summary>
      <![CDATA[<h2 id="第三章-Go-语言内存管理洗髓经"><a href="#第三章-Go-语言内存管理洗髓经" class="headerlink" title="第三章 Go 语言内存管理洗髓经"></a>第三章 Go 语言内存管理洗髓经</h2><h3 id="内存为什么需要管理]]>
    </summary>
    <title>《深入理解Go语言》读书笔记 - Go 语言内存管理</title>
    <updated>2026-01-04T12:09:23.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="读书笔记" scheme="https://cooooing.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="《深入理解Go语言》" scheme="https://cooooing.github.io/tags/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B/"/>
    <category term="GC" scheme="https://cooooing.github.io/tags/GC/"/>
    <content>
      <![CDATA[<h2 id="第二章-Go-语言混合写屏障的-GC-全场景分析"><a href="#第二章-Go-语言混合写屏障的-GC-全场景分析" class="headerlink" title="第二章 Go 语言混合写屏障的 GC 全场景分析"></a>第二章 Go 语言混合写屏障的 GC 全场景分析</h2><p>垃圾回收(Garbage Collection,GC)是编程语言中提供的自动内存管理机制,GC 能够自动释放不需要的内存对象,让出存储器资源,其释放过程中无须程序员手动执行。<br>GC 机制在现代很多编程语言得到支持,针对 GC 性能的优劣程度,也是不同语言之间的对比指标之一。</p><p>Go 语言在 GC 的演进过程中也经历了几次重大改变,如下:</p><ol><li>Go V1.3之前的STW的标记和清除(SWT Mark Sweep)。</li><li>Go V1.5的三色并发标记法。“强-弱”三色不变式、插入屏障、删除屏障。</li><li>Go V1.8混合写屏障机制。</li></ol><h3 id="Go-V1-3-标记-清除算法"><a href="#Go-V1-3-标记-清除算法" class="headerlink" title="Go V1.3 标记-清除算法"></a>Go V1.3 标记-清除算法</h3><p>此算法主要有两个步骤:</p><ol><li>标记(Mark Phase)。</li><li>清除(Sweep Phase)。</li></ol><p>找到需要被清除的内存数据,然后一次性清除。</p><h4 id="标记清除-Mark-and-Sweep-算法的详细过程"><a href="#标记清除-Mark-and-Sweep-算法的详细过程" class="headerlink" title="标记清除(Mark and Sweep)算法的详细过程"></a>标记清除(Mark and Sweep)算法的详细过程</h4><ol><li>暂停程序业务逻辑,对可达和不可达的对象进行分类,然后做上标记。</li><li>开始标记。程序找出它所有可达的对象,并做上标记。</li><li>标记完了之后,开始清除未标记的对象。<br>Mark and Sweep 算法在执行的时候,需要程序暂停,即 STW(Stop The World)。在 STW 的过程中,CPU不执行用户代码,全部用于垃圾回收。<br>这个过程影响很大,所以 STW 也是一些回收机制最大的难题和希望优化的点。</li><li>停止暂停,让程序继续运行,然后重复这个过程,直到 Process 程序生命周期结束。</li></ol><h4 id="标记清除算法的缺点"><a href="#标记清除算法的缺点" class="headerlink" title="标记清除算法的缺点"></a>标记清除算法的缺点</h4><p>标记清除算法简单明了,但是也有非常严重的问题:</p><ol><li>STW 让程序暂停,所以程序会出现卡顿。</li><li>标记需要扫描整个 Heap。</li><li>清除数据会产生 Heap 碎片。</li></ol><p>Go V1.3 版本执行 GC 的基本流程就是首先启动 STW,使程序暂停,然后执行标记,再执行数据回收,最后停止STW。<br>全部的 GC 时间都是包裹在 STW 范围之内的,Go V1.3 做了简单的优化,将停止 STW 的步骤提前,缩小 STW 暂停的时间范围。<br>将 Sweep 清除的步骤放到停止 STW 之后执行,因为这些对象已经是不可达对象了,不会出现回收写冲突等问题。</p><p>但是无论怎么优化,这个算法都会暂停整个程序。</p><h3 id="Go-V1-5-三色标记法"><a href="#Go-V1-5-三色标记法" class="headerlink" title="Go V1.5 三色标记法"></a>Go V1.5 三色标记法</h3><p>三色标记法 GC 过程和其他用户 Goroutine 可并发运行,但<strong>根对象扫描仍然依赖 STW</strong>,这是该版本 GC 停顿的主要来源。</p><h4 id="对象的初始颜色状态"><a href="#对象的初始颜色状态" class="headerlink" title="对象的初始颜色状态"></a>对象的初始颜色状态</h4><p>在一次 GC 周期开始时:<br>堆中所有存活对象在逻辑上均视为“白色”</p><blockquote><p>白色表示:当前 GC 轮次中尚未被发现为可达对象</p></blockquote><p>需要注意的是:这里的“程序”并不是一个抽象整体,而是由一组 GC Root 组成,包括:</p><ul><li>所有 goroutine 的栈</li><li>全局变量</li><li>runtime 内部引用的对象</li></ul><p>从 GC 的视角看,“程序是否可达”本质上等价于“是否能从 Root 出发访问到”。</p><h4 id="GC-开始-STW-根扫描阶段"><a href="#GC-开始-STW-根扫描阶段" class="headerlink" title="GC 开始:STW 根扫描阶段"></a>GC 开始:STW 根扫描阶段</h4><h5 id="Stop-The-World-第一次"><a href="#Stop-The-World-第一次" class="headerlink" title="Stop-The-World(第一次)"></a>Stop-The-World(第一次)</h5><p>在 Go 1.5 中,三色标记开始前必须先进入 STW,原因是:</p><ul><li>goroutine 栈 <strong>不能并发扫描</strong></li><li>必须冻结世界,保证 root 集合稳定</li></ul><h5 id="Root-扫描与初始标记"><a href="#Root-扫描与初始标记" class="headerlink" title="Root 扫描与初始标记"></a>Root 扫描与初始标记</h5><p>在 STW 状态下,GC 执行以下操作:</p><ol><li><p>扫描所有 GC Root</p></li><li><p>将从 Root 直接可达的对象:</p><ul><li>从 <strong>白色标记为灰色</strong></li><li>放入 <strong>灰色标记队列(mark queue)</strong></li></ul></li></ol><p>这一步是:</p><ul><li><strong>一次性扫描</strong></li><li><strong>非递归</strong></li><li>仅处理 Root 的第一层可达对象</li></ul><p>例如:Root 直接引用对象 1 和对象 4 ,则对象 1、对象 4 被标记为灰色</p><h5 id="启用写屏障并恢复世界"><a href="#启用写屏障并恢复世界" class="headerlink" title="启用写屏障并恢复世界"></a>启用写屏障并恢复世界</h5><p>Root 扫描完成后:</p><ul><li>启用 <strong>写屏障(Write Barrier)</strong></li><li>恢复用户 goroutine 执行<br>GC 进入并发标记阶段</li></ul><p>这一阶段的 STW 时间与:goroutine 数量、栈大小 <strong>强相关</strong></p><h4 id="并发三色标记阶段-Concurrent-Mark"><a href="#并发三色标记阶段-Concurrent-Mark" class="headerlink" title="并发三色标记阶段(Concurrent Mark)"></a>并发三色标记阶段(Concurrent Mark)</h4><h5 id="灰色对象扫描规则"><a href="#灰色对象扫描规则" class="headerlink" title="灰色对象扫描规则"></a>灰色对象扫描规则</h5><p>GC worker 与用户 goroutine <strong>并发运行</strong>,不断执行以下过程:</p><ol><li>从灰色标记队列中取出一个灰色对象</li><li>扫描该对象的所有指针字段</li><li>对每一个被引用的对象:<ul><li>若为白色 → 标记为灰色,并加入灰色队列</li></ul></li><li>当前灰色对象扫描完成后:<ul><li>标记为黑色</li><li>从灰色集合移动到黑色集合</li></ul></li></ol><p>例如:</p><ul><li>对象 1、对象 4 初始为灰色</li><li>扫描对象 1,发现对象 2、对象 7<ul><li>对象 2、对象 7 由白色 → 灰色</li></ul></li><li>对象 1、对象 4 扫描完成后 → 黑色</li></ul><h5 id="重复扫描直到灰色集合为空"><a href="#重复扫描直到灰色集合为空" class="headerlink" title="重复扫描直到灰色集合为空"></a>重复扫描直到灰色集合为空</h5><p>上述过程不断重复:</p><ul><li>灰色对象 → 黑色</li><li>白色对象 → 灰色</li></ul><p>直到:</p><ul><li>灰色标记队列为空</li><li>所有可达对象均已被访问</li></ul><p>此时,内存中的对象只剩下两种颜色:</p><ul><li><strong>黑色</strong>:从 Root 可达的存活对象</li><li><strong>白色</strong>:从 Root 不可达的对象(垃圾)</li></ul><h5 id="并发正确性的保证-Go-1-5-的写屏障"><a href="#并发正确性的保证-Go-1-5-的写屏障" class="headerlink" title="并发正确性的保证:Go 1.5 的写屏障"></a>并发正确性的保证:Go 1.5 的写屏障</h5><p>在并发标记期间,用户程序仍可能修改对象引用关系,例如:</p><ul><li>黑对象新增指向白对象的引用</li><li>可能导致白对象被错误回收</li></ul><p>为解决该问题,Go 1.5 引入 <strong>写屏障机制</strong>:</p><p>在指针写操作时:</p><ul><li>若新写入的对象尚未被标记</li><li>则将其标记为灰色</li></ul><p>写屏障的作用是:</p><blockquote><p><strong>防止在并发标记过程中遗漏任何逻辑上可达的对象</strong></p></blockquote><p>代价是:</p><ul><li>每一次指针写入都会增加额外开销</li><li>写密集型程序在 Go 1.5 中 GC 成本明显偏高</li></ul><h4 id="标记结束确认-Mark-Termination-STW"><a href="#标记结束确认-Mark-Termination-STW" class="headerlink" title="标记结束确认:Mark Termination(STW)"></a>标记结束确认:Mark Termination(STW)</h4><p>当并发标记完成后,会进行下面的操作。</p><h5 id="Stop-The-World-第二次"><a href="#Stop-The-World-第二次" class="headerlink" title="Stop-The-World(第二次)"></a>Stop-The-World(第二次)</h5><p>Go 1.5 会再次进入 STW,用于:</p><ol><li>确认灰色队列为空</li><li>确保没有遗漏的标记工作</li><li>关闭写屏障</li></ol><p>这一次 STW 相对较短,但仍然存在。</p><h4 id="清除阶段-Sweep"><a href="#清除阶段-Sweep" class="headerlink" title="清除阶段(Sweep)"></a>清除阶段(Sweep)</h4><h5 id="并发清除白色对象"><a href="#并发清除白色对象" class="headerlink" title="并发清除白色对象"></a>并发清除白色对象</h5><p>GC 进入清除阶段:</p><ul><li>遍历堆中的内存 span</li><li>回收所有 <strong>白色对象</strong></li><li>黑色对象继续存活</li><li>回收的内存重新进入分配池</li></ul><p>清除阶段是 <strong>并发执行的,不再 STW</strong>。</p><h3 id="Go-V1-5-的屏障机制"><a href="#Go-V1-5-的屏障机制" class="headerlink" title="Go V1.5 的屏障机制"></a>Go V1.5 的屏障机制</h3><p>并发标记下的问题本质:三色不变式</p><p>在 <strong>并发三色标记阶段</strong>,用户程序(mutator)仍在运行,可能发生:</p><ul><li>黑对象新增指向白对象的引用</li><li>灰对象到白对象的可达路径被删除</li></ul><p>如果不加限制,会直接导致:<strong>仍被程序使用的对象,被错误回收</strong></p><p>为此,Go 1.5 通过 <strong>三色不变式</strong> 和 <strong>写屏障</strong> 来约束并发行为。</p><h4 id="并发标记下的问题本质-三色不变式"><a href="#并发标记下的问题本质-三色不变式" class="headerlink" title="并发标记下的问题本质:三色不变式"></a>并发标记下的问题本质:三色不变式</h4><h5 id="强三色不变式"><a href="#强三色不变式" class="headerlink" title="强三色不变式"></a>强三色不变式</h5><ul><li><strong>不允许黑色对象直接引用白色对象</strong></li><li>一旦发生引用:白色对象必须立即转为灰色</li></ul><p>特征:</p><ul><li>安全性强</li><li>写屏障成本高</li><li>标记精度高</li></ul><h5 id="弱三色不变式"><a href="#弱三色不变式" class="headerlink" title="弱三色不变式"></a>弱三色不变式</h5><ul><li>允许黑色对象引用白色对象</li><li>但要求:该白色对象仍然被某个灰色对象间接保护</li></ul><p>特征:</p><ul><li>屏障成本低</li><li>允许对象“多活一轮”</li><li>标记精度较低</li></ul><h4 id="插入屏障-Insertion-Barrier"><a href="#插入屏障-Insertion-Barrier" class="headerlink" title="插入屏障(Insertion Barrier)"></a>插入屏障(Insertion Barrier)</h4><ul><li>满足 <strong>强三色不变式</strong></li><li>防止黑对象新增指向白对象的引用</li></ul><p>当执行指针写入操作:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">A.field = B</span><br></pre></td></tr></table></figure><p><strong>若:A 为黑色, B 为白色。则将 B 标记为灰色</strong></p><blockquote><p>插入屏障 <strong>仅用于堆对象</strong> <strong>不作用于栈对象</strong><br>原因:栈写频繁,屏障成本不可接受</p></blockquote><blockquote><p><strong>栈上新增的白色对象引用,无法被插入屏障捕获</strong></p></blockquote><p>因此,在 Go 1.5 中:</p><p>并发标记结束后,<strong>必须对栈进行一次 STW 重新扫描</strong>,以防止栈中仍存在“黑 → 白”的未保护引用</p><p>这正是 Go 1.5 <strong>GC 停顿的主要来源之一</strong>。</p><h4 id="删除屏障-Deletion-Barrier"><a href="#删除屏障-Deletion-Barrier" class="headerlink" title="删除屏障(Deletion Barrier)"></a>删除屏障(Deletion Barrier)</h4><ul><li>满足 <strong>弱三色不变式</strong></li><li>防止“可达路径被并发删除”</li></ul><p>当一个对象引用被删除或覆盖时:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">old = A.field</span><br><span class="line">A.field = nil</span><br></pre></td></tr></table></figure><p><strong>若 <code>old</code> 为白色或灰色:将 <code>old</code> 标记为灰色</strong></p><blockquote><p><strong>保证:即使引用被删除或覆盖,原可达对象仍能在本轮 GC 中被扫描</strong></p></blockquote><p>被删除但已标灰的对象:<strong>可能在本轮 GC 中存活</strong>,只能在下一轮 GC 中回收</p><blockquote><p>删除屏障 <strong>牺牲回收精度,换取并发安全性</strong></p></blockquote><h3 id="Go-V1-8-的混合写屏障"><a href="#Go-V1-8-的混合写屏障" class="headerlink" title="Go V1.8 的混合写屏障"></a>Go V1.8 的混合写屏障</h3><p>在 Go 1.5 中:</p><ol><li>插入屏障 保证强三色不变式,结束阶段必须 STW 重新扫描栈</li><li>删除屏障 无需栈 Re-scan ,GC 回收精度低(快照语义)</li></ol><p>Go 1.8 引入了混合写屏障机制(Hybrid Write Barrier),避免了对栈 Re-scan(重新扫 描)的过程,这也极大地减少了 STW 的时间,同时结合了插入写屏障和删除写屏障两者的优点</p><h4 id="混合写屏障-Hybrid-Write-Barrier-规则"><a href="#混合写屏障-Hybrid-Write-Barrier-规则" class="headerlink" title="混合写屏障(Hybrid Write Barrier)规则"></a>混合写屏障(Hybrid Write Barrier)规则</h4><blockquote><p><strong>把“栈”在 GC 语义上直接视为“永远安全的黑色区域”,通过“堆上的写屏障 + 初始栈全黑”,同时覆盖插入与删除两种风险。</strong></p></blockquote><ol><li>GC 开始时,一次性 STW 扫描栈。<strong>所有栈可达对象全部标记为黑色,栈对象在整个 GC 期间都被视为安全,不会对栈进行第二次扫描。</strong></li><li>GC 期间,栈上新创建的对象直接为黑色。<strong>新对象如果分配在栈上,直接视为黑色。栈对象永远不会成为白色</strong></li><li>堆上“删除引用” → 被删除对象标记为灰色。</li><li>堆上“新增引用” → 被新增对象标记为灰色。</li></ol><p>混合写屏障实际上满足的是一种变形的弱三色不变式。</p><blockquote><p><strong>写屏障只作用于堆的写操作,栈写入永远不出发屏障。</strong></p></blockquote><h4 id="一些理解上的问题"><a href="#一些理解上的问题" class="headerlink" title="一些理解上的问题"></a>一些理解上的问题</h4><h5 id="Go-的-GC-回收哪些内存-栈和堆分别由谁管理"><a href="#Go-的-GC-回收哪些内存-栈和堆分别由谁管理" class="headerlink" title="Go 的 GC 回收哪些内存?栈和堆分别由谁管理?"></a>Go 的 GC 回收哪些内存?栈和堆分别由谁管理?</h5><ul><li><strong>GC 只回收堆内存</strong></li><li><strong>栈内存不归 GC 管理</strong></li></ul><table><thead><tr><th>内存类型</th><th>管理者</th><th>回收方式</th></tr></thead><tbody><tr><td>栈</td><td>编译器 + runtime</td><td>函数返回直接回收</td></tr><tr><td>堆</td><td>GC</td><td>标记-清除</td></tr></tbody></table><p>栈帧随着函数调用&#x2F;返回自动创建和销毁,回收成本极低,不适合也不需要 GC 介入。</p><h5 id="为什么栈对象“不会被-GC-回收”-却仍然参与-GC"><a href="#为什么栈对象“不会被-GC-回收”-却仍然参与-GC" class="headerlink" title="为什么栈对象“不会被 GC 回收”,却仍然参与 GC?"></a>为什么栈对象“不会被 GC 回收”,却仍然参与 GC?</h5><ul><li><strong>栈对象本身不回收</strong></li><li>栈上的指针是 GC 的根(Root Set)</li><li><strong>堆对象会被栈或全局变量间接引用</strong></li></ul><blockquote><p>GC 需要判断 <strong>堆对象</strong> 是否还能被 <strong>栈或全局变量间接引用</strong>,以便确定堆对象是否可以被回收。</p></blockquote><h5 id="混合写屏障带来了什么-代价是什么"><a href="#混合写屏障带来了什么-代价是什么" class="headerlink" title="混合写屏障带来了什么,代价是什么?"></a>混合写屏障带来了什么,代价是什么?</h5><p>混合写屏障通过 对堆做插入屏障和删除屏障,对栈特殊处理不做屏障。</p><p>好处:</p><ul><li><strong>不会误删仍可达的堆对象</strong></li><li>不需要 STW 栈 re-scan,极大缩短 STW 时间</li><li>栈完全无屏障,极大降低写路径成本</li></ul><p>缺点:</p><ul><li>回收精度降低(延迟回收):不保证本轮 GC 回收所有垃圾,某些对象可能延迟一轮回收</li><li>内存峰值略有抬高:因为保护对象增多,延迟回收</li><li>不是严格实时的 GC</li></ul><h3 id="一些源码"><a href="#一些源码" class="headerlink" title="一些源码"></a>一些源码</h3><p>参考 Go 版本为 1.25.5</p><h4 id="整体流程"><a href="#整体流程" class="headerlink" title="整体流程"></a>整体流程</h4><ol><li>gcStart() 是 GC 的入口</li><li>防止 GC 在“不安全的执行上下文”(<code>gp == mp.g0 || mp.locks &gt; 1 || mp.preemptoff != &quot;&quot;</code>)中启动<ul><li><code>gp == mp.g0</code> 当前在 system stack</li><li><code>mp.locks &gt; 1</code> 持有 runtime 锁</li><li><code>mp.preemptoff != &quot;&quot;</code> 明确禁止抢占</li></ul></li><li>补扫 sweep,<code>sweepone()</code>,扫描并释放上一轮 GC 未 sweep 完的 span ,保证 heap 状态在 GC 开始前“干净”</li><li>获取“GC 启动权”,<strong>防止多个 goroutine 同时启动 GC</strong></li><li>STW<ul><li><code>stw = stopTheWorldWithSema(stwGCSweepTerm)</code> 停止所有 P ,停止所有 G ,建立 STW 边界<ul><li><code>casGToWaitingForSuspendG(getg().m.curg, _Grunning, waitReasonStoppingTheWorld)</code> 把“当前发起 STW 的 goroutine”标记为 _Gwaiting。允许 GC mark worker 扫描它的栈,tracer 观察它的状态,GC 和 调度器 的交叉点,防止死锁。</li><li><code>sched.stopwait = gomaxprocs;sched.gcwaiting.Store(true);preemptall()</code> stopwait&#x3D;GOMAXPROCS,设置全局 GC 停顿标志,向所有的 P 发起抢占。</li><li><code>for sched.stopwait &gt; 0 { ...</code> 等待所有 P 停止,周期性抢占(preempt),防止遗漏</li><li>STW 成功后的校验:统计每个 P 的停顿 CPU 时间,为 GC pause time 记账</li><li>STW 最终状态:<code>worldStopped();casgstatus(getg().m.curg, _Gwaiting, _Grunning)</code></li></ul></li><li><code>finishsweep_m()</code> 确保所有 span 都处于可分配状态,为 mark 阶段准备 heap。<strong>语义上确保上一轮 GC 的 sweep 在 mark 开始前彻底完成</strong></li><li><code>clearpools()</code> 清空 sync.Pool,防止池中对象延迟一个 GC 周期回收</li></ul></li><li>GC 全局状态初始化<ul><li><code>gcController.startCycle(now, int(gomaxprocs), trigger)</code> 控制 GC 频率,计算 assist 比例,决定 mutator 要“干多少 GC 活”</li><li><code>gcCPULimiter.startGCTransition(true, now)</code> 控制 GC 占用 CPU 的上限,防止 GC 抢光 mutator CPU</li></ul></li><li>进入 mark 阶段<ul><li><code>setGCPhase(_GCmark)</code> 切换 GC phase(全局语义切换),写屏障将开始生效,三色标记语义正式成立<ul><li><code>_GCoff</code>:本轮 GC 已完成标记,允许 sweep,关闭写屏障</li><li><code>_GCmark</code>:三色标记生效,写屏障开启,新分配对象直接当作黑色,灰色队列开始 drain</li><li><code>_GCmarktermination</code>:写屏障仍然开启,所有 P 必须协助 GC,即将切换到 <code>GCoff</code>。这是为了<strong>安全地过渡</strong></li></ul></li><li><code>gcBgMarkPrepare()</code> 构造一个“永远不会因为 worker 不够而提前结束 mark 的判定条件”,<strong>它并不启动任何 goroutine,真正启动 mark worker 的地方是 STW 之前的 <code>gcBgMarkStartWorkers()</code></strong>。</li><li><code>gcPrepareMarkRoots()</code> 准备 root,标记:goroutine 栈、全局变量、runtime roots。为后续 gcMarkRoot 做准备,“栈只扫描一次”的入口就在这之后<br>把“本轮 GC 需要扫描的所有 root”,拆分成可并发调度的 root jobs<ul><li>Global Roots(data 已初始化的全局变量 &#x2F; bss 零值全局变量):<strong>按 block 切分,避免一个巨大 module 的 root 扫描被一个 worker 独占</strong></li><li>Span Roots(finalizer specials):带 specials 的 span 主要是:finalizer、weak references(未来)</li><li>Stack Roots:栈的 root</li><li>Fixed Roots(runtime 内部):不属于 data&#x2F;bss、不在任何 goroutine 栈上、但 runtime 必须长期持有、且可能指向堆对象的全局结构(调度器相关结构、所有 goroutine 管理结构、空闲 G &#x2F; stack 复用池、定时器系统(timer heap)、同步原语等待队列(sudog)等等)。<ul><li>生命周期 ≈ 程序生命周期</li><li>不在用户可见的全局变量中</li><li><strong>只能由 runtime 主动扫描</strong></li></ul></li><li><code>gcMarkTinyAllocs()</code> tiny alloc 特殊处理,tinyalloc block 直接置黑,避免分配路径插入额外屏障</li><li><code>atomic.Store(&amp;gcBlackenEnabled, 1)</code> 分配会“强迫 mutator 帮 GC 干活”</li></ul></li></ul></li><li>恢复世界,进入并发标记 <code>now = startTheWorldWithSema(0, stw)</code> mutator 恢复运行,写屏障开启,并发 mark worker 开始 drain 灰色队列。<strong>GC 的“并发标记阶段”正式开始</strong><ul><li><code>assertWorldStopped();mp := acquirem()</code> 恢复世界前的准备,保证世界确实是停的、当前 P 不会被抢占,防止 P 错乱</li><li><code>list, delta := netpoll(0);injectglist(&amp;list);netpollAdjustWaiters(delta)</code> 处理 STW 期间积累的外部事件,是 I&#x2F;O 与 STW 的边界处理</li><li><code>p1 := procresize(procs);sched.gcwaiting.Store(false)</code> 重新建立 P 的规模与归属关系,处理 GOMAXPROCS 变化、清除 gcwaiting 、唤醒 sysmon(如果需要)</li><li>把 P 重新交还给 M(或创建新 M),是调度恢复的核心</li><li><code>sched.stwTotalTimeGC.record(...);trace.STWDone()</code> 记录 STW 总耗时和 trace</li><li><code>wakep()</code> 唤醒调度系统,防止 runnable G 堆积 、P 全部 idle</li></ul></li></ol><blockquote><p>可以发现,这里的 gcStart 流程只是为 GC 进行环境的准备,保证上一次 GC 完全结束,并且拿到所有的 root。<br>真正的标记阶段是 gcStart 启动的异步的,在 STW 之后才开始的。<br>并且这里没有真正的对白色对象进行清理。</p><p><strong>Go GC 的“清理阶段(Sweep)”不在 gcStart 里作为一个完整阶段出现,是因为:Sweep 是「跨 GC 周期、并发 + 增量」执行的,而不是一个集中、封闭的阶段。</strong><br>gcStart 中会有一部分清理(sweepone 和 finishsweep_m),但<strong>真正的大部分清理工作是在 GC mark 结束之后,下一轮 GC 开始之前,且与 mutator 并发执行的。</strong></p></blockquote><p>真正清理阶段:</p><ol><li>Sweep 的入口:<code>gcMarkTermination()</code></li><li>Sweep 的执行:<code>sweepone() / sweepLocked()</code>,<strong>Sweep 不是一个统一的 loop,而是被“顺便”触发的</strong>。<ul><li>分配内存</li><li>后台 sweep goroutine</li><li>下一轮 GC 启动前</li><li>heap 不足</li></ul></li><li>后台 sweep worker,Go 有专门的后台 sweep worker:在 GC 结束后启动,与 mutator 并发,慢慢把 span 扫干净。<code>bgsweep() / sweepone()</code></li></ol><h4 id="源码参考"><a href="#源码参考" class="headerlink" title="源码参考"></a>源码参考</h4><p>入口:<code>gcStart()</code>:go1.25.5&#x2F;src&#x2F;runtime&#x2F;mgc.go:643</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gcStart starts the GC. It transitions from _GCoff to _GCmark (if</span></span><br><span class="line"><span class="comment">// debug.gcstoptheworld == 0) or performs all of GC (if</span></span><br><span class="line"><span class="comment">// debug.gcstoptheworld != 0).</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// This may return without performing this transition in some cases,</span></span><br><span class="line"><span class="comment">// such as when called on a system stack or with locks held.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">gcStart</span><span class="params">(trigger gcTrigger)</span></span> &#123;</span><br><span class="line"><span class="comment">// Since this is called from malloc and malloc is called in</span></span><br><span class="line"><span class="comment">// the guts of a number of libraries that might be holding</span></span><br><span class="line"><span class="comment">// locks, don&#x27;t attempt to start GC in non-preemptible or</span></span><br><span class="line"><span class="comment">// potentially unstable situations.</span></span><br><span class="line">mp := acquirem()</span><br><span class="line"><span class="keyword">if</span> gp := getg(); gp == mp.g0 || mp.locks &gt; <span class="number">1</span> || mp.preemptoff != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">releasem(mp)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">releasem(mp)</span><br><span class="line">mp = <span class="literal">nil</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> gp := getg(); gp.bubble != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// Disassociate the G from its synctest bubble while allocating.</span></span><br><span class="line"><span class="comment">// This is less elegant than incrementing the group&#x27;s active count,</span></span><br><span class="line"><span class="comment">// but avoids any contamination between GC and synctest.</span></span><br><span class="line">bubble := gp.bubble</span><br><span class="line">gp.bubble = <span class="literal">nil</span></span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">gp.bubble = bubble</span><br><span class="line">&#125;()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Pick up the remaining unswept/not being swept spans concurrently</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// This shouldn&#x27;t happen if we&#x27;re being invoked in background</span></span><br><span class="line"><span class="comment">// mode since proportional sweep should have just finished</span></span><br><span class="line"><span class="comment">// sweeping everything, but rounding errors, etc, may leave a</span></span><br><span class="line"><span class="comment">// few spans unswept. In forced mode, this is necessary since</span></span><br><span class="line"><span class="comment">// GC can be forced at any point in the sweeping cycle.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// We check the transition condition continuously here in case</span></span><br><span class="line"><span class="comment">// this G gets delayed in to the next GC cycle.</span></span><br><span class="line"><span class="keyword">for</span> trigger.test() &amp;&amp; sweepone() != ^<span class="type">uintptr</span>(<span class="number">0</span>) &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Perform GC initialization and the sweep termination</span></span><br><span class="line"><span class="comment">// transition.</span></span><br><span class="line">semacquire(&amp;work.startSema)</span><br><span class="line"><span class="comment">// Re-check transition condition under transition lock.</span></span><br><span class="line"><span class="keyword">if</span> !trigger.test() &#123;</span><br><span class="line">semrelease(&amp;work.startSema)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// In gcstoptheworld debug mode, upgrade the mode accordingly.</span></span><br><span class="line"><span class="comment">// We do this after re-checking the transition condition so</span></span><br><span class="line"><span class="comment">// that multiple goroutines that detect the heap trigger don&#x27;t</span></span><br><span class="line"><span class="comment">// start multiple STW GCs.</span></span><br><span class="line">mode := gcBackgroundMode</span><br><span class="line"><span class="keyword">if</span> debug.gcstoptheworld == <span class="number">1</span> &#123;</span><br><span class="line">mode = gcForceMode</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> debug.gcstoptheworld == <span class="number">2</span> &#123;</span><br><span class="line">mode = gcForceBlockMode</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Ok, we&#x27;re doing it! Stop everybody else</span></span><br><span class="line">semacquire(&amp;gcsema)</span><br><span class="line">semacquire(&amp;worldsema)</span><br><span class="line"></span><br><span class="line"><span class="comment">// For stats, check if this GC was forced by the user.</span></span><br><span class="line"><span class="comment">// Update it under gcsema to avoid gctrace getting wrong values.</span></span><br><span class="line">work.userForced = trigger.kind == gcTriggerCycle</span><br><span class="line"></span><br><span class="line">trace := traceAcquire()</span><br><span class="line"><span class="keyword">if</span> trace.ok() &#123;</span><br><span class="line">trace.GCStart()</span><br><span class="line">traceRelease(trace)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Check that all Ps have finished deferred mcache flushes.</span></span><br><span class="line"><span class="keyword">for</span> _, p := <span class="keyword">range</span> allp &#123;</span><br><span class="line"><span class="keyword">if</span> fg := p.mcache.flushGen.Load(); fg != mheap_.sweepgen &#123;</span><br><span class="line"><span class="built_in">println</span>(<span class="string">&quot;runtime: p&quot;</span>, p.id, <span class="string">&quot;flushGen&quot;</span>, fg, <span class="string">&quot;!= sweepgen&quot;</span>, mheap_.sweepgen)</span><br><span class="line">throw(<span class="string">&quot;p mcache not flushed&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Initialize ptrBuf if necessary.</span></span><br><span class="line"><span class="keyword">if</span> goexperiment.GreenTeaGC &amp;&amp; p.gcw.ptrBuf == <span class="literal">nil</span> &#123;</span><br><span class="line">p.gcw.ptrBuf = (*[gc.PageSize / goarch.PtrSize]<span class="type">uintptr</span>)(persistentalloc(gc.PageSize, goarch.PtrSize, &amp;memstats.gcMiscSys))</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">gcBgMarkStartWorkers()</span><br><span class="line"></span><br><span class="line">systemstack(gcResetMarkState)</span><br><span class="line"></span><br><span class="line">work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs</span><br><span class="line"><span class="keyword">if</span> work.stwprocs &gt; numCPUStartup &#123;</span><br><span class="line"><span class="comment">// This is used to compute CPU time of the STW phases, so it</span></span><br><span class="line"><span class="comment">// can&#x27;t be more than the CPU count, even if GOMAXPROCS is.</span></span><br><span class="line">work.stwprocs = numCPUStartup</span><br><span class="line">&#125;</span><br><span class="line">work.heap0 = gcController.heapLive.Load()</span><br><span class="line">work.pauseNS = <span class="number">0</span></span><br><span class="line">work.mode = mode</span><br><span class="line"></span><br><span class="line">now := nanotime()</span><br><span class="line">work.tSweepTerm = now</span><br><span class="line"><span class="keyword">var</span> stw worldStop</span><br><span class="line">systemstack(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">stw = stopTheWorldWithSema(stwGCSweepTerm)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Accumulate fine-grained stopping time.</span></span><br><span class="line">work.cpuStats.accumulateGCPauseTime(stw.stoppingCPUTime, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Finish sweep before we start concurrent scan.</span></span><br><span class="line">systemstack(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">finishsweep_m()</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// clearpools before we start the GC. If we wait the memory will not be</span></span><br><span class="line"><span class="comment">// reclaimed until the next GC cycle.</span></span><br><span class="line">clearpools()</span><br><span class="line"></span><br><span class="line">work.cycles.Add(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Assists and workers can start the moment we start</span></span><br><span class="line"><span class="comment">// the world.</span></span><br><span class="line">gcController.startCycle(now, <span class="type">int</span>(gomaxprocs), trigger)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Notify the CPU limiter that assists may begin.</span></span><br><span class="line">gcCPULimiter.startGCTransition(<span class="literal">true</span>, now)</span><br><span class="line"></span><br><span class="line"><span class="comment">// In STW mode, disable scheduling of user Gs. This may also</span></span><br><span class="line"><span class="comment">// disable scheduling of this goroutine, so it may block as</span></span><br><span class="line"><span class="comment">// soon as we start the world again.</span></span><br><span class="line"><span class="keyword">if</span> mode != gcBackgroundMode &#123;</span><br><span class="line">schedEnableUser(<span class="literal">false</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Enter concurrent mark phase and enable</span></span><br><span class="line"><span class="comment">// write barriers.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Because the world is stopped, all Ps will</span></span><br><span class="line"><span class="comment">// observe that write barriers are enabled by</span></span><br><span class="line"><span class="comment">// the time we start the world and begin</span></span><br><span class="line"><span class="comment">// scanning.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Write barriers must be enabled before assists are</span></span><br><span class="line"><span class="comment">// enabled because they must be enabled before</span></span><br><span class="line"><span class="comment">// any non-leaf heap objects are marked. Since</span></span><br><span class="line"><span class="comment">// allocations are blocked until assists can</span></span><br><span class="line"><span class="comment">// happen, we want to enable assists as early as</span></span><br><span class="line"><span class="comment">// possible.</span></span><br><span class="line">setGCPhase(_GCmark)</span><br><span class="line"></span><br><span class="line">gcBgMarkPrepare() <span class="comment">// Must happen before assists are enabled.</span></span><br><span class="line">gcPrepareMarkRoots()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Mark all active tinyalloc blocks. Since we&#x27;re</span></span><br><span class="line"><span class="comment">// allocating from these, they need to be black like</span></span><br><span class="line"><span class="comment">// other allocations. The alternative is to blacken</span></span><br><span class="line"><span class="comment">// the tiny block on every allocation from it, which</span></span><br><span class="line"><span class="comment">// would slow down the tiny allocator.</span></span><br><span class="line">gcMarkTinyAllocs()</span><br><span class="line"></span><br><span class="line"><span class="comment">// At this point all Ps have enabled the write</span></span><br><span class="line"><span class="comment">// barrier, thus maintaining the no white to</span></span><br><span class="line"><span class="comment">// black invariant. Enable mutator assists to</span></span><br><span class="line"><span class="comment">// put back-pressure on fast allocating</span></span><br><span class="line"><span class="comment">// mutators.</span></span><br><span class="line">atomic.Store(&amp;gcBlackenEnabled, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// In STW mode, we could block the instant systemstack</span></span><br><span class="line"><span class="comment">// returns, so make sure we&#x27;re not preemptible.</span></span><br><span class="line">mp = acquirem()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Update the CPU stats pause time.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Use maxprocs instead of stwprocs here because the total time</span></span><br><span class="line"><span class="comment">// computed in the CPU stats is based on maxprocs, and we want them</span></span><br><span class="line"><span class="comment">// to be comparable.</span></span><br><span class="line">work.cpuStats.accumulateGCPauseTime(nanotime()-stw.finishedStopping, work.maxprocs)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Concurrent mark.</span></span><br><span class="line">systemstack(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">now = startTheWorldWithSema(<span class="number">0</span>, stw)</span><br><span class="line">work.pauseNS += now - stw.startedStopping</span><br><span class="line">work.tMark = now</span><br><span class="line"></span><br><span class="line"><span class="comment">// Release the CPU limiter.</span></span><br><span class="line">gcCPULimiter.finishGCTransition(now)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Release the world sema before Gosched() in STW mode</span></span><br><span class="line"><span class="comment">// because we will need to reacquire it later but before</span></span><br><span class="line"><span class="comment">// this goroutine becomes runnable again, and we could</span></span><br><span class="line"><span class="comment">// self-deadlock otherwise.</span></span><br><span class="line">semrelease(&amp;worldsema)</span><br><span class="line">releasem(mp)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Make sure we block instead of returning to user code</span></span><br><span class="line"><span class="comment">// in STW mode.</span></span><br><span class="line"><span class="keyword">if</span> mode != gcBackgroundMode &#123;</span><br><span class="line">Gosched()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">semrelease(&amp;work.startSema)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>setGCPhase(_GCmark)</code> 切换 GC phase(全局语义切换):go1.25.5&#x2F;src&#x2F;runtime&#x2F;mgc.go:255</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Garbage collector phase.</span></span><br><span class="line"><span class="comment">// Indicates to write barrier and synchronization task to perform.</span></span><br><span class="line"><span class="keyword">var</span> gcphase <span class="type">uint32</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// The compiler knows about this variable.</span></span><br><span class="line"><span class="comment">// If you change it, you must change builtin/runtime.go, too.</span></span><br><span class="line"><span class="comment">// If you change the first four bytes, you must also change the write</span></span><br><span class="line"><span class="comment">// barrier insertion code.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// writeBarrier should be an internal detail,</span></span><br><span class="line"><span class="comment">// but widely used packages access it using linkname.</span></span><br><span class="line"><span class="comment">// Notable members of the hall of shame include:</span></span><br><span class="line"><span class="comment">//   - github.com/bytedance/sonic</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Do not remove or change the type signature.</span></span><br><span class="line"><span class="comment">// See go.dev/issue/67401.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//go:linkname writeBarrier</span></span><br><span class="line"><span class="keyword">var</span> writeBarrier <span class="keyword">struct</span> &#123;</span><br><span class="line">enabled <span class="type">bool</span>    <span class="comment">// compiler emits a check of this before calling write barrier</span></span><br><span class="line">pad     [<span class="number">3</span>]<span class="type">byte</span> <span class="comment">// compiler uses 32-bit load for &quot;enabled&quot; field</span></span><br><span class="line">alignme <span class="type">uint64</span>  <span class="comment">// guarantee alignment so that compiler can use a 32 or 64-bit load</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">_GCoff             = <span class="literal">iota</span> <span class="comment">// GC not running; sweeping in background, write barrier disabled</span></span><br><span class="line">_GCmark                   <span class="comment">// GC marking roots and workbufs: allocate black, write barrier ENABLED</span></span><br><span class="line">_GCmarktermination        <span class="comment">// GC mark termination: allocate black, P&#x27;s help GC, write barrier ENABLED</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">//go:nosplit</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">setGCPhase</span><span class="params">(x <span class="type">uint32</span>)</span></span> &#123;</span><br><span class="line">atomic.Store(&amp;gcphase, x)</span><br><span class="line">writeBarrier.enabled = gcphase == _GCmark || gcphase == _GCmarktermination</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>gcBgMarkPrepare</code>:go1.25.5&#x2F;src&#x2F;runtime&#x2F;mgc.go:1393</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gcBgMarkPrepare sets up state for background marking.</span></span><br><span class="line"><span class="comment">// Mutator assists must not yet be enabled.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">gcBgMarkPrepare</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// Background marking will stop when the work queues are empty</span></span><br><span class="line"><span class="comment">// and there are no more workers (note that, since this is</span></span><br><span class="line"><span class="comment">// concurrent, this may be a transient state, but mark</span></span><br><span class="line"><span class="comment">// termination will clean it up). Between background workers</span></span><br><span class="line"><span class="comment">// and assists, we don&#x27;t really know how many workers there</span></span><br><span class="line"><span class="comment">// will be, so we pretend to have an arbitrarily large number</span></span><br><span class="line"><span class="comment">// of workers, almost all of which are &quot;waiting&quot;. While a</span></span><br><span class="line"><span class="comment">// worker is working it decrements nwait. If nproc == nwait,</span></span><br><span class="line"><span class="comment">// there are no workers.</span></span><br><span class="line">work.nproc = ^<span class="type">uint32</span>(<span class="number">0</span>)</span><br><span class="line">work.nwait = ^<span class="type">uint32</span>(<span class="number">0</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>gcPrepareMarkRoots</code>:go1.25.5&#x2F;src&#x2F;runtime&#x2F;mgcmark.go:60</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gcPrepareMarkRoots queues root scanning jobs (stacks, globals, and</span></span><br><span class="line"><span class="comment">// some miscellany) and initializes scanning-related state.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// The world must be stopped.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">gcPrepareMarkRoots</span><span class="params">()</span></span> &#123;</span><br><span class="line">assertWorldStopped()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Compute how many data and BSS root blocks there are.</span></span><br><span class="line">nBlocks := <span class="function"><span class="keyword">func</span><span class="params">(bytes <span class="type">uintptr</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="type">int</span>(divRoundUp(bytes, rootBlockBytes))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">work.nDataRoots = <span class="number">0</span></span><br><span class="line">work.nBSSRoots = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Scan globals.</span></span><br><span class="line"><span class="keyword">for</span> _, datap := <span class="keyword">range</span> activeModules() &#123;</span><br><span class="line">nDataRoots := nBlocks(datap.edata - datap.data)</span><br><span class="line"><span class="keyword">if</span> nDataRoots &gt; work.nDataRoots &#123;</span><br><span class="line">work.nDataRoots = nDataRoots</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">nBSSRoots := nBlocks(datap.ebss - datap.bss)</span><br><span class="line"><span class="keyword">if</span> nBSSRoots &gt; work.nBSSRoots &#123;</span><br><span class="line">work.nBSSRoots = nBSSRoots</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Scan span roots for finalizer specials.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// We depend on addfinalizer to mark objects that get</span></span><br><span class="line"><span class="comment">// finalizers after root marking.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// We&#x27;re going to scan the whole heap (that was available at the time the</span></span><br><span class="line"><span class="comment">// mark phase started, i.e. markArenas) for in-use spans which have specials.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Break up the work into arenas, and further into chunks.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Snapshot heapArenas as markArenas. This snapshot is safe because heapArenas</span></span><br><span class="line"><span class="comment">// is append-only.</span></span><br><span class="line">mheap_.markArenas = mheap_.heapArenas[:<span class="built_in">len</span>(mheap_.heapArenas):<span class="built_in">len</span>(mheap_.heapArenas)]</span><br><span class="line">work.nSpanRoots = <span class="built_in">len</span>(mheap_.markArenas) * (pagesPerArena / pagesPerSpanRoot)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Scan stacks.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Gs may be created after this point, but it&#x27;s okay that we</span></span><br><span class="line"><span class="comment">// ignore them because they begin life without any roots, so</span></span><br><span class="line"><span class="comment">// there&#x27;s nothing to scan, and any roots they create during</span></span><br><span class="line"><span class="comment">// the concurrent phase will be caught by the write barrier.</span></span><br><span class="line">work.stackRoots = allGsSnapshot()</span><br><span class="line">work.nStackRoots = <span class="built_in">len</span>(work.stackRoots)</span><br><span class="line"></span><br><span class="line">work.markrootNext = <span class="number">0</span></span><br><span class="line">work.markrootJobs = <span class="type">uint32</span>(fixedRootCount + work.nDataRoots + work.nBSSRoots + work.nSpanRoots + work.nStackRoots)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Calculate base indexes of each root type</span></span><br><span class="line">work.baseData = <span class="type">uint32</span>(fixedRootCount)</span><br><span class="line">work.baseBSS = work.baseData + <span class="type">uint32</span>(work.nDataRoots)</span><br><span class="line">work.baseSpans = work.baseBSS + <span class="type">uint32</span>(work.nBSSRoots)</span><br><span class="line">work.baseStacks = work.baseSpans + <span class="type">uint32</span>(work.nSpanRoots)</span><br><span class="line">work.baseEnd = work.baseStacks + <span class="type">uint32</span>(work.nStackRoots)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>stopTheWorldWithSema(reason stwReason)</code>:go1.25.5&#x2F;src&#x2F;runtime&#x2F;proc.go:1617<br><code>startTheWorldWithSema(now int64, w worldStop)</code>:go1.25.5&#x2F;src&#x2F;runtime&#x2F;proc.go:1763</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// stopTheWorldWithSema is the core implementation of stopTheWorld.</span></span><br><span class="line"><span class="comment">// The caller is responsible for acquiring worldsema and disabling</span></span><br><span class="line"><span class="comment">// preemption first and then should stopTheWorldWithSema on the system</span></span><br><span class="line"><span class="comment">// stack:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//semacquire(&amp;worldsema, 0)</span></span><br><span class="line"><span class="comment">//m.preemptoff = &quot;reason&quot;</span></span><br><span class="line"><span class="comment">//var stw worldStop</span></span><br><span class="line"><span class="comment">//systemstack(func() &#123;</span></span><br><span class="line"><span class="comment">//stw = stopTheWorldWithSema(reason)</span></span><br><span class="line"><span class="comment">//&#125;)</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// When finished, the caller must either call startTheWorld or undo</span></span><br><span class="line"><span class="comment">// these three operations separately:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//m.preemptoff = &quot;&quot;</span></span><br><span class="line"><span class="comment">//systemstack(func() &#123;</span></span><br><span class="line"><span class="comment">//now = startTheWorldWithSema(stw)</span></span><br><span class="line"><span class="comment">//&#125;)</span></span><br><span class="line"><span class="comment">//semrelease(&amp;worldsema)</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// It is allowed to acquire worldsema once and then execute multiple</span></span><br><span class="line"><span class="comment">// startTheWorldWithSema/stopTheWorldWithSema pairs.</span></span><br><span class="line"><span class="comment">// Other P&#x27;s are able to execute between successive calls to</span></span><br><span class="line"><span class="comment">// startTheWorldWithSema and stopTheWorldWithSema.</span></span><br><span class="line"><span class="comment">// Holding worldsema causes any other goroutines invoking</span></span><br><span class="line"><span class="comment">// stopTheWorld to block.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Returns the STW context. When starting the world, this context must be</span></span><br><span class="line"><span class="comment">// passed to startTheWorldWithSema.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//go:systemstack</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">stopTheWorldWithSema</span><span class="params">(reason stwReason)</span></span> worldStop &#123;</span><br><span class="line"><span class="comment">// Mark the goroutine which called stopTheWorld preemptible so its</span></span><br><span class="line"><span class="comment">// stack may be scanned by the GC or observed by the execution tracer.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// This lets a mark worker scan us or the execution tracer take our</span></span><br><span class="line"><span class="comment">// stack while we try to stop the world since otherwise we could get</span></span><br><span class="line"><span class="comment">// in a mutual preemption deadlock.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// We must not modify anything on the G stack because a stack shrink</span></span><br><span class="line"><span class="comment">// may occur, now that we switched to _Gwaiting, specifically if we&#x27;re</span></span><br><span class="line"><span class="comment">// doing this during the mark phase (mark termination excepted, since</span></span><br><span class="line"><span class="comment">// we know that stack scanning is done by that point). A stack shrink</span></span><br><span class="line"><span class="comment">// is otherwise OK though because in order to return from this function</span></span><br><span class="line"><span class="comment">// (and to leave the system stack) we must have preempted all</span></span><br><span class="line"><span class="comment">// goroutines, including any attempting to scan our stack, in which</span></span><br><span class="line"><span class="comment">// case, any stack shrinking will have already completed by the time we</span></span><br><span class="line"><span class="comment">// exit.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// N.B. The execution tracer is not aware of this status transition and</span></span><br><span class="line"><span class="comment">// andles it specially based on the wait reason.</span></span><br><span class="line">casGToWaitingForSuspendG(getg().m.curg, _Grunning, waitReasonStoppingTheWorld)</span><br><span class="line"></span><br><span class="line">trace := traceAcquire()</span><br><span class="line"><span class="keyword">if</span> trace.ok() &#123;</span><br><span class="line">trace.STWStart(reason)</span><br><span class="line">traceRelease(trace)</span><br><span class="line">&#125;</span><br><span class="line">gp := getg()</span><br><span class="line"></span><br><span class="line"><span class="comment">// If we hold a lock, then we won&#x27;t be able to stop another M</span></span><br><span class="line"><span class="comment">// that is blocked trying to acquire the lock.</span></span><br><span class="line"><span class="keyword">if</span> gp.m.locks &gt; <span class="number">0</span> &#123;</span><br><span class="line">throw(<span class="string">&quot;stopTheWorld: holding locks&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">lock(&amp;sched.lock)</span><br><span class="line">start := nanotime() <span class="comment">// exclude time waiting for sched.lock from start and total time metrics.</span></span><br><span class="line">sched.stopwait = gomaxprocs</span><br><span class="line">sched.gcwaiting.Store(<span class="literal">true</span>)</span><br><span class="line">preemptall()</span><br><span class="line"><span class="comment">// stop current P</span></span><br><span class="line">gp.m.p.ptr().status = _Pgcstop <span class="comment">// Pgcstop is only diagnostic.</span></span><br><span class="line">gp.m.p.ptr().gcStopTime = start</span><br><span class="line">sched.stopwait--</span><br><span class="line"><span class="comment">// try to retake all P&#x27;s in Psyscall status</span></span><br><span class="line">trace = traceAcquire()</span><br><span class="line"><span class="keyword">for</span> _, pp := <span class="keyword">range</span> allp &#123;</span><br><span class="line">s := pp.status</span><br><span class="line"><span class="keyword">if</span> s == _Psyscall &amp;&amp; atomic.Cas(&amp;pp.status, s, _Pgcstop) &#123;</span><br><span class="line"><span class="keyword">if</span> trace.ok() &#123;</span><br><span class="line">trace.ProcSteal(pp, <span class="literal">false</span>)</span><br><span class="line">&#125;</span><br><span class="line">pp.syscalltick++</span><br><span class="line">pp.gcStopTime = nanotime()</span><br><span class="line">sched.stopwait--</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> trace.ok() &#123;</span><br><span class="line">traceRelease(trace)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// stop idle P&#x27;s</span></span><br><span class="line">now := nanotime()</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">pp, _ := pidleget(now)</span><br><span class="line"><span class="keyword">if</span> pp == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br><span class="line">pp.status = _Pgcstop</span><br><span class="line">pp.gcStopTime = nanotime()</span><br><span class="line">sched.stopwait--</span><br><span class="line">&#125;</span><br><span class="line">wait := sched.stopwait &gt; <span class="number">0</span></span><br><span class="line">unlock(&amp;sched.lock)</span><br><span class="line"></span><br><span class="line"><span class="comment">// wait for remaining P&#x27;s to stop voluntarily</span></span><br><span class="line"><span class="keyword">if</span> wait &#123;</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="comment">// wait for 100us, then try to re-preempt in case of any races</span></span><br><span class="line"><span class="keyword">if</span> notetsleep(&amp;sched.stopnote, <span class="number">100</span>*<span class="number">1000</span>) &#123;</span><br><span class="line">noteclear(&amp;sched.stopnote)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br><span class="line">preemptall()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">finish := nanotime()</span><br><span class="line">startTime := finish - start</span><br><span class="line"><span class="keyword">if</span> reason.isGC() &#123;</span><br><span class="line">sched.stwStoppingTimeGC.record(startTime)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">sched.stwStoppingTimeOther.record(startTime)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Double-check we actually stopped everything, and all the invariants hold.</span></span><br><span class="line"><span class="comment">// Also accumulate all the time spent by each P in _Pgcstop up to the point</span></span><br><span class="line"><span class="comment">// where everything was stopped. This will be accumulated into the total pause</span></span><br><span class="line"><span class="comment">// CPU time by the caller.</span></span><br><span class="line">stoppingCPUTime := <span class="type">int64</span>(<span class="number">0</span>)</span><br><span class="line">bad := <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">if</span> sched.stopwait != <span class="number">0</span> &#123;</span><br><span class="line">bad = <span class="string">&quot;stopTheWorld: not stopped (stopwait != 0)&quot;</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">for</span> _, pp := <span class="keyword">range</span> allp &#123;</span><br><span class="line"><span class="keyword">if</span> pp.status != _Pgcstop &#123;</span><br><span class="line">bad = <span class="string">&quot;stopTheWorld: not stopped (status != _Pgcstop)&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> pp.gcStopTime == <span class="number">0</span> &amp;&amp; bad == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">bad = <span class="string">&quot;stopTheWorld: broken CPU time accounting&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">stoppingCPUTime += finish - pp.gcStopTime</span><br><span class="line">pp.gcStopTime = <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> freezing.Load() &#123;</span><br><span class="line"><span class="comment">// Some other thread is panicking. This can cause the</span></span><br><span class="line"><span class="comment">// sanity checks above to fail if the panic happens in</span></span><br><span class="line"><span class="comment">// the signal handler on a stopped thread. Either way,</span></span><br><span class="line"><span class="comment">// we should halt this thread.</span></span><br><span class="line">lock(&amp;deadlock)</span><br><span class="line">lock(&amp;deadlock)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> bad != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">throw(bad)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">worldStopped()</span><br><span class="line"></span><br><span class="line"><span class="comment">// Switch back to _Grunning, now that the world is stopped.</span></span><br><span class="line">casgstatus(getg().m.curg, _Gwaiting, _Grunning)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> worldStop&#123;</span><br><span class="line">reason:           reason,</span><br><span class="line">startedStopping:  start,</span><br><span class="line">finishedStopping: finish,</span><br><span class="line">stoppingCPUTime:  stoppingCPUTime,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// reason is the same STW reason passed to stopTheWorld. start is the start</span></span><br><span class="line"><span class="comment">// time returned by stopTheWorld.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// now is the current time; prefer to pass 0 to capture a fresh timestamp.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// stattTheWorldWithSema returns now.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">startTheWorldWithSema</span><span class="params">(now <span class="type">int64</span>, w worldStop)</span></span> <span class="type">int64</span> &#123;</span><br><span class="line">assertWorldStopped()</span><br><span class="line"></span><br><span class="line">mp := acquirem() <span class="comment">// disable preemption because it can be holding p in a local var</span></span><br><span class="line"><span class="keyword">if</span> netpollinited() &#123;</span><br><span class="line">list, delta := netpoll(<span class="number">0</span>) <span class="comment">// non-blocking</span></span><br><span class="line">injectglist(&amp;list)</span><br><span class="line">netpollAdjustWaiters(delta)</span><br><span class="line">&#125;</span><br><span class="line">lock(&amp;sched.lock)</span><br><span class="line"></span><br><span class="line">procs := gomaxprocs</span><br><span class="line"><span class="keyword">if</span> newprocs != <span class="number">0</span> &#123;</span><br><span class="line">procs = newprocs</span><br><span class="line">newprocs = <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line">p1 := procresize(procs)</span><br><span class="line">sched.gcwaiting.Store(<span class="literal">false</span>)</span><br><span class="line"><span class="keyword">if</span> sched.sysmonwait.Load() &#123;</span><br><span class="line">sched.sysmonwait.Store(<span class="literal">false</span>)</span><br><span class="line">notewakeup(&amp;sched.sysmonnote)</span><br><span class="line">&#125;</span><br><span class="line">unlock(&amp;sched.lock)</span><br><span class="line"></span><br><span class="line">worldStarted()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> p1 != <span class="literal">nil</span> &#123;</span><br><span class="line">p := p1</span><br><span class="line">p1 = p1.link.ptr()</span><br><span class="line"><span class="keyword">if</span> p.m != <span class="number">0</span> &#123;</span><br><span class="line">mp := p.m.ptr()</span><br><span class="line">p.m = <span class="number">0</span></span><br><span class="line"><span class="keyword">if</span> mp.nextp != <span class="number">0</span> &#123;</span><br><span class="line">throw(<span class="string">&quot;startTheWorld: inconsistent mp-&gt;nextp&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">mp.nextp.set(p)</span><br><span class="line">notewakeup(&amp;mp.park)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// Start M to run P.  Do not start another M below.</span></span><br><span class="line">newm(<span class="literal">nil</span>, p, <span class="number">-1</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Capture start-the-world time before doing clean-up tasks.</span></span><br><span class="line"><span class="keyword">if</span> now == <span class="number">0</span> &#123;</span><br><span class="line">now = nanotime()</span><br><span class="line">&#125;</span><br><span class="line">totalTime := now - w.startedStopping</span><br><span class="line"><span class="keyword">if</span> w.reason.isGC() &#123;</span><br><span class="line">sched.stwTotalTimeGC.record(totalTime)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">sched.stwTotalTimeOther.record(totalTime)</span><br><span class="line">&#125;</span><br><span class="line">trace := traceAcquire()</span><br><span class="line"><span class="keyword">if</span> trace.ok() &#123;</span><br><span class="line">trace.STWDone()</span><br><span class="line">traceRelease(trace)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Wakeup an additional proc in case we have excessive runnable goroutines</span></span><br><span class="line"><span class="comment">// in local queues or in the global queue. If we don&#x27;t, the proc will park itself.</span></span><br><span class="line"><span class="comment">// If we have lots of excessive work, resetspinning will unpark additional procs as necessary.</span></span><br><span class="line">wakep()</span><br><span class="line"></span><br><span class="line">releasem(mp)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> now</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E6%B7%B7%E5%90%88%E5%86%99%E5%B1%8F%E9%9A%9C%E7%9A%84GC%E5%85%A8%E5%9C%BA%E6%99%AF%E5%88%86%E6%9E%90/</id>
    <link href="https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E6%B7%B7%E5%90%88%E5%86%99%E5%B1%8F%E9%9A%9C%E7%9A%84GC%E5%85%A8%E5%9C%BA%E6%99%AF%E5%88%86%E6%9E%90/"/>
    <published>2025-12-28T09:24:00.000Z</published>
    <summary>
      <![CDATA[<h2 id="第二章-Go-语言混合写屏障的-GC-全场景分析"><a href="#第二章-Go-语言混合写屏障的-GC-全场景分析" class="headerlink" title="第二章 Go 语言混合写屏障的 GC 全场景分析"></a>第二章 Go 语言混合写屏障]]>
    </summary>
    <title>《深入理解Go语言》读书笔记 - Go 语言混合写屏障的 GC 全场景分析</title>
    <updated>2025-12-28T09:24:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="读书笔记" scheme="https://cooooing.github.io/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="《深入理解Go语言》" scheme="https://cooooing.github.io/tags/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B/"/>
    <category term="GPM" scheme="https://cooooing.github.io/tags/GPM/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>想深入下 Go 的底层,主要是并发模型、GC和内存管理相关内容。</p><p>刘丹冰老师的这本书分为3篇21章<br>第一篇(1~4章)主要讲 GMP 模型、GC、内存管理和 IO 复用并发模型,是比较核心的内容,会详细阅读。<br>第二篇(5~12章)主要讲 Go 语言特性和一些进阶知识,会选择性地详细阅读。<br>第三篇(13~21章)主要是项目实战,通过 Zinx 框架了解 TCP&#x2F;IP 网络服务器架构,这篇大概率会跳过。</p><h2 id="第一章-深入理解-Go-语言协程调度器-GPM-模型"><a href="#第一章-深入理解-Go-语言协程调度器-GPM-模型" class="headerlink" title="第一章 深入理解 Go 语言协程调度器 GPM 模型"></a>第一章 深入理解 Go 语言协程调度器 GPM 模型</h2><h3 id="调度器由来"><a href="#调度器由来" class="headerlink" title="调度器由来"></a>调度器由来</h3><h4 id="单进程时代不需要调度器"><a href="#单进程时代不需要调度器" class="headerlink" title="单进程时代不需要调度器"></a>单进程时代不需要调度器</h4><p>单进程时代过于久远,现在应该不会有这样的系统了,估计只存在于计算机组成原理的课上了。</p><p>单一的执行流程,计算机只能一个任务一个任务处理,所有的程序都是阻塞的。<br>因为在一个进程的完整生命周期中,所需要访问的物理部分包括 CPU、Cache、主内存、磁盘、网络等。<br><strong>不同的硬件媒介处理计算的能力相差甚大,可能几个数量级。如果将这些处理速度不同的处理媒介通过一个进程串在一起,则会出现高速度媒介等待和浪费的现象。</strong><br>如当一个程序加载一个磁盘数据的时候,在读写的过程中,CPU处于等待状态,那么对于单进程的操作系统来讲,很明显会造成CPU运算能力的浪费,因为CPU此刻本应该被合理地分配到其他进程上去做高层的计算。</p><h4 id="多进程-多线程时代的调度器"><a href="#多进程-多线程时代的调度器" class="headerlink" title="多进程&#x2F;多线程时代的调度器"></a>多进程&#x2F;多线程时代的调度器</h4><p>当一个进程阻塞 CPU 可以立刻切换到其他进程中去执行,而且调度CPU的算法可以保证在运行的进程都可以被分配到CPU的运行时间片。从宏观来看,似乎多个进程是在同时被运行。</p><blockquote><p>时间片(Timeslice)又称为“量子(Quantum)”或“处理器片(Processor Slice)”<br>是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是从进程开始运行直到被抢占的时间)。</p></blockquote><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%A4%9A%E8%BF%9B%E7%A8%8B%E6%89%A7%E8%A1%8C%E9%A1%BA%E5%BA%8F.png"                        alt="多线程多进程执行顺序.png"                 ></p><p>这是会带来新的问题,当进程越来越多,每个进程又拥有太多的资源时,进程的创建、切换、销毁都会占用很多 CPU 性能。<br>即进程切换和调度带来的性能消耗可能会超过进程本身的性能消耗,这时候就很不划算了。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/CPU%E8%B0%83%E5%BA%A6%E5%88%87%E6%8D%A2%E7%9A%84%E6%88%90%E6%9C%AC.png"                        alt="CPU调度切换的成本.png"                 ></p><h4 id="协程提供-CPU-利用率"><a href="#协程提供-CPU-利用率" class="headerlink" title="协程提供 CPU 利用率"></a>协程提供 CPU 利用率</h4><p>一个线程可以分为“内核态”和“用户态”两种形态的线程。<br>所谓<strong>用户态线程就是把内核态的线程在用户态实现了一遍而已</strong>,目的是更轻量化(更少的内存占用、更少的隔离、更快的调度)和更高的可控性(可以自己控制调度器)。<br>用户态中的所有东⻄内核态都看得⻅,只是对于内核而言用户态线程只是一堆内存数据而已。</p><p>一个用户态线程必须绑定一个内核态线程,但是CPU并不知道有用户态线程的存在,它只知道它运行的是一个内核态线程(Linux的PCB进程控制块)。<br>如果将线程再进行细化,内核线程依然叫线程(Thread),而用户线程则叫协程(Co-routine)。操作系统层面的线程就是所谓的内核态线程,用户态线程则多种多样,只要能满足在同一个内核线程上执行多个任务,例如Co-routine、Go的Goroutine、C#的Task等。</p><p>协程和线程的映射关系有三种,它们分别是 N:1 关系、1:1 关系和 M:N 关系。</p><ol><li>N:1 关系<br>N个协程绑定1个线程,优点就是协程在用户态线程即完成切换,不会陷入内核态,这种切换非常轻量快速,但缺点也很明显。1个进程的所有协程都绑定在1个线程上。<br>这种情况下,<strong>单个程序只能利用一个核,即使是多核CPU</strong>;<strong>某一个协程阻塞,会造成线程阻塞,本进程的其他协程都无法执行了,进而导致没有任何并发能力</strong>。</li><li>1:1 关系<br>1个协程绑定1个线程,这种方式最容易实现。协程的调度都由CPU完成了,和直接使用线程没有太大区别。</li><li>M:N 关系<br>M个协程绑定1个线程,实现起来最为复杂。同一个调度器上挂载M个协程,调度器下游则是多个CPU核心资源。<br>协程跟线程是有区别的,线程由CPU调度是抢占式的,协程由用户态调度是协作式的,一个协程让出CPU后,才执行下一个协程,所以针对M:N模型的中间层的调度器设计就变得尤为重要,提高线程和协程的绑定关系和执行效率也变为不同语言在设计调度器时的优先目标。</li></ol><h4 id="Go-语言的协程-Goroutine"><a href="#Go-语言的协程-Goroutine" class="headerlink" title="Go 语言的协程 Goroutine"></a>Go 语言的协程 Goroutine</h4><p>Go语言为了提供更容易使用的并发方法,使用了Goroutine和Channel。<br>Goroutine来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被 runtime 调度,从而转移到其他可运行的线程上。<br>Goroutine的特点,占用内存更小(几KB)和调度更灵活(runtime调度)。</p><h4 id="被废弃的-Goroutine-调度器"><a href="#被废弃的-Goroutine-调度器" class="headerlink" title="被废弃的 Goroutine 调度器"></a>被废弃的 Goroutine 调度器</h4><p>Go 的调度器在 2012 年重新设计了。</p><blockquote><p>G 表示 Goroutine<br>M 表示 线程</p></blockquote><p>早期的调度器是基于 M:N 的基础上实现的。<br>所有的协程(G)都会被放在一个全局的Go协程队列中,在全局队列的外面由于是多个 M 的共享资源,所以会加上一个用于同步及互斥作用的锁。<br>M 想要执行、放回 G 都必须访问全局 G 队列,并且 M 有多个,即多线程访问同一资源需要加锁进行保证互斥&#x2F;同步,所以全局 G 队列是由互斥锁进行保护的。</p><p><strong>这会带来激烈的锁竞争</strong>,因为创建、销毁、调度 G 都需要每个 M 获取锁。<br><strong>M 转移 G 会造成延迟和额外的系统负载</strong>。例如当 G 中包含创建新协程的时候,M 创建了 G′,为了继续执行 G,需要把 G′ 交给 M2 执行,<strong>也造成了很差的局部性(共享数据、共享栈索引、可能访问相同内存)</strong>,因为 G′ 和 G 是相关的,最好放在M上执行,而不是其他 M2。<br>系统调用(CPU在M之间的切换)导致频繁的线程阻塞和取消阻塞操作增加了系统开销。</p><h3 id="Go语言调度器GPM模型的设计思想"><a href="#Go语言调度器GPM模型的设计思想" class="headerlink" title="Go语言调度器GPM模型的设计思想"></a>Go语言调度器GPM模型的设计思想</h3><p>面对之前调度器的问题,新的调度器引入了 P (处理器)。<br>处理器包含了运行 Goroutine 的资源,如果线程想运行 Goroutine,必须先获取 P,P 中还包含了可运行的 G 队列。</p><h4 id="GPM-模型"><a href="#GPM-模型" class="headerlink" title="GPM 模型"></a>GPM 模型</h4><p>在Go中,线程是运行 Goroutine 的实体,调度器的功能是把可运行的 Goroutine 分配到工作线程上。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/GPM%E6%A8%A1%E5%9E%8B.png"                        alt="GPM模型.png"                 ></p><ol><li><strong>全局队列(Global Queue):存放等待运行的 G</strong>。<br>全局队列可能被任意的 P 去获取里面的 G,所以全局队列相当于整个模型中的全局资源,那么自然对于队列的读写操作是要加入互斥动作的。</li><li><strong>P 的本地队列:同全局队列类似,存放的也是等待运行的 G,但存放的数量有限,不超过 256 个</strong>。<br>新建 G′ 时,G′ 优先加入 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列。</li><li><strong>P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置)个</strong>。</li><li><strong>M:线程想运行任务就得获取 P,从 P 的本地队列获取 G</strong>,当 P 队列为空时,M 也会尝试从全局队列获得一批 G 放到 P 的本地队列,或从其他P的本地队列“偷”一半放到自己 P 的本地队列(工作窃取)。<br>M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。</li></ol><p>Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS调度器负责把内核线程分配到 CPU 的核上执行。</p><h5 id="有关-P-和-M-个数的问题"><a href="#有关-P-和-M-个数的问题" class="headerlink" title="有关 P 和 M 个数的问题"></a>有关 P 和 M 个数的问题</h5><p>P 的数量由启动时环境变量 $GOMAXPROCS 或者由 runtime 的方法 GOMAXPROCS() 决定。<br>这意味着在程序执行的任意时刻都只有 $GOMAXPROCS 个 Goroutine 在同时运行。(真正的并行,CPU 核心的数量)</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> runtime</span><br><span class="line"></span><br><span class="line"><span class="comment">// GOMAXPROCS sets the maximum number of CPUs that can be executing</span></span><br><span class="line"><span class="comment">// simultaneously and returns the previous setting. It defaults to</span></span><br><span class="line"><span class="comment">// the value of [runtime.NumCPU]. If n &lt; 1, it does not change the current setting.</span></span><br><span class="line"><span class="comment">// This call will go away when the scheduler improves.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GOMAXPROCS</span><span class="params">(n <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">if</span> GOARCH == <span class="string">&quot;wasm&quot;</span> &amp;&amp; n &gt; <span class="number">1</span> &#123;</span><br><span class="line">n = <span class="number">1</span> <span class="comment">// WebAssembly has no threads yet, so only one CPU is possible.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">lock(&amp;sched.lock)</span><br><span class="line">ret := <span class="type">int</span>(gomaxprocs)</span><br><span class="line">unlock(&amp;sched.lock)</span><br><span class="line"><span class="keyword">if</span> n &lt;= <span class="number">0</span> || n == ret &#123;</span><br><span class="line"><span class="keyword">return</span> ret</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">stw := stopTheWorldGC(stwGOMAXPROCS)</span><br><span class="line"></span><br><span class="line"><span class="comment">// newprocs will be processed by startTheWorld</span></span><br><span class="line">newprocs = <span class="type">int32</span>(n)</span><br><span class="line"></span><br><span class="line">startTheWorldGC(stw)</span><br><span class="line"><span class="keyword">return</span> ret</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>M 的数量由 Go 语言本身的限制决定,Go 程序启动时会设置 M 的最大数量,默认为 10000 个,但是内核很难支持这么多的线程数,所以这个限制可以忽略。<br>runtime&#x2F;debug 中的 SetMaxThreads() 函数可设置 M 的最大数量,当一个 M 阻塞了时会创建新的 M。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> runtime</span><br><span class="line"></span><br><span class="line"><span class="comment">// SetMaxThreads sets the maximum number of operating system</span></span><br><span class="line"><span class="comment">// threads that the Go program can use. If it attempts to use more than</span></span><br><span class="line"><span class="comment">// this many, the program crashes.</span></span><br><span class="line"><span class="comment">// SetMaxThreads returns the previous setting.</span></span><br><span class="line"><span class="comment">// The initial setting is 10,000 threads.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// The limit controls the number of operating system threads, not the number</span></span><br><span class="line"><span class="comment">// of goroutines. A Go program creates a new thread only when a goroutine</span></span><br><span class="line"><span class="comment">// is ready to run but all the existing threads are blocked in system calls, cgo calls,</span></span><br><span class="line"><span class="comment">// or are locked to other goroutines due to use of runtime.LockOSThread.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// SetMaxThreads is useful mainly for limiting the damage done by</span></span><br><span class="line"><span class="comment">// programs that create an unbounded number of threads. The idea is</span></span><br><span class="line"><span class="comment">// to take down the program before it takes down the operating system.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">SetMaxThreads</span><span class="params">(threads <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> setMaxThreads(threads)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。</p><h5 id="有关-P-和-M-何时被创建"><a href="#有关-P-和-M-何时被创建" class="headerlink" title="有关 P 和 M 何时被创建"></a>有关 P 和 M 何时被创建</h5><ol><li>P 创建的时机在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P。</li><li>M 创建的时机是在当没有足够的 M 来关联 P 并运行其中可运行的 G 的时候。<br>例如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,如果此时没有空闲的 M,就会去创建新的 M。</li></ol><h4 id="调度器的设计策略"><a href="#调度器的设计策略" class="headerlink" title="调度器的设计策略"></a>调度器的设计策略</h4><h5 id="复用线程"><a href="#复用线程" class="headerlink" title="复用线程"></a>复用线程</h5><p>避免频繁地创建、销毁线程,而是对线程的复用。</p><ol><li>偷取(Work Stealing)机制<br>当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/%E5%81%B7%E5%8F%96%E6%9C%BA%E5%88%B6.png"                        alt="偷取机制.png"                 ><br>偷取的动作一定是由 P 发起的,而非 M,因为 P 的数量是固定的,如果一个 M 得不到一个 P,那么这个 M 是没有执行的本地队列的,更谈不上向其他的 P 队列偷取了。</li><li>移交(Hand Off)机制<br>当本线程因为 G 进行系统调用阻塞时,线程会释放绑定的 P,把 P 转移给其他空闲的线程执行,此时若在 M1 的 GPM 组合中,G1 正在被调度,并且已经发生了阻塞,则这个时候就会触发移交的设计机制。<br>GPM 模型为了更大程度地利用 M 和 P 的性能,不会让一个 P 永远被一个阻塞的 G1 耽误之后的工作,所以遇⻅这种情况的时候,移交机制的设计理念是应该立刻将此时的 P 释放出来。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/%E7%A7%BB%E4%BA%A4%E6%9C%BA%E5%88%B6%EF%BC%9AG1%E5%8F%91%E7%8E%B0%E9%98%BB%E5%A1%9E.png"                        alt="移交机制:G1发现阻塞.png"                 ><br>为了释放 P,所以将 P 和 M1、G1 分离,M1 由于正在执行当前的 G1,全部的程序栈空间均在 M1 中保存,所以 M1 此时应该与 G1 一同进入阻塞的状态,但是已经被释放的 P 需要跟另一个 M 进行绑定,所以就会选择一个 M3(如果此时没有M3,则会创建一个新的或者唤醒一个正在睡眠的M)进行绑定,这样新的 P 就会继续工作,接收新的 G 或者从其他的队列中实施偷取机制。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/%E7%A7%BB%E4%BA%A4%E6%9C%BA%E5%88%B6%EF%BC%9A%E9%98%BB%E5%A1%9E%E7%9A%84P%E8%A2%AB%E9%87%8A%E6%94%BE.png"                        alt="移交机制:阻塞的P被释放.png"                 ></li></ol><h5 id="利用并行"><a href="#利用并行" class="headerlink" title="利用并行"></a>利用并行</h5><p>GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。<br>GOMAXPROCS 也限制了并发的程度,例如 GOMAXPROCS &#x3D; 核数 &#x2F; 2,表示最多利用一半的 CPU 核进行并行。</p><h5 id="抢占"><a href="#抢占" class="headerlink" title="抢占"></a>抢占</h5><p>在 Co-routine 中要等待一个协程主动让出 CPU 才执行下一个协程。<br>在 Go 中,一个 Goroutine 最多占用CPU 10ms,防止其他 Goroutine 无资源可用,这是 Goroutine 不同于 Co-routine 的一个地方。</p><h5 id="全局G队列"><a href="#全局G队列" class="headerlink" title="全局G队列"></a>全局G队列</h5><p>在新的调度器中依然有全局 G 队列,但功能已经被弱化了,当 M 执行偷取,但从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/%E5%85%A8%E5%B1%80%E9%98%9F%E5%88%97%E8%8E%B7%E5%8F%96.png"                        alt="全局队列获取.png"                 ></p><h4 id="go-func-调度流程"><a href="#go-func-调度流程" class="headerlink" title="go func() 调度流程"></a>go func() 调度流程</h4><ol><li>通过 go func() 创建一个 Goroutine<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/go-func()%E5%88%9B%E5%BB%BAGoroutine.png"                        alt="go func()创建Goroutine.png"                 ></li><li>有两个存储 G 的队列,一个是局部调度器 P 的本地队列,另一个全局 G 队列。<br>新创建的 G 会先保存在 P 的本地队列中,如果 P 本地队列满了,则会保存在全局的队列中。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/go-func()%E6%96%B0%E5%BB%BAG%E7%9A%84%E6%94%BE%E7%BD%AE%E4%BD%8D%E7%BD%AE.png"                        alt="go func()新建G的放置位置.png"                 ></li><li>G 只能运行在 M 中,一个 M 必须持有一个 P,M 与 P 是 1:1 的关系。<br>M 会从 P 的本地队列弹出一个可执行状态的 G 来执行,如果 P 的本地队列为空,则会从全局队列进行获取,如果从全局队列获取不到,则会向其他的 MP 组合偷取一个可执行的 G 来执行。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/go-func()%E8%8E%B7%E5%8F%96G%E7%9A%84%E6%96%B9%E5%BC%8F.png"                        alt="go func()获取G的方式.png"                 ></li><li>一个 M 调度 G 执行的过程是一个循环机制。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/go-func()M%E8%B0%83%E5%BA%A6G%E6%98%AF%E5%BE%AA%E7%8E%AF%E5%BE%80%E5%A4%8D%E7%9A%84.png"                        alt="go func()M调度G是循环往复的.png"                 ></li><li>当 M 执行某一个 G 时如果发生了 syscall 或者其余阻塞操作,则 M 会阻塞,如果当前有一些 G 在执行,runtime 则会把这个线程 M 从 P 中移除(Detach),然后创建一个新的操作系统线程(如果有空闲的线程可用就复用空闲线程)来服务于这个 P。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/go-func()%E5%BD%93M1%E4%B8%8A%E7%9A%84G%E5%8F%91%E7%94%9F%E9%98%BB%E5%A1%9E.png"                        alt="go func()当M1上的G发生阻塞.png"                 ></li><li>当 M 系统调用结束时,这个 G 会尝试获取一个空闲的 P 执行,并放入这个 P 的本地队列。如果获取不到 P,则这个线程 M 会变成休眠状态,加入空闲线程中,然后这个 G 会被放入全局队列中。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/go-func()M1%E5%B0%86%E4%BC%91%E7%9C%A0,G%E5%9B%9E%E5%88%B0%E5%85%A8%E5%B1%80%E9%98%9F%E5%88%97.png"                        alt="go func()M1将休眠,G回到全局队列.png"                 ></li></ol><h4 id="调度器的生命周期"><a href="#调度器的生命周期" class="headerlink" title="调度器的生命周期"></a>调度器的生命周期</h4><p>Go 语言调度器的 GPM 模型中还有两个比较特殊的⻆色,它们分别是 M0 和 G0。</p><ol><li>M0<ol><li>启动程序后的编号为0的主线程。</li><li>在全局命令 runtime.m0 中,不需要在 heap 堆上分配。</li><li>负责执行初始化操作和启动第 1 个 G。</li><li>启动第 1 个 G 后,M0 就和其他的 M 一样了。</li></ol></li><li>G0<ol><li>每次启动一个 M,创建的第1个 Goroutine 就是 G0。</li><li>G0 用于负责调度 G,此外还有信号处理、抢占切换等职责。</li><li>G0 不指向任何可执行的用户函数,但仍会运行 runtime 函数。</li><li>每个 M 都会有一个自己的 G0。</li><li>在调度或系统调度时,会使用 M 切换到 G0,再通过 G0 调度。</li><li>M0 的 G0 会放在全局空间。</li></ol></li></ol><p>对于一个简单的 main 程序</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>流程如下:</p><ol><li>runtime 创建最初的线程 M0 和 Goroutine G0,并将它们关联。</li><li>调度器初始化:初始化 M0、栈、垃圾回收,以及创建和初始化由 GOMAXPROCS 个 P 构成的 P 列表。</li><li>示例代码中的 main()函数是 main.main,runtime 中也有 1 个 main() 函数 runtime.main,代码经过编译后,runtime.main 会调用 main.main,程序启动时会为 runtime.main 创建 Goroutine,称为 Main Goroutine,然后把 Main Goroutine 加入 P 的本地队列。</li><li>启动 M0,M0 已经绑定了 P,会从 P 的本地队列获取 G,并获取 Main Goroutine。</li><li>G 拥有栈,M 根据 G 中的栈信息和调度信息设置运行环境。</li><li>M 运行 G。</li><li>G 退出,再次回到 M 获取可运行的 G,这样重复下去,直到 main.main 退出,runtime.main 执行 Defer 和 Panic 处理,或调用 runtime.exit 退出程序。</li></ol><p>调度器的生命周期几乎占满了一个 Go 程序的一生。<br>runtime.main 的 Goroutine 执行之前都是为调度器做准备工作,runtime.main 的 Goroutine 运行才是调度器的真正开始,直到 runtime.main 结束而结束。</p><h4 id="可视化-GPM-编程"><a href="#可视化-GPM-编程" class="headerlink" title="可视化 GPM 编程"></a>可视化 GPM 编程</h4><p>Go 语言提供了两种方式可以查看一个程序的 GPM 数据。</p><h5 id="go-tool-trace"><a href="#go-tool-trace" class="headerlink" title="go tool trace"></a>go tool trace</h5><p>trace 记录了运行时的信息,能提供可视化的 Web ⻚面。</p><p>以下面的程序为例:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;os&quot;</span></span><br><span class="line"><span class="string">&quot;runtime/trace&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">f, err := os.Create(<span class="string">&quot;trace.out&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> f.Close()</span><br><span class="line"></span><br><span class="line">err = trace.Start(f)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> trace.Stop()</span><br><span class="line"></span><br><span class="line"><span class="comment">// main</span></span><br><span class="line">fmt.Println(<span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行后会得到 trace.out 文件,使用 <code>go tool</code> 打开该文件:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@ubuntu:~$ go tool trace trace.out</span><br><span class="line">2025/12/27 22:00:48 Preparing trace for viewer...</span><br><span class="line">2025/12/27 22:00:48 Splitting trace for viewer...</span><br><span class="line">2025/12/27 22:00:48 Opening browser. Trace viewer is listening on http://127.0.0.1:64230</span><br></pre></td></tr></table></figure><p>通过浏览器打开后,点开 view 可以看到:</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/trace%E4%BF%A1%E6%81%AF.png"                        alt="trace信息.png"                 ></p><h5 id="Debug-trace"><a href="#Debug-trace" class="headerlink" title="Debug trace"></a>Debug trace</h5><p>编译下面的程序:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">5</span>; i++ &#123;</span><br><span class="line">time.Sleep(<span class="number">1</span> * time.Second)</span><br><span class="line">fmt.Println(<span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 debug 运行:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@ubuntu:~$ go build main.go</span><br><span class="line">ubuntu@ubuntu:~$ GODEBUG=schedtrace=1000 main</span><br><span class="line">SCHED 0ms: gomaxprocs=20 idleprocs=20 threads=6 spinningthreads=0 needspinning=0 idlethreads=3 runqueue=0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] schedticks=[ 1 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]</span><br><span class="line">hello world</span><br><span class="line">SCHED 1004ms: gomaxprocs=20 idleprocs=20 threads=6 spinningthreads=0 needspinning=0 idlethreads=3 runqueue=0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] schedticks=[ 1 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]</span><br><span class="line">hello world</span><br><span class="line">SCHED 2005ms: gomaxprocs=20 idleprocs=20 threads=6 spinningthreads=0 needspinning=0 idlethreads=3 runqueue=0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] schedticks=[ 1 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]</span><br><span class="line">hello world</span><br><span class="line">SCHED 3014ms: gomaxprocs=20 idleprocs=20 threads=6 spinningthreads=0 needspinning=0 idlethreads=3 runqueue=0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] schedticks=[ 1 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]</span><br><span class="line">hello world</span><br><span class="line">SCHED 4016ms: gomaxprocs=20 idleprocs=20 threads=6 spinningthreads=0 needspinning=0 idlethreads=3 runqueue=0 [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] schedticks=[ 1 4 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]</span><br><span class="line">hello world</span><br></pre></td></tr></table></figure><ol><li>SCHED:调试信息输出标志字符串,代表本行是Goroutine调度器的输出。</li><li>0ms:从程序启动到输出这行日志的时间。</li><li>gomaxprocs:P 的数量(并行度的上限),因为默认的 P 的属性是和 CPU 核心数量一致,当然也可以通过 GOMAXPROCS 设置。</li><li>idleprocs:处于 idle 状态、没有绑定运行 G 的 P 的数量;通过 gomaxprocs 和 idleprocs 的差值,就可知道执行 Go 代码的 P 的数量。</li><li>threads:os threads&#x2F;M 的数量,包含 scheduler 使用的 m 数量,加上 runtime 自用的类似 sysmon 这样的 thread 的数量。</li><li>spinningthreads:处于自旋状态的os thread数量。</li><li>needspinning:当前调度器是否需要新的 spinning M。</li><li>idlethread:处于 idle 状态的 os thread 的数量。</li><li>runqueue&#x3D;0:全局 runqueue(sched.runq)中等待执行的 G 的数量。</li><li>[0 0 …]:每个 P 的本地 runqueue 中等待执行的 G 的数量,数量与 gomaxprocs 一致。</li><li>schedticks&#x3D;[ … ]:每个 P 上发生过多少次调度循环(schedule 调用)。</li></ol><h3 id="Go-调度器调度流程"><a href="#Go-调度器调度流程" class="headerlink" title="Go 调度器调度流程"></a>Go 调度器调度流程</h3><p>参考 Go 版本 1.25.5 的代码。<br>场景:当一个 M 已经绑定一个 P,准备执行下一个 G。</p><p>首先调度器的总体结构:</p><ol><li>newproc 创建 G</li><li>ready&#x2F;runqput G 进入 runnable</li><li>schedule 调度入口</li><li>findRunnable 寻找可运行 G</li><li>execute G 真正运行</li><li>park&#x2F;exit&#x2F;preempt 阻塞、退出、抢占</li></ol><h4 id="创建阶段-newproc"><a href="#创建阶段-newproc" class="headerlink" title="创建阶段(newproc)"></a>创建阶段(newproc)</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">go f():编译器会调用 newproc,newproc 流程如下</span><br><span class="line">↓</span><br><span class="line">newproc1:</span><br><span class="line">  - 分配 / 复用 G</span><br><span class="line">  - 初始化栈与 sched 上下文</span><br><span class="line">  - 状态从 _Gdead 原子切换为 _Grunnable(或 _Gwaiting)</span><br><span class="line">↓</span><br><span class="line">runqput:</span><br><span class="line">  - 优先放入 runnext(若 next=true 且条件允许)</span><br><span class="line">  - 否则放入当前 P 的本地 runq</span><br><span class="line">  - 若本地 runq 满:</span><br><span class="line">      调用 runqputslow</span><br><span class="line">      将本地 runq 的前一半(len/2)+ 当前 G</span><br><span class="line">      批量转移到 global runq</span><br><span class="line">↓</span><br><span class="line">当一个 G 被变为 runnable(newproc / ready)时,</span><br><span class="line">在调度器已初始化且条件允许的情况下:</span><br><span class="line">  调用 wakep</span><br><span class="line">↓</span><br><span class="line">wakep:</span><br><span class="line">  - 若已有 spinning M,则直接返回</span><br><span class="line">  - 否则尝试从 idle P 队列中取一个 P</span><br><span class="line">  - 若没有 idle P(所有 P 已在用),则什么都不做</span><br><span class="line">  - 若成功取到 P:</span><br><span class="line">      启动 / 唤醒一个 M</span><br><span class="line">      绑定该 P</span><br><span class="line">      以 spinning 状态开始调度</span><br></pre></td></tr></table></figure><p>go f():<code>newproc()</code>: go1.25.5&#x2F;src&#x2F;runtime&#x2F;proc.go:5158</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Create a new g running fn.</span></span><br><span class="line"><span class="comment">// Put it on the queue of g&#x27;s waiting to run.</span></span><br><span class="line"><span class="comment">// The compiler turns a go statement into a call to this.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">newproc</span><span class="params">(fn *funcval)</span></span> &#123;</span><br><span class="line">gp := getg()</span><br><span class="line">pc := sys.GetCallerPC()</span><br><span class="line">systemstack(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">newg := newproc1(fn, gp, pc, <span class="literal">false</span>, waitReasonZero)</span><br><span class="line"></span><br><span class="line">pp := getg().m.p.ptr()</span><br><span class="line">runqput(pp, newg, <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> mainStarted &#123;</span><br><span class="line">wakep()</span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>创建 G :<code>newproc1()</code>: go1.25.5&#x2F;src&#x2F;runtime&#x2F;proc.go:5176</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Create a new g in state _Grunnable (or _Gwaiting if parked is true), starting at fn.</span></span><br><span class="line"><span class="comment">// callerpc is the address of the go statement that created this. The caller is responsible</span></span><br><span class="line"><span class="comment">// for adding the new g to the scheduler. If parked is true, waitreason must be non-zero.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">newproc1</span><span class="params">(fn *funcval, callergp *g, callerpc <span class="type">uintptr</span>, parked <span class="type">bool</span>, waitreason waitReason)</span></span> *g &#123;</span><br><span class="line"><span class="keyword">if</span> fn == <span class="literal">nil</span> &#123;</span><br><span class="line">fatal(<span class="string">&quot;go of nil func value&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">mp := acquirem() <span class="comment">// disable preemption because we hold M and P in local vars.</span></span><br><span class="line">pp := mp.p.ptr()</span><br><span class="line">newg := gfget(pp)</span><br><span class="line"><span class="keyword">if</span> newg == <span class="literal">nil</span> &#123;</span><br><span class="line">newg = malg(stackMin)</span><br><span class="line">casgstatus(newg, _Gidle, _Gdead)</span><br><span class="line">allgadd(newg) <span class="comment">// publishes with a g-&gt;status of Gdead so GC scanner doesn&#x27;t look at uninitialized stack.</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> newg.stack.hi == <span class="number">0</span> &#123;</span><br><span class="line">throw(<span class="string">&quot;newproc1: newg missing stack&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> readgstatus(newg) != _Gdead &#123;</span><br><span class="line">throw(<span class="string">&quot;newproc1: new g is not Gdead&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">totalSize := <span class="type">uintptr</span>(<span class="number">4</span>*goarch.PtrSize + sys.MinFrameSize) <span class="comment">// extra space in case of reads slightly beyond frame</span></span><br><span class="line">totalSize = alignUp(totalSize, sys.StackAlign)</span><br><span class="line">sp := newg.stack.hi - totalSize</span><br><span class="line"><span class="keyword">if</span> usesLR &#123;</span><br><span class="line"><span class="comment">// caller&#x27;s LR</span></span><br><span class="line">*(*<span class="type">uintptr</span>)(unsafe.Pointer(sp)) = <span class="number">0</span></span><br><span class="line">prepGoExitFrame(sp)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> GOARCH == <span class="string">&quot;arm64&quot;</span> &#123;</span><br><span class="line"><span class="comment">// caller&#x27;s FP</span></span><br><span class="line">*(*<span class="type">uintptr</span>)(unsafe.Pointer(sp - goarch.PtrSize)) = <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">memclrNoHeapPointers(unsafe.Pointer(&amp;newg.sched), unsafe.Sizeof(newg.sched))</span><br><span class="line">newg.sched.sp = sp</span><br><span class="line">newg.stktopsp = sp</span><br><span class="line">newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum <span class="comment">// +PCQuantum so that previous instruction is in same function</span></span><br><span class="line">newg.sched.g = guintptr(unsafe.Pointer(newg))</span><br><span class="line">gostartcallfn(&amp;newg.sched, fn)</span><br><span class="line">newg.parentGoid = callergp.goid</span><br><span class="line">newg.gopc = callerpc</span><br><span class="line">newg.ancestors = saveAncestors(callergp)</span><br><span class="line">newg.startpc = fn.fn</span><br><span class="line">newg.runningCleanups.Store(<span class="literal">false</span>)</span><br><span class="line"><span class="keyword">if</span> isSystemGoroutine(newg, <span class="literal">false</span>) &#123;</span><br><span class="line">sched.ngsys.Add(<span class="number">1</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// Only user goroutines inherit synctest groups and pprof labels.</span></span><br><span class="line">newg.bubble = callergp.bubble</span><br><span class="line"><span class="keyword">if</span> mp.curg != <span class="literal">nil</span> &#123;</span><br><span class="line">newg.labels = mp.curg.labels</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> goroutineProfile.active &#123;</span><br><span class="line"><span class="comment">// A concurrent goroutine profile is running. It should include</span></span><br><span class="line"><span class="comment">// exactly the set of goroutines that were alive when the goroutine</span></span><br><span class="line"><span class="comment">// profiler first stopped the world. That does not include newg, so</span></span><br><span class="line"><span class="comment">// mark it as not needing a profile before transitioning it from</span></span><br><span class="line"><span class="comment">// _Gdead.</span></span><br><span class="line">newg.goroutineProfiled.Store(goroutineProfileSatisfied)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Track initial transition?</span></span><br><span class="line">newg.trackingSeq = <span class="type">uint8</span>(cheaprand())</span><br><span class="line"><span class="keyword">if</span> newg.trackingSeq%gTrackingPeriod == <span class="number">0</span> &#123;</span><br><span class="line">newg.tracking = <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line">gcController.addScannableStack(pp, <span class="type">int64</span>(newg.stack.hi-newg.stack.lo))</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get a goid and switch to runnable. Make all this atomic to the tracer.</span></span><br><span class="line">trace := traceAcquire()</span><br><span class="line"><span class="keyword">var</span> status <span class="type">uint32</span> = _Grunnable</span><br><span class="line"><span class="keyword">if</span> parked &#123;</span><br><span class="line">status = _Gwaiting</span><br><span class="line">newg.waitreason = waitreason</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> pp.goidcache == pp.goidcacheend &#123;</span><br><span class="line"><span class="comment">// Sched.goidgen is the last allocated id,</span></span><br><span class="line"><span class="comment">// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].</span></span><br><span class="line"><span class="comment">// At startup sched.goidgen=0, so main goroutine receives goid=1.</span></span><br><span class="line">pp.goidcache = sched.goidgen.Add(_GoidCacheBatch)</span><br><span class="line">pp.goidcache -= _GoidCacheBatch - <span class="number">1</span></span><br><span class="line">pp.goidcacheend = pp.goidcache + _GoidCacheBatch</span><br><span class="line">&#125;</span><br><span class="line">newg.goid = pp.goidcache</span><br><span class="line">casgstatus(newg, _Gdead, status)</span><br><span class="line">pp.goidcache++</span><br><span class="line">newg.trace.reset()</span><br><span class="line"><span class="keyword">if</span> trace.ok() &#123;</span><br><span class="line">trace.GoCreate(newg, newg.startpc, parked)</span><br><span class="line">traceRelease(trace)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Set up race context.</span></span><br><span class="line"><span class="keyword">if</span> raceenabled &#123;</span><br><span class="line">newg.racectx = racegostart(callerpc)</span><br><span class="line">newg.raceignore = <span class="number">0</span></span><br><span class="line"><span class="keyword">if</span> newg.labels != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// See note in proflabel.go on labelSync&#x27;s role in synchronizing</span></span><br><span class="line"><span class="comment">// with the reads in the signal handler.</span></span><br><span class="line">racereleasemergeg(newg, unsafe.Pointer(&amp;labelSync))</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">releasem(mp)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> newg</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将 G 放入队列:<code>runqput()</code>: go1.25.5&#x2F;src&#x2F;runtime&#x2F;proc.go:7058</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// runqput tries to put g on the local runnable queue.</span></span><br><span class="line"><span class="comment">// If next is false, runqput adds g to the tail of the runnable queue.</span></span><br><span class="line"><span class="comment">// If next is true, runqput puts g in the pp.runnext slot.</span></span><br><span class="line"><span class="comment">// If the run queue is full, runnext puts g on the global queue.</span></span><br><span class="line"><span class="comment">// Executed only by the owner P.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">runqput</span><span class="params">(pp *p, gp *g, next <span class="type">bool</span>)</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> !haveSysmon &amp;&amp; next &#123;</span><br><span class="line"><span class="comment">// A runnext goroutine shares the same time slice as the</span></span><br><span class="line"><span class="comment">// current goroutine (inheritTime from runqget). To prevent a</span></span><br><span class="line"><span class="comment">// ping-pong pair of goroutines from starving all others, we</span></span><br><span class="line"><span class="comment">// depend on sysmon to preempt &quot;long-running goroutines&quot;. That</span></span><br><span class="line"><span class="comment">// is, any set of goroutines sharing the same time slice.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// If there is no sysmon, we must avoid runnext entirely or</span></span><br><span class="line"><span class="comment">// risk starvation.</span></span><br><span class="line">next = <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> randomizeScheduler &amp;&amp; next &amp;&amp; randn(<span class="number">2</span>) == <span class="number">0</span> &#123;</span><br><span class="line">next = <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> next &#123;</span><br><span class="line">retryNext:</span><br><span class="line">oldnext := pp.runnext</span><br><span class="line"><span class="keyword">if</span> !pp.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) &#123;</span><br><span class="line"><span class="keyword">goto</span> retryNext</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> oldnext == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Kick the old runnext out to the regular run queue.</span></span><br><span class="line">gp = oldnext.ptr()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">retry:</span><br><span class="line">h := atomic.LoadAcq(&amp;pp.runqhead) <span class="comment">// load-acquire, synchronize with consumers</span></span><br><span class="line">t := pp.runqtail</span><br><span class="line"><span class="keyword">if</span> t-h &lt; <span class="type">uint32</span>(<span class="built_in">len</span>(pp.runq)) &#123;</span><br><span class="line">pp.runq[t%<span class="type">uint32</span>(<span class="built_in">len</span>(pp.runq))].set(gp)</span><br><span class="line">atomic.StoreRel(&amp;pp.runqtail, t+<span class="number">1</span>) <span class="comment">// store-release, makes the item available for consumption</span></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> runqputslow(pp, gp, h, t) &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// the queue is not full, now the put above must succeed</span></span><br><span class="line"><span class="keyword">goto</span> retry</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Put g and a batch of work from local runnable queue on global queue.</span></span><br><span class="line"><span class="comment">// Executed only by the owner P.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">runqputslow</span><span class="params">(pp *p, gp *g, h, t <span class="type">uint32</span>)</span></span> <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">var</span> batch [<span class="built_in">len</span>(pp.runq)/<span class="number">2</span> + <span class="number">1</span>]*g</span><br><span class="line"></span><br><span class="line"><span class="comment">// First, grab a batch from local queue.</span></span><br><span class="line">n := t - h</span><br><span class="line">n = n / <span class="number">2</span></span><br><span class="line"><span class="keyword">if</span> n != <span class="type">uint32</span>(<span class="built_in">len</span>(pp.runq)/<span class="number">2</span>) &#123;</span><br><span class="line">throw(<span class="string">&quot;runqputslow: queue is not full&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> i := <span class="type">uint32</span>(<span class="number">0</span>); i &lt; n; i++ &#123;</span><br><span class="line">batch[i] = pp.runq[(h+i)%<span class="type">uint32</span>(<span class="built_in">len</span>(pp.runq))].ptr()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> !atomic.CasRel(&amp;pp.runqhead, h, h+n) &#123; <span class="comment">// cas-release, commits consume</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line">batch[n] = gp</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> randomizeScheduler &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="type">uint32</span>(<span class="number">1</span>); i &lt;= n; i++ &#123;</span><br><span class="line">j := cheaprandn(i + <span class="number">1</span>)</span><br><span class="line">batch[i], batch[j] = batch[j], batch[i]</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Link the goroutines.</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="type">uint32</span>(<span class="number">0</span>); i &lt; n; i++ &#123;</span><br><span class="line">batch[i].schedlink.set(batch[i+<span class="number">1</span>])</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">q := gQueue&#123;batch[<span class="number">0</span>].guintptr(), batch[n].guintptr(), <span class="type">int32</span>(n + <span class="number">1</span>)&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Now put the batch on global queue.</span></span><br><span class="line">lock(&amp;sched.lock)</span><br><span class="line">globrunqputbatch(&amp;q)</span><br><span class="line">unlock(&amp;sched.lock)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>尝试添加 P 来执行 G:<code>wakep</code>: go1.25.5&#x2F;src&#x2F;runtime&#x2F;proc.go:3217</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Tries to add one more P to execute G&#x27;s.</span></span><br><span class="line"><span class="comment">// Called when a G is made runnable (newproc, ready).</span></span><br><span class="line"><span class="comment">// Must be called with a P.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// wakep should be an internal detail,</span></span><br><span class="line"><span class="comment">// but widely used packages access it using linkname.</span></span><br><span class="line"><span class="comment">// Notable members of the hall of shame include:</span></span><br><span class="line"><span class="comment">//   - gvisor.dev/gvisor</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Do not remove or change the type signature.</span></span><br><span class="line"><span class="comment">// See go.dev/issue/67401.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//go:linkname wakep</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">wakep</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// Be conservative about spinning threads, only start one if none exist</span></span><br><span class="line"><span class="comment">// already.</span></span><br><span class="line"><span class="keyword">if</span> sched.nmspinning.Load() != <span class="number">0</span> || !sched.nmspinning.CompareAndSwap(<span class="number">0</span>, <span class="number">1</span>) &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Disable preemption until ownership of pp transfers to the next M in</span></span><br><span class="line"><span class="comment">// startm. Otherwise preemption here would leave pp stuck waiting to</span></span><br><span class="line"><span class="comment">// enter _Pgcstop.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// See preemption comment on acquirem in startm for more details.</span></span><br><span class="line">mp := acquirem()</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> pp *p</span><br><span class="line">lock(&amp;sched.lock)</span><br><span class="line">pp, _ = pidlegetSpinning(<span class="number">0</span>)</span><br><span class="line"><span class="keyword">if</span> pp == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">if</span> sched.nmspinning.Add(<span class="number">-1</span>) &lt; <span class="number">0</span> &#123;</span><br><span class="line">throw(<span class="string">&quot;wakep: negative nmspinning&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">unlock(&amp;sched.lock)</span><br><span class="line">releasem(mp)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Since we always have a P, the race in the &quot;No M is available&quot;</span></span><br><span class="line"><span class="comment">// comment in startm doesn&#x27;t apply during the small window between the</span></span><br><span class="line"><span class="comment">// unlock here and lock in startm. A checkdead in between will always</span></span><br><span class="line"><span class="comment">// see at least one running M (ours).</span></span><br><span class="line">unlock(&amp;sched.lock)</span><br><span class="line"></span><br><span class="line">startm(pp, <span class="literal">true</span>, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">releasem(mp)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/</id>
    <link href="https://cooooing.github.io/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Go%E8%AF%AD%E8%A8%80%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0-Go%E8%AF%AD%E8%A8%80%E5%8D%8F%E7%A8%8B%E8%B0%83%E5%BA%A6%E5%99%A8GPM%E6%A8%A1%E5%9E%8B/"/>
    <published>2025-12-27T06:33:28.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>想深入下 Go 的底层,主要是并发模型、GC和内存管理相关内容。</p>
<p>刘丹冰老师的这本书分为3篇21章<br>第一篇(1~4章)主]]>
    </summary>
    <title>《深入理解Go语言》读书笔记 - Go 语言协程调度器 GPM 模型</title>
    <updated>2025-12-27T06:33:28.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="编程记录" scheme="https://cooooing.github.io/categories/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/"/>
    <category term="Docker" scheme="https://cooooing.github.io/tags/Docker/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="CI/CD" scheme="https://cooooing.github.io/tags/CI-CD/"/>
    <category term="UPX" scheme="https://cooooing.github.io/tags/UPX/"/>
    <content>
      <![CDATA[<h2 id="前言：减小-Go-应用产物体积"><a href="#前言：减小-Go-应用产物体积" class="headerlink" title="前言：减小 Go 应用产物体积"></a>前言：减小 Go 应用产物体积</h2><p>Go 语言通过静态编译生成单一二进制文件，这一特性非常适合用于容器化部署。<br>但在默认构建和打包方式下，Go 应用的最终交付产物，二进制可执行文件或者镜像会比较大。</p><p>这里可以使用下面两种方式减小产物体积：</p><ul><li><strong>使用 UPX 压缩 Go 可执行文件，减少二进制体积</strong></li><li><strong>使用 Docker scratch 镜像，去掉不必要的运行环境</strong></li></ul><h2 id="使用-UPX-压缩-Go-可执行文件"><a href="#使用-UPX-压缩-Go-可执行文件" class="headerlink" title="使用 UPX 压缩 Go 可执行文件"></a>使用 UPX 压缩 Go 可执行文件</h2><p>UPX（Ultimate Packer for eXecutables）是一个<strong>通用的可执行文件压缩工具</strong>。</p><p>UPX 的核心特点是：</p><ul><li><strong>在磁盘上以压缩形式存在</strong></li><li><strong>程序启动时自动解压到内存中执行</strong></li><li>对程序逻辑完全透明，无需改代码</li></ul><p>UPX 将原始可执行文件压缩，在文件头部插入一个极小的解压 stub，程序启动时，stub 将主体代码解压到内存，跳转到真正的入口点执行。<br>所以，它的磁盘占用会显著减少，但内存中仍是完整程序，仅在启动阶段会有一次性的解压开销。</p><p>对于 Go 应用，在使用 UPX 之前，应该尽量减小二进制产物体积（在每个阶段都考虑减小），推荐使用下面的参数进行编译：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CGO_ENABLED=0 \</span><br><span class="line">go build \</span><br><span class="line">  -trimpath \</span><br><span class="line">  -ldflags &quot;-s -w&quot; \</span><br><span class="line">  -o app</span><br></pre></td></tr></table></figure><p>参数说明：</p><ul><li><code>CGO_ENABLED=0</code>：禁用 CGO ，避免链接系统 libc（如 glibc &#x2F; musl），生成完全静态的 Go 二进制。是使用 <code>scratch</code> 镜像的前提，避免运行时依赖系统动态库。</li><li><code>-trimpath</code>：从编译产物中移除本地源码的绝对路径。减小少量二进制体积，避免泄露本地目录结构。</li><li><code>-ldflags &quot;-s -w&quot;</code>：去除符号表和调试信息。<ul><li>-s：去除符号表（symbol table），移除函数名、变量名等符号信息，显著减小二进制体积。</li><li>-w：去除 DWARF 调试信息，移除用于调试和栈回溯的 DWARF 数据，对运行逻辑无影响。</li></ul></li></ul><p>在此基础上，就可以使用 UPX 镜像压缩了。</p><p><strong>安装 UPX：<code>apt install upx-ucl</code></strong></p><p><strong>UPX 使用：<code>upx --best --lzma app</code></strong></p><p>参数说明：</p><ul><li><code>--best</code> | 使用最高压缩等级</li><li><code>--lzma</code> | 使用 LZMA 算法，压缩率最高</li></ul><p>使用 UPX 的代价是：增加 CI 环节的步骤、启动阶段一次性解压开销。</p><h2 id="使用-Docker-Scratch-构建最小运行镜像"><a href="#使用-Docker-Scratch-构建最小运行镜像" class="headerlink" title="使用 Docker Scratch 构建最小运行镜像"></a>使用 Docker Scratch 构建最小运行镜像</h2><p><code>scratch</code> 是 Docker 提供的一个<strong>空白基础镜像</strong>：</p><ul><li>没有操作系统</li><li>没有 shell</li><li>没有任何系统库</li><li>大小为 <strong>0 字节</strong></li></ul><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> scratch</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> app /app</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;/app&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>使用 scratch 的理念是：</p><blockquote><p><strong>只把“程序本身”放进镜像中</strong></p></blockquote><p>非常适合 Go 这类构建产物为完全静态二进制、无动态链接和外部运行时依赖的语言和应用场景。</p><p>使用 scratch 镜像的代价：运行环境没有任何工具，非常不利于调试。</p><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>附一张使用 UPX 和 Docker Scratch 镜像的体积对比<br>原镜像使用没有使用 UPX 压缩，使用 debian:stable-slim 作为运行时镜像<br>效果还是非常明显的：</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/Go%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%EF%BC%9AUPX%E5%8E%8B%E7%BC%A9%E4%B8%8EDockerScratch%E9%95%9C%E5%83%8F/%E9%95%9C%E5%83%8F%E7%9A%84%E4%BD%93%E7%A7%AF%E5%AF%B9%E6%AF%94.png"                        alt="镜像的体积对比.png"                 ></p>]]>
    </content>
    <id>https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/Go%20%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%EF%BC%9AUPX%20%E5%8E%8B%E7%BC%A9%E4%B8%8E%20Docker%20Scratch%20%E9%95%9C%E5%83%8F/</id>
    <link href="https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/Go%20%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2%EF%BC%9AUPX%20%E5%8E%8B%E7%BC%A9%E4%B8%8E%20Docker%20Scratch%20%E9%95%9C%E5%83%8F/"/>
    <published>2025-12-25T11:05:13.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言：减小-Go-应用产物体积"><a href="#前言：减小-Go-应用产物体积" class="headerlink" title="前言：减小 Go 应用产物体积"></a>前言：减小 Go 应用产物体积</h2><p>Go 语言通过静态编译生成单一二进制]]>
    </summary>
    <title>Go 应用部署：UPX 压缩与 Docker Scratch 镜像</title>
    <updated>2025-12-25T11:05:13.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="记录生活" scheme="https://cooooing.github.io/categories/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/"/>
    <category term="年终总结" scheme="https://cooooing.github.io/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
    <category term="记录生活" scheme="https://cooooing.github.io/tags/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<p>已经写了四年的流水账了，完全不知道今年怎么写。过得比去年还要平淡了。<br>脑海中对于今年的记忆好像只有换了工作换了城市…</p><h3 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h3><p>从印象最深影响最大的换工作开始说吧。<br>其实简历一直在断断续续得投了有大半年，投到了现在这个杭州的小公司。</p><p>整个两次面试都没超过半小时，头一天晚上技术面问了一些 go 基础，就十来分钟，第二天中午和领导视频聊了十来分钟。聊完就发了offer。<br>快的难以置信，整个过程不管是我还是这个公司，我都觉得非常草率。<br>草率在就二三十分钟就决定了要完全换个城市工作生活。（不过大概是我投的时候就做好心里准备了</p><p>五月中来杭州，五月底入职。还赶得上杭州的应届生补贴政策，没错，那时候我毕业还没有满一年，但实际上班已经快两年了。<br>等11月份的社保到账之后，就可以去申请了。</p><blockquote><p>换工作后，我的整个技术栈也从 Java 换成了 Go。<br>这个决定不知道会有什么影响，也许很大，但也许很小。</p></blockquote><h3 id="生活"><a href="#生活" class="headerlink" title="生活"></a>生活</h3><p>以前每天都加班，可谓是没有什么生活了。<br>现在虽然单休，但完全不加班。感觉工作时长反而短了。</p><p>朋友六月份也换了工作，来到了杭州。<br>所以后面一起去了西湖、工联CC、苹果授权店、九溪、法喜寺等等。<br>原本这个月准备去灵隐寺的，但可惜感冒了，只能暂时搁置了。</p><p>附大厨周末来我这给我做的牛排、排骨汤和香肠，嘎嘎好吃。<br>那天在我的推荐下，还去看了罗小黑战记2的电影。（吃完就库库睡了一下午，啥也没干，乐</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/2025/%E7%89%9B%E6%8E%92.jpg"                        alt="牛排.jpg"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/2025/%E6%8E%92%E9%AA%A8%E6%B1%A4.jpg"                        alt="排骨汤.jpg"                 ></p><p>最近几天要和他一起把双人成行打个全成就，打完后准备去玩双影奇境。<br>哦，对。今年还买了 steamdeck，也展示下吧。（台式电脑的显卡就空着吧，无限期搁置</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/2025/steamdeck1.jpg"                        alt="steamdeck1.jpg"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/2025/steamdeck2.jpg"                        alt="steamdeck2.jpg"                 ></p><h3 id="摸鱼"><a href="#摸鱼" class="headerlink" title="摸鱼"></a>摸鱼</h3><p>以前摸鱼都是找本书看看，不知不觉也看完了好几本（虽然库里还存了几十本没看<br>找到工作之后，反而不怎么去看书了。大概是没那么焦虑了。学习的节奏也慢了，更多的倒是想实践。</p><p>今年也是挖了很多坑，但没一个填上的。</p><p>首先就是和鱼油们玩的 奶牛放置。鸽鸽和我都准备复刻一个，可惜，我的暂时弃坑了。（此处，催更鸽鸽<br>现在回头去看那段时间的代码，反而觉得写的很不好。</p><p>其次就是实践！挖了一个巨大坑。<br>准备用 go 实现一个完整的论坛社区，主要参考鱼排、V2EX等。<br>这个坑像是无底洞，不知道还要写多久。不过好在有跳佬帮忙写前端，应该是不至于烂尾。</p><p>此外就是在我完全不熟悉的领域了。vibe coding 写前端。<br>给跳佬的 WJU 增加了按难度生成谜题，其实是个没怎么研究明白的数学问题。<br>前两天给棋牌室加了各全是 bug 的单人俄罗斯方块。</p><h3 id="End"><a href="#End" class="headerlink" title="End"></a>End</h3><p>现阶段暂时是稳定的，平平淡淡吧。关于技术之类的，我觉得我需要沉淀沉淀，停下脚步，认真地写点什么。<br>最后，借跳佬的话作为结语：<strong>人生难得成为平凡太郎</strong> </p>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/2025/</id>
    <link href="https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/2025/"/>
    <published>2025-12-20T01:39:32.000Z</published>
    <summary>
      <![CDATA[<p>已经写了四年的流水账了，完全不知道今年怎么写。过得比去年还要平淡了。<br>脑海中对于今年的记忆好像只有换了工作换了城市…</p>
<h3 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h3><p>从]]>
    </summary>
    <title>2025</title>
    <updated>2025-12-20T01:39:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="编程记录" scheme="https://cooooing.github.io/categories/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/"/>
    <category term="Arch Linux" scheme="https://cooooing.github.io/tags/Arch-Linux/"/>
    <content>
      <![CDATA[<h2 id="系统"><a href="#系统" class="headerlink" title="系统"></a>系统</h2><p>之前就在虚拟机和笔记本上装过 ArchLinux，想尝试尝试 Linux 系统作为主力开发系统。<br>主要原因是 windows 的命令行很难用，即便是有了 powershell，也改变不了使用它的痛苦。<br>特别是之前做 CI&#x2F;CD 写 Makefile、Dokcerfile 和 github workflow.yaml 。光是路径问题就花了很久。<br>替换的契机是前几天用 rdp 远程开发的时候，连接断开（第二次了）。等到回去一看，电脑没有任何反应，应该是死机了。不知道任何原因。<br>所以花了两三天，给电脑刷了 ArchLinux 系统。</p><p>之前有写过一篇安装过程，其实还是很繁琐的。<br>后来发现其实系统镜像内置了方便的工具 <em><a class="link"   href="https://wiki.archlinux.org/title/Archinstall" >archinstall<i class="fas fa-external-link-alt"></i></a></em> ，只需要根据选项进行配置就可以了。<br>比我自己手动装的要好不少（一些配置方面</p><p>装完之后难免要去自己自定义美化一下，虽然默认的审美上比以前用过的 ubuntu 桌面之类的有些提升。<br>下面就是都装完之后，选个好看的壁纸：</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/ArchLinux%E4%BD%9C%E4%B8%BA%E4%B8%BB%E5%8A%9B%E7%B3%BB%E7%BB%9F/arch-desktop.png"                        alt="arch-desktop"                 ></p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><h3 id="输入法"><a href="#输入法" class="headerlink" title="输入法"></a>输入法</h3><p>使用这个系统目前还是有很多问题的，主要集中在 KDE 的 wayland 这个桌面和图形平台上。<br>最严重的莫过于输入法的问题了，纯英文情况下还可以，只要配置得当，不会有什么问题。但是中文就不太行了。</p><p>Jetbrains Idea 至今还不支持。详见：(Wayland: support input methods (text-input-unstable-v3))[<a class="link"   href="https://youtrack.jetbrains.com/issue/JBR-5672/Wayland-support-input-methods-text-input-unstable-v3]" >https://youtrack.jetbrains.com/issue/JBR-5672/Wayland-support-input-methods-text-input-unstable-v3]<i class="fas fa-external-link-alt"></i></a><br>好消息是将在2025.3版本支持，坏消息是目前最新正式版本是2025.2.5。也是给我赶上了，明年就能用了（好耶！）</p><p>VSCode 我以为也不会有问题的，结果和 Idea 的表现一模一样。（用的微软官方提供的 <a class="link"   href="https://aur.archlinux.org/packages/visual-studio-code-bin/" >visual-studio-code-bin<i class="fas fa-external-link-alt"></i></a> 版本<br>不过好消息是可以解决。</p><p>修改 <code>~/.config/code-flags.conf</code> 文件即可（文件默认不存在，需要创建），添加以下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">--enable-features=UseOzonePlatform</span><br><span class="line">--ozone-platform-hint=wayland</span><br><span class="line">--enable-wayland-ime</span><br></pre></td></tr></table></figure><h3 id="远程控制"><a href="#远程控制" class="headerlink" title="远程控制"></a>远程控制</h3><p>另一个问题就是远程控制了，对于远程开发来说还是很重要的功能。<br>但很可惜，wayland 在这方面做的很不好，可能与他们的安全限制有关系，无法做到无人值守的状态。</p><p>目前只能是使用 ssh 了。对于桌面可能就无缘了。</p><p>不过对于 Idea 远程开发，使用了它们的 Jetbrains Gateway 的方案。不得不说，各种问题，光是安装就费了我很大的劲，不过好在安装完可以用，虽然体验没那么好。<br>Jetbrains 在远程开发和 AI 方面真的是没有跟上。</p><p>后续可能会尝试  VSCode 的远程开发，不过我用惯了 Jetbrains，是真不习惯 VSCode。</p><h3 id="2025-12-07-后记"><a href="#2025-12-07-后记" class="headerlink" title="2025-12-07 后记"></a>2025-12-07 后记</h3><p>终于是受不了 Wayland 的各种问题了，决定回归初心。作为开发环境，需要的是稳定，默认的也许就是最好的，于是决定转向 Ubuntu。<br>选择的系统是 Linux Mint，在经过了一个晚上之后，就得到了：</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/ArchLinux%E4%BD%9C%E4%B8%BA%E4%B8%BB%E5%8A%9B%E7%B3%BB%E7%BB%9F/mint-desktop.png"                        alt="mint-desktop"                 ></p><p>标题也许换成 《Linux 作为主力系统》 更合适。</p>]]>
    </content>
    <id>https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/ArchLinux%E4%BD%9C%E4%B8%BA%E4%B8%BB%E5%8A%9B%E7%B3%BB%E7%BB%9F/</id>
    <link href="https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/ArchLinux%E4%BD%9C%E4%B8%BA%E4%B8%BB%E5%8A%9B%E7%B3%BB%E7%BB%9F/"/>
    <published>2025-11-30T10:14:07.000Z</published>
    <summary>
      <![CDATA[<h2 id="系统"><a href="#系统" class="headerlink" title="系统"></a>系统</h2><p>之前就在虚拟机和笔记本上装过 ArchLinux，想尝试尝试 Linux 系统作为主力开发系统。<br>主要原因是 windows 的命令行]]>
    </summary>
    <title>ArchLinux作为主力系统</title>
    <updated>2025-11-30T10:14:07.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="记录生活" scheme="https://cooooing.github.io/categories/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/"/>
    <category term="旅游" scheme="https://cooooing.github.io/tags/%E6%97%85%E6%B8%B8/"/>
    <category term="西湖" scheme="https://cooooing.github.io/tags/%E8%A5%BF%E6%B9%96/"/>
    <category term="法喜寺" scheme="https://cooooing.github.io/tags/%E6%B3%95%E5%96%9C%E5%AF%BA/"/>
    <content>
      <![CDATA[<p>这一篇不止是徒步线的记录，还记录了国庆假期左右的其他，其实分两三篇应该会更好。</p><h3 id="国庆前"><a href="#国庆前" class="headerlink" title="国庆前"></a>国庆前</h3><p>首先国庆和中秋连在一起放了八天假期。<br>公司在节前发了蟹卡（五公五母十只螃蟹）和两个柚子。螃蟹带回家了没有尝到，柚子倒是很不错。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E8%9F%B9%E5%8D%A1.jpg"                        alt="蟹卡"                 ></p><h3 id="家"><a href="#家" class="headerlink" title="家"></a>家</h3><p>国庆假期的前几天在家，久违的过年的感觉。只是比过年的人少了很多，没有那么的热闹，但也足够了。<br>和朋友去吃了火锅，六个人吃了不到四百，人均才六十多，不到七十。相比我之前和现在工作的地方真的是便宜太多了。<br>那家火锅店也是很偏僻了，在县城的边边上，旁边就是农田了，对面就是护城河。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E7%81%AB%E9%94%85%E5%BA%97.jpg"                        alt="火锅店"                 ></p><p>朋友还跑了两三家药店帮我买药，我跑了几家也没得卖。最后买到了四小瓶。<br>后来朋友妈妈知道了还打电话来和我说，因为朋友舅舅得过这个病，所以她比较了解，给我推荐了别的药。<br>真的非常感谢了。</p><p>去姨妈家吃饭，在门口盆里的鸡胗和肠子还被猫叼跑了。痛失两道菜。<br>一共四只猫，还为了这些吃的打架。<br>对峙吵架中…</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E7%8C%AB%E5%92%AA%E5%AF%B9%E5%B3%99.jpg"                        alt="猫咪对峙"                 ></p><p>吃完后就躺在树下的花坛边睡觉，过得真舒服啊。</p><h3 id="徒步"><a href="#徒步" class="headerlink" title="徒步"></a>徒步</h3><p>假期后半程回到杭州，准备和朋友去徒步。<br>计划路线是：九溪公交站 - 九溪烟树 - 九溪十八涧 - 龙井村 - 十里锒铛 - 云栖竹径<br>但是实际上并没有走这条线。</p><p>我俩在九溪公交站会合，这里可以看到钱塘江：</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E4%B9%9D%E6%BA%AA%E5%85%AC%E4%BA%A4%E7%AB%99.jpg"                        alt="九溪公交站"                 ></p><p>到了之后，就开始向里进发。应该是沿着小溪一路向上，不多久就可以到九溪烟树。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E4%B9%9D%E6%BA%AA%E7%83%9F%E6%A0%911.jpg"                        alt="九溪烟树1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E4%B9%9D%E6%BA%AA%E7%83%9F%E6%A0%912.jpg"                        alt="九溪烟树2"                 ></p><p>再往后，一路上都能碰到各种小溪。喜欢玩水的人会非常开心了，大热天玩水也是很舒服。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E5%B0%8F%E6%BA%AA1.jpg"                        alt="小溪1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E5%B0%8F%E6%BA%AA2.jpg"                        alt="小溪2"                 ></p><p>然后就是龙井村，这里的每家每户好像都卖茶叶。<br>这里商业化不是非常严重，每家都是独栋建筑，有些巷子可以看到别人家的院子，有些在路边会有弄得很好看的花园。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E9%BE%99%E4%BA%95%E6%9D%91.jpg"                        alt="龙井村"                 ></p><p>在龙井村这里，我们偏离了原定路线。准备接着向北走，去法喜寺尝下素斋。<br>此时已经下午快一点，除了路上带的几瓶水，两块面包和几块巧克力，我们没吃别的。（饿啊<br>但后面是上山的路，也是最难走的。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E8%8C%B6%E7%94%B01.jpg"                        alt="茶田1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E8%8C%B6%E7%94%B02.jpg"                        alt="茶田2"                 ></p><p>接着爬到山顶，有个三叉还是四岔路，往法喜寺的方向是下山的方向。一路上都是树荫遮蔽，比上山的茶田一路上晒过来要好了很多。<br>出了树林，有一段很长很长的下坡，坡度很大。没有阶梯，纯坡。看着护林员骑着电动车走大S线骑上来…</p><p>下山后，到法喜寺。进门每人给了三柱香。（朋友说他是党员，所以我有了六柱<br>寺门口有很多人在摸字。<br>寺里的锦鲤很多很好看。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E6%B3%95%E5%96%9C%E5%AF%BA1.jpg"                        alt="法喜寺1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E6%B3%95%E5%96%9C%E5%AF%BA2.jpg"                        alt="法喜寺2"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E6%B3%95%E5%96%9C%E5%AF%BA3.jpg"                        alt="法喜寺3"                 ></p><p>在法喜寺休息了很久，有大麦茶和菊花茶。<br>可惜的是因为在修缮的原因，没有吃到素斋。</p><p>不过出了寺庙之后，去吃了一家素食自主，是今天的第一顿饭了。此时已经四点了大概（有点难崩，不过吃了三碗</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E7%B4%A0%E6%96%8B.jpg"                        alt="素斋"                 ></p><p>最后迎着夕阳去往公交站，准备坐公交去云栖竹径（他说是不能忘记最开始的目标</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/%E5%A4%95%E9%98%B3.jpg"                        alt="夕阳"                 ></p><p>公交等了很久很久，324H线！<br>等车的时候，遇到了一家印度人（应该是印度人，非常经典的印度形象）。应该是老父亲，他的英语和中文说的都不错，和他儿子交流是他们自己的语言。<br>大概聊了一些。</p><p>等坐上公交，到达云栖竹径时，已经七点了，天已经黑了。<br>然后云栖竹径关门了！很遗憾，只能回家了。</p><p>遗憾也是常态，不按计划、随心而动的旅途才是放松身心的旅游。相遇是缘，不遇也是。<br>没有赶进度的计划，没有被时间追着跑。能在公交站等一个小时的公交，和遇到印度人聊聊天也不错，不是吗。</p>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/</id>
    <link href="https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E6%9D%AD%E5%B7%9E%E4%B9%9D%E6%BA%AA%E8%87%B3%E4%BA%91%E6%A0%96%E7%AB%B9%E5%BE%84%E5%BE%92%E6%AD%A5%E7%BA%BF/"/>
    <published>2025-10-23T12:41:49.000Z</published>
    <summary>
      <![CDATA[<p>这一篇不止是徒步线的记录，还记录了国庆假期左右的其他，其实分两三篇应该会更好。</p>
<h3 id="国庆前"><a href="#国庆前" class="headerlink" title="国庆前"></a>国庆前</h3><p>首先国庆和中秋连在一起放了八天假期。<]]>
    </summary>
    <title>杭州九溪至云栖竹径徒步线</title>
    <updated>2025-10-23T12:41:49.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="编程记录" scheme="https://cooooing.github.io/categories/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/"/>
    <category term="Arch Linux" scheme="https://cooooing.github.io/tags/Arch-Linux/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Ubuntu、Deepin之类的玩腻了，尝试下新鲜玩意。<br>最开始在旧电脑上尝试直接安装，但是失败了，很多报错，现在看来是镜像源的问题。<br>然后尝试在 Hyper-V 虚拟机中安装，过程很顺利，基本没有问题。<br>最后再次尝试在旧电脑物理机上安装，有了虚拟机的经验，安装也比较顺利。<br><strong>唯一失败的就是：分区时没注意磁盘，将分区信息写到了系统u盘，导致u盘数据丢失。惨痛的教训 <code>lsblk</code> 或者 <code>fdisk -l</code> 很重要！</strong></p><h2 id="安装-Arch-Linux-系统"><a href="#安装-Arch-Linux-系统" class="headerlink" title="安装 Arch Linux 系统"></a>安装 Arch Linux 系统</h2><p>首先进入 Arch Live 环境（archiso），是官方 ISO 引导后的安装环境。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Arch Linux 6.15.8-arch1-2 (tty1)</span><br><span class="line"></span><br><span class="line">archiso login:root (automatic login)</span><br><span class="line"></span><br><span class="line">To install Arch Linux follow the installation guide:</span><br><span class="line">https://wiki.archlinux.org/title/Installation_guide</span><br><span class="line"></span><br><span class="line">For Wi-Fi,authenticate to the wireless network using the iwctl utility.</span><br><span class="line">For mobile broadband (WWAN) modems,comnect with the mmcli utility.</span><br><span class="line">Ethernet,WLAN and WWAN interfaces using DHCP should work automatically.</span><br><span class="line"></span><br><span class="line">After comnecting to the internet,the installation guide can be accessed</span><br><span class="line">via the convenience script Installation_guide.</span><br><span class="line"></span><br><span class="line">root@archiso ~ #</span><br></pre></td></tr></table></figure><h3 id="配置网络"><a href="#配置网络" class="headerlink" title="配置网络"></a>配置网络</h3><p><code>ping archlinux.org -c 3</code> 确认是否有网络</p><p>如果是有线网络，通常自动就有网络。如果是 WIFI ，使用 <code>iwctl</code> 命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">iwctl</span><br><span class="line"><span class="comment"># 交互模式下（示例）</span></span><br><span class="line">device list</span><br><span class="line">station wlan0 scan</span><br><span class="line">station wlan0 get-networks</span><br><span class="line">station wlan0 connect YOUR_SSID</span><br><span class="line">station list <span class="comment"># 用于查看连接状态</span></span><br><span class="line"><span class="comment"># exit 返回</span></span><br></pre></td></tr></table></figure><h3 id="启动模式"><a href="#启动模式" class="headerlink" title="启动模式"></a>启动模式</h3><p><code>ls /sys/firmware/efi/efivars</code> 用于输出 <strong>EFI 变量</strong>，区分启动模式是 BIOS 还是 UEFI。</p><ul><li>BIOS 启动：内核只会得到传统的硬件初始化信息，不会加载 EFI 相关功能。</li><li>UEFI 启动：内核会通过 UEFI 固件接口（EFI Runtime Services）获得一系列 EFI 变量（比如引导顺序、启动项、安全启动开关等）。Linux 内核会把这些变量暴露在文件系统里，就是 <code>/sys/firmware/efi/efivars</code>。</li></ul><p>所以 UEFI 启动必须有一个 <strong>EFI 分区 (ESP)</strong>，格式化为 FAT32，挂载到 <code>/boot</code> 或 <code>/boot/efi</code>。</p><h4 id="BIOS-与-UEFI"><a href="#BIOS-与-UEFI" class="headerlink" title="BIOS 与 UEFI"></a>BIOS 与 UEFI</h4><p>BIOS：</p><ul><li>历史悠久：BIOS（Basic Input&#x2F;Output System）是上世纪 80 年代就有的固件。</li><li>分区表限制：BIOS 通常和 <strong>MBR 分区表</strong>搭配使用。MBR 最大只支持 <strong>2TB 硬盘容量</strong>，最多 4 个主分区。</li><li>启动方式：BIOS 启动时，会从磁盘开头的 <strong>MBR (Master Boot Record)</strong> 读取引导代码，然后加载操作系统。</li><li>兼容性好：老机器几乎都是 BIOS，新机器大多支持兼容模式（CSM），可以让 BIOS 模式继续使用。</li></ul><p>UEFI：</p><ul><li>现代标准：UEFI（Unified Extensible Firmware Interface）是 BIOS 的替代品，大多数 2015 年以后的电脑默认用 UEFI。</li><li>分区表支持：UEFI 通常和 <strong>GPT 分区表</strong>搭配。GPT 支持 <strong>&gt;2TB 的硬盘</strong>，分区数量几乎无限制。</li><li>启动方式：UEFI 会读取硬盘上的 <strong>EFI 系统分区 (ESP)</strong>，里面存放引导程序（例如 <code>grubx64.efi</code> 或 <code>systemd-boot</code>）。</li><li>功能更强：支持图形界面、鼠标操作、安全启动（Secure Boot）、多系统管理更方便。</li></ul><h3 id="分区设置"><a href="#分区设置" class="headerlink" title="分区设置"></a>分区设置</h3><p><strong>一定注意分区设置的位置！</strong><br><strong>事先使用 <code>lsblk</code> 或者 <code>fdisk -l</code> 命令查看磁盘信息，确定分区位置。</strong></p><h4 id="创建分区"><a href="#创建分区" class="headerlink" title="创建分区"></a>创建分区</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fdisk /dev/sda</span><br></pre></td></tr></table></figure><p>然后依次输入：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">g                   # 创建 GPT 分区表</span><br><span class="line"></span><br><span class="line">n                   # 新建分区 1</span><br><span class="line">&lt;回车&gt;              # 分区号，直接回车（默认 1）</span><br><span class="line">&lt;回车&gt;              # 起始扇区，直接回车（默认）</span><br><span class="line">+512M               # 结束扇区，输入大小 → +512M</span><br><span class="line">t                   # 改类型</span><br><span class="line">1                   # 选择分区 1</span><br><span class="line">1                   # 设置为 EFI System</span><br><span class="line"></span><br><span class="line">n                   # 新建分区 2</span><br><span class="line">&lt;回车&gt;              # 分区号，直接回车（默认 2）</span><br><span class="line">&lt;回车&gt;              # 起始扇区，直接回车</span><br><span class="line">+2G                 # 结束扇区，输入大小 → +2G</span><br><span class="line">t                   # 改类型</span><br><span class="line">2                   # 选择分区 2</span><br><span class="line">19                  # 设置为 Linux swap</span><br><span class="line"></span><br><span class="line">n                   # 新建分区 3</span><br><span class="line">&lt;回车&gt;              # 分区号，直接回车（默认 3）</span><br><span class="line">&lt;回车&gt;              # 起始扇区，直接回车</span><br><span class="line">&lt;回车&gt;              # 结束扇区，直接回车（用完剩余空间）</span><br><span class="line"></span><br><span class="line">w                   # 保存并退出</span><br></pre></td></tr></table></figure><h4 id="格式化分区"><a href="#格式化分区" class="headerlink" title="格式化分区"></a>格式化分区</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># EFI 分区</span></span><br><span class="line">mkfs.fat -F32 /dev/sda1</span><br><span class="line"></span><br><span class="line"><span class="comment"># swap 分区</span></span><br><span class="line">mkswap /dev/sda2</span><br><span class="line">swapon /dev/sda2</span><br><span class="line"></span><br><span class="line"><span class="comment"># 根分区</span></span><br><span class="line">mkfs.ext4 /dev/sda3</span><br></pre></td></tr></table></figure><h4 id="挂载分区"><a href="#挂载分区" class="headerlink" title="挂载分区"></a>挂载分区</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mount /dev/sda3 /mnt</span><br><span class="line"><span class="built_in">mkdir</span> -p /mnt/boot</span><br><span class="line">mount /dev/sda1 /mnt/boot</span><br></pre></td></tr></table></figure><h3 id="配置镜像源（可选）"><a href="#配置镜像源（可选）" class="headerlink" title="配置镜像源（可选）"></a>配置镜像源（可选）</h3><ol><li>临时使用国内镜像<br>在 live 环境里，修改 pacman 的镜像源列表： <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先备份原来的</span></span><br><span class="line"><span class="built_in">cp</span> /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak</span><br><span class="line"></span><br><span class="line"><span class="comment"># 编辑镜像列表</span></span><br><span class="line">nano /etc/pacman.d/mirrorlist</span><br></pre></td></tr></table></figure>把国内源放到前面，例如： <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch</span><br><span class="line">Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch</span><br><span class="line">Server = https://mirrors.aliyun.com/archlinux/$repo/os/$arch</span><br></pre></td></tr></table></figure><blockquote><p>注意：把想用的镜像放在 <strong>最上面</strong>，pacman 会优先使用。<br>保存退出后，运行：<br> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -Syyu</span><br></pre></td></tr></table></figure></p></blockquote></li><li>选择最快的镜像<br>Arch 自带 <code>reflector</code> 工具（Live ISO 可能没有，需要先安装）： <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pacman -Sy reflector --noconfirm</span><br><span class="line">reflector --country China --latest 5 --<span class="built_in">sort</span> rate --save /etc/pacman.d/mirrorlist</span><br></pre></td></tr></table></figure>会自动选择最近最快的中国镜像。</li></ol><h3 id="安装基础系统"><a href="#安装基础系统" class="headerlink" title="安装基础系统"></a>安装基础系统</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacstrap /mnt base linux linux-firmware vim nano networkmanager</span><br></pre></td></tr></table></figure><p>安装完成后生成 fstab：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">genfstab -U /mnt &gt;&gt; /mnt/etc/fstab</span><br></pre></td></tr></table></figure><p>然后进入新系统环境：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arch-chroot /mnt</span><br></pre></td></tr></table></figure><h2 id="安装并配置-bootloader（UEFI-推荐-GRUB-或-systemd-boot）"><a href="#安装并配置-bootloader（UEFI-推荐-GRUB-或-systemd-boot）" class="headerlink" title="安装并配置 bootloader（UEFI 推荐 GRUB 或 systemd-boot）"></a>安装并配置 bootloader（<strong>UEFI 推荐 GRUB 或 systemd-boot</strong>）</h2><h4 id="使用-GRUB（UEFI）"><a href="#使用-GRUB（UEFI）" class="headerlink" title="使用 GRUB（UEFI）"></a>使用 GRUB（UEFI）</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pacman -S --noconfirm grub efibootmgr dosfstools os-prober mtools</span><br><span class="line">grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB</span><br><span class="line">grub-mkconfig -o /boot/grub/grub.cfg</span><br></pre></td></tr></table></figure><h4 id="使用-GRUB（BIOS）"><a href="#使用-GRUB（BIOS）" class="headerlink" title="使用 GRUB（BIOS）"></a>使用 GRUB（BIOS）</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pacman -S --noconfirm grub</span><br><span class="line">grub-install --target=i386-pc /dev/sda   <span class="comment"># 注意写在磁盘（如 /dev/sda），不是分区</span></span><br><span class="line">grub-mkconfig -o /boot/grub/grub.cfg</span><br></pre></td></tr></table></figure><h3 id="系统其余配置"><a href="#系统其余配置" class="headerlink" title="系统其余配置"></a>系统其余配置</h3><ol><li>时区： <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ln</span> -sf /usr/share/zoneinfo/Asia/Taipei /etc/localtime</span><br><span class="line">hwclock --systohc</span><br></pre></td></tr></table></figure></li><li>本地化（示例同时启用 zh_CN.UTF-8 与 en_US.UTF-8）： <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sed -i <span class="string">&#x27;s/^#zh_CN.UTF-8/zh_CN.UTF-8/&#x27;</span> /etc/locale.gen</span><br><span class="line">sed -i <span class="string">&#x27;s/^#en_US.UTF-8/en_US.UTF-8/&#x27;</span> /etc/locale.gen</span><br><span class="line">locale-gen</span><br><span class="line"><span class="built_in">echo</span> LANG=zh_CN.UTF-8 &gt; /etc/locale.conf</span><br><span class="line"><span class="built_in">echo</span> KEYMAP=us &gt; /etc/vconsole.conf   <span class="comment"># 如需中文控制台可改为 zh_CN</span></span><br></pre></td></tr></table></figure></li><li>主机名与 hosts： <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="built_in">arch</span> &gt; /etc/hostname</span><br><span class="line"><span class="built_in">cat</span> &gt;&gt; /etc/hosts &lt;&lt;<span class="string">EOF</span></span><br><span class="line"><span class="string">127.0.0.1localhost</span></span><br><span class="line"><span class="string">::1    localhost</span></span><br><span class="line"><span class="string">EOF</span></span><br></pre></td></tr></table></figure></li><li>root 密码： <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd</span><br></pre></td></tr></table></figure></li></ol><h3 id="安装-ssh-服务"><a href="#安装-ssh-服务" class="headerlink" title="安装 ssh 服务"></a>安装 ssh 服务</h3><p><code>sudo pacman -S --noconfirm openssh</code><br><code>sudo systemctl enable sshd</code><br><code>sudo systemctl start sshd</code></p><p>记得编辑<code>/etc/ssh/sshd_config</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">PasswordAuthentication yes   # 允许密码登录</span><br><span class="line">PermitRootLogin yes          # 如果你用 root 登录（可选）</span><br><span class="line">UsePAM yes                   # 必须启用 PAM</span><br></pre></td></tr></table></figure><p><code>sudo systemctl restart sshd</code></p><h2 id="安装-KDE-桌面环境"><a href="#安装-KDE-桌面环境" class="headerlink" title="安装 KDE 桌面环境"></a>安装 KDE 桌面环境</h2><p><code>sudo pacman -Syu</code> 更新系统<br><code>sudo pacman -S --noconfirm xorg</code> 安装 Xorg（显示服务器）</p><p>安装 KDE Plasma 桌面和常用应用：</p><ul><li><code>sudo pacman -S --noconfirm plasma kde-system-meta kde-utilities-meta</code> 最小化安装，比较轻量</li><li><code>sudo pacman -S --noconfirm plasma kde-applications</code> 完整安装（含所有 KDE 应用，比如浏览器、相册、邮件客户端等）</li></ul><p>安装登录管理器（推荐 SDDM）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -S --noconfirm sddm</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> sddm</span><br></pre></td></tr></table></figure><p><code>sudo pacman -S noto-fonts noto-fonts-cjk noto-fonts-emoji adobe-source-han-sans-cn-fonts adobe-source-han-serif-cn-fonts</code> 安装 KDE 常用中文字体</p><p>手动指定QT环境变量，在 <code>~/.xprofile</code> 或 <code>~/.bashrc</code> 里加上：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> LANG=zh_CN.UTF-8</span><br><span class="line"><span class="built_in">export</span> LC_ALL=zh_CN.UTF-8</span><br><span class="line"><span class="built_in">export</span> QT_QPA_PLATFORMTHEME=kde</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/ArchLinux%E5%AE%89%E8%A3%85/</id>
    <link href="https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/ArchLinux%E5%AE%89%E8%A3%85/"/>
    <published>2025-09-21T14:38:38.000Z</published>
    <summary>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Ubuntu、Deepin之类的玩腻了，尝试下新鲜玩意。<br>最开始在旧电脑上尝试直接安装，但是失败了，很多报错，现在看来是镜像源的问题。]]>
    </summary>
    <title>ArchLinux安装</title>
    <updated>2025-09-21T14:38:38.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="学习笔记" scheme="https://cooooing.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="时间" scheme="https://cooooing.github.io/tags/%E6%97%B6%E9%97%B4/"/>
    <content>
      <![CDATA[<h2 id="Go-时间布局格式"><a href="#Go-时间布局格式" class="headerlink" title="Go 时间布局格式"></a>Go 时间布局格式</h2><p>Go 使用 <strong>参考时间</strong> <code>Mon Jan 2 15:04:05 MST 2006</code> 来定义格式，而不是像 C 语言或 Python 的 <code>%Y %m %d</code>。</p><table><thead><tr><th>类型</th><th>标记</th><th>含义</th><th>示例（参考时间 2025-09-06 11:45:05.123456789）</th><th>注意事项</th></tr></thead><tbody><tr><td><strong>年</strong></td><td><code>2006</code></td><td>四位年份</td><td><code>2025</code></td><td>常用于完整年份显示</td></tr><tr><td></td><td><code>06</code></td><td>两位年份</td><td><code>25</code></td><td>适合老式格式或节省字符</td></tr><tr><td><strong>月</strong></td><td><code>01</code></td><td>两位数字月份</td><td><code>09</code></td><td>0 填充</td></tr><tr><td></td><td><code>1</code></td><td>数字月份（无前导零）</td><td><code>9</code></td><td>方便简写</td></tr><tr><td></td><td><code>Jan</code></td><td>英文缩写月份</td><td><code>Sep</code></td><td>三个字母英文缩写</td></tr><tr><td></td><td><code>January</code></td><td>英文全称月份</td><td><code>September</code></td><td>更易读，长度可变</td></tr><tr><td><strong>日</strong></td><td><code>02</code></td><td>两位数字日</td><td><code>06</code></td><td>0 填充</td></tr><tr><td></td><td><code>_2</code></td><td>空格填充日</td><td><code> 6</code></td><td>用于对齐，日&lt;10时左侧空格</td></tr><tr><td><strong>小时</strong></td><td><code>15</code></td><td>24 小时制</td><td><code>11</code></td><td>0-23</td></tr><tr><td></td><td><code>03</code></td><td>12 小时制</td><td><code>11</code></td><td>0-12，0 会显示为 12</td></tr><tr><td></td><td><code>3</code></td><td>12 小时制，无前导零</td><td><code>11</code></td><td>0-12</td></tr><tr><td><strong>分钟&#x2F;秒</strong></td><td><code>04</code></td><td>分钟</td><td><code>45</code></td><td>两位数字</td></tr><tr><td></td><td><code>05</code></td><td>秒</td><td><code>05</code></td><td>两位数字</td></tr><tr><td><strong>上下午</strong></td><td><code>PM</code></td><td>大写 AM&#x2F;PM</td><td><code>AM</code></td><td>12 小时制，区分大小写</td></tr><tr><td></td><td><code>pm</code></td><td>小写 am&#x2F;pm</td><td><code>am</code></td><td>12 小时制，区分大小写</td></tr><tr><td><strong>时区</strong></td><td><code>MST</code></td><td>时区缩写</td><td><code>SGT</code> &#x2F; <code>CST</code> &#x2F; <code>UTC</code></td><td>受系统时区影响，可能有歧义</td></tr><tr><td></td><td><code>-0700</code></td><td>数值时区偏移（无冒号）</td><td><code>+0800</code></td><td>可解析性高，推荐用于存储&#x2F;传输</td></tr><tr><td></td><td><code>Z07:00</code></td><td>数值时区偏移（含冒号）</td><td><code>+08:00</code> 或 <code>Z</code>（UTC）</td><td>RFC3339 常用</td></tr><tr><td><strong>引号</strong></td><td><code>&#39;...&#39;</code></td><td>单引号内内容按字面量输出</td><td><code>&quot;&#39;06&quot;</code> → <code>&#39;25</code></td><td>可在格式中插入固定字符</td></tr><tr><td><strong>小数秒</strong></td><td><code>.000</code></td><td>毫秒</td><td><code>.123</code></td><td>固定 3 位小数</td></tr><tr><td></td><td><code>.000000</code></td><td>微秒</td><td><code>.123456</code></td><td>固定 6 位小数</td></tr><tr><td></td><td><code>.000000000</code></td><td>纳秒</td><td><code>.123456789</code></td><td>固定 9 位小数</td></tr><tr><td><strong>星期</strong></td><td><code>Mon</code></td><td>英文缩写星期</td><td><code>Sat</code></td><td>三个字母</td></tr><tr><td></td><td><code>Monday</code></td><td>英文全称星期</td><td><code>Saturday</code></td><td>适合全名显示</td></tr><tr><td><strong>其他符号</strong></td><td><code>T</code></td><td>ISO 8601 中的时间分隔符</td><td><code>T</code></td><td>直接输出字母 T</td></tr><tr><td></td><td><code>-</code> <code>/</code> <code>:</code> <code> </code></td><td>直接输出</td><td><code>-</code>、<code>/</code>、<code>:</code></td><td>可以自由组合布局</td></tr></tbody></table><p>注意事项</p><ol><li><strong>空格填充 vs 零填充</strong>：<ul><li><code>_2</code> → 空格填充的日，<code>02</code> → 零填充。</li></ul></li><li><strong>时区选择</strong>：<ul><li><code>MST</code> → 缩写，易读但解析可能歧义。</li><li><code>-0700</code>&#x2F;<code>Z07:00</code> → 数值偏移，更标准、更安全。</li></ul></li><li><strong>12 小时制 vs 24 小时制</strong>：<ul><li><code>3</code>&#x2F;<code>03</code> → 12 小时制，配合 <code>PM</code>&#x2F;<code>pm</code>。</li><li><code>15</code> → 24 小时制。</li></ul></li><li><strong>小数秒</strong>：<ul><li><code>StampMilli</code> &#x2F; <code>StampMicro</code> &#x2F; <code>StampNano</code> 都基于 <code>.000</code>&#x2F;<code>.000000</code>&#x2F;<code>.000000000</code>。</li></ul></li></ol><h2 id="Go-time-包时间格式常量"><a href="#Go-time-包时间格式常量" class="headerlink" title="Go time 包时间格式常量"></a>Go time 包时间格式常量</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> time</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">Layout      = <span class="string">&quot;01/02 03:04:05PM &#x27;06 -0700&quot;</span> <span class="comment">// The reference time, in numerical order.</span></span><br><span class="line">ANSIC       = <span class="string">&quot;Mon Jan _2 15:04:05 2006&quot;</span></span><br><span class="line">UnixDate    = <span class="string">&quot;Mon Jan _2 15:04:05 MST 2006&quot;</span></span><br><span class="line">RubyDate    = <span class="string">&quot;Mon Jan 02 15:04:05 -0700 2006&quot;</span></span><br><span class="line">RFC822      = <span class="string">&quot;02 Jan 06 15:04 MST&quot;</span></span><br><span class="line">RFC822Z     = <span class="string">&quot;02 Jan 06 15:04 -0700&quot;</span> <span class="comment">// RFC822 with numeric zone</span></span><br><span class="line">RFC850      = <span class="string">&quot;Monday, 02-Jan-06 15:04:05 MST&quot;</span></span><br><span class="line">RFC1123     = <span class="string">&quot;Mon, 02 Jan 2006 15:04:05 MST&quot;</span></span><br><span class="line">RFC1123Z    = <span class="string">&quot;Mon, 02 Jan 2006 15:04:05 -0700&quot;</span> <span class="comment">// RFC1123 with numeric zone</span></span><br><span class="line">RFC3339     = <span class="string">&quot;2006-01-02T15:04:05Z07:00&quot;</span></span><br><span class="line">RFC3339Nano = <span class="string">&quot;2006-01-02T15:04:05.999999999Z07:00&quot;</span></span><br><span class="line">Kitchen     = <span class="string">&quot;3:04PM&quot;</span></span><br><span class="line"><span class="comment">// Handy time stamps.</span></span><br><span class="line">Stamp      = <span class="string">&quot;Jan _2 15:04:05&quot;</span></span><br><span class="line">StampMilli = <span class="string">&quot;Jan _2 15:04:05.000&quot;</span></span><br><span class="line">StampMicro = <span class="string">&quot;Jan _2 15:04:05.000000&quot;</span></span><br><span class="line">StampNano  = <span class="string">&quot;Jan _2 15:04:05.000000000&quot;</span></span><br><span class="line">DateTime   = <span class="string">&quot;2006-01-02 15:04:05&quot;</span></span><br><span class="line">DateOnly   = <span class="string">&quot;2006-01-02&quot;</span></span><br><span class="line">TimeOnly   = <span class="string">&quot;15:04:05&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="Go-time-包时间格式常量对照表"><a href="#Go-time-包时间格式常量对照表" class="headerlink" title="Go time 包时间格式常量对照表"></a>Go time 包时间格式常量对照表</h2><table><thead><tr><th>名称</th><th>布局字符串</th><th>示例输出（2025-09-06 11:45:05 +08:00）</th><th>典型用途</th><th>注意事项</th></tr></thead><tbody><tr><td><strong>Layout</strong></td><td><code>01/02 03:04:05PM &#39;06 -0700</code></td><td><code>09/06 11:45:05AM &#39;25 +0800</code></td><td>Go 的参考布局示例，展示格式规则</td><td>实际项目中较少直接使用</td></tr><tr><td><strong>ANSIC</strong></td><td><code>Mon Jan _2 15:04:05 2006</code></td><td><code>Sat Sep  6 11:45:05 2025</code></td><td>类 Unix 日志&#x2F;老式系统</td><td><code>_2</code> 表示空格填充日期</td></tr><tr><td><strong>UnixDate</strong></td><td><code>Mon Jan _2 15:04:05 MST 2006</code></td><td><code>Sat Sep  6 11:45:05 SGT 2025</code></td><td>类 Unix 日期（带时区缩写）</td><td><code>MST</code> 会替换为实际时区缩写，可能有歧义</td></tr><tr><td><strong>RubyDate</strong></td><td><code>Mon Jan 02 15:04:05 -0700 2006</code></td><td><code>Sat Sep 06 11:45:05 +0800 2025</code></td><td>Ruby 默认日期格式</td><td>使用数值时区，无歧义</td></tr><tr><td><strong>RFC822</strong></td><td><code>02 Jan 06 15:04 MST</code></td><td><code>06 Sep 25 11:45 SGT</code></td><td>老邮件标准</td><td>两位年份+时区缩写，解析不安全</td></tr><tr><td><strong>RFC822Z</strong></td><td><code>02 Jan 06 15:04 -0700</code></td><td><code>06 Sep 25 11:45 +0800</code></td><td>RFC822 数值时区版</td><td>优于 RFC822，推荐</td></tr><tr><td><strong>RFC850</strong></td><td><code>Monday, 02-Jan-06 15:04:05 MST</code></td><td><code>Saturday, 06-Sep-25 11:45:05 SGT</code></td><td>早期 HTTP&#x2F;邮件日期</td><td>已过时</td></tr><tr><td><strong>RFC1123</strong></td><td><code>Mon, 02 Jan 2006 15:04:05 MST</code></td><td><code>Sat, 06 Sep 2025 11:45:05 SGT</code></td><td>HTTP-date（旧版）</td><td>通常用 <code>GMT</code>，时区缩写有歧义</td></tr><tr><td><strong>RFC1123Z</strong></td><td><code>Mon, 02 Jan 2006 15:04:05 -0700</code></td><td><code>Sat, 06 Sep 2025 11:45:05 +0800</code></td><td>HTTP-date（推荐版）</td><td>更精确，兼容性好</td></tr><tr><td><strong>RFC3339</strong></td><td><code>2006-01-02T15:04:05Z07:00</code></td><td><code>2025-09-06T11:45:05+08:00</code></td><td>JSON &#x2F; API 常用</td><td>UTC 时输出 <code>Z</code>，否则 <code>+HH:MM</code></td></tr><tr><td><strong>RFC3339Nano</strong></td><td><code>2006-01-02T15:04:05.999999999Z07:00</code></td><td><code>2025-09-06T11:45:05.123456789+08:00</code></td><td>高精度 API 日志</td><td>输出到纳秒，0 的处理依版本不同</td></tr><tr><td><strong>Kitchen</strong></td><td><code>3:04PM</code></td><td><code>11:45AM</code></td><td>简洁 UI 时间显示</td><td>无秒、无日期</td></tr><tr><td><strong>Stamp</strong></td><td><code>Jan _2 15:04:05</code></td><td><code>Sep  6 11:45:05</code></td><td>简洁日志时间戳</td><td>无年、无时区</td></tr><tr><td><strong>StampMilli</strong></td><td><code>Jan _2 15:04:05.000</code></td><td><code>Sep  6 11:45:05.000</code></td><td>日志，带毫秒</td><td>固定 3 位小数</td></tr><tr><td><strong>StampMicro</strong></td><td><code>Jan _2 15:04:05.000000</code></td><td><code>Sep  6 11:45:05.000000</code></td><td>日志，带微秒</td><td>固定 6 位小数</td></tr><tr><td><strong>StampNano</strong></td><td><code>Jan _2 15:04:05.000000000</code></td><td><code>Sep  6 11:45:05.000000000</code></td><td>日志，带纳秒</td><td>固定 9 位小数</td></tr><tr><td><strong>DateTime</strong></td><td><code>2006-01-02 15:04:05</code></td><td><code>2025-09-06 11:45:05</code></td><td>数据库&#x2F;日志常用</td><td>无时区，解析时需额外设定</td></tr><tr><td><strong>DateOnly</strong></td><td><code>2006-01-02</code></td><td><code>2025-09-06</code></td><td>仅日期字段</td><td>常见于表单、数据库</td></tr><tr><td><strong>TimeOnly</strong></td><td><code>15:04:05</code></td><td><code>11:45:05</code></td><td>仅时间字段</td><td>无日期、无时区</td></tr></tbody></table><h2 id="格式化输出测试"><a href="#格式化输出测试" class="headerlink" title="格式化输出测试"></a>格式化输出测试</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestName</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">now := time.Now()</span><br><span class="line">layouts := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>&#123;</span><br><span class="line"><span class="string">&quot;Layout&quot;</span>:      <span class="string">&quot;01/02 03:04:05PM &#x27;06 -0700&quot;</span>,</span><br><span class="line"><span class="string">&quot;ANSIC&quot;</span>:       time.ANSIC,</span><br><span class="line"><span class="string">&quot;UnixDate&quot;</span>:    time.UnixDate,</span><br><span class="line"><span class="string">&quot;RubyDate&quot;</span>:    time.RubyDate,</span><br><span class="line"><span class="string">&quot;RFC822&quot;</span>:      time.RFC822,</span><br><span class="line"><span class="string">&quot;RFC822Z&quot;</span>:     time.RFC822Z,</span><br><span class="line"><span class="string">&quot;RFC850&quot;</span>:      time.RFC850,</span><br><span class="line"><span class="string">&quot;RFC1123&quot;</span>:     time.RFC1123,</span><br><span class="line"><span class="string">&quot;RFC1123Z&quot;</span>:    time.RFC1123Z,</span><br><span class="line"><span class="string">&quot;RFC3339&quot;</span>:     time.RFC3339,</span><br><span class="line"><span class="string">&quot;RFC3339Nano&quot;</span>: time.RFC3339Nano,</span><br><span class="line"><span class="string">&quot;Kitchen&quot;</span>:     time.Kitchen,</span><br><span class="line"><span class="string">&quot;Stamp&quot;</span>:       time.Stamp,</span><br><span class="line"><span class="string">&quot;StampMilli&quot;</span>:  time.StampMilli,</span><br><span class="line"><span class="string">&quot;StampMicro&quot;</span>:  time.StampMicro,</span><br><span class="line"><span class="string">&quot;StampNano&quot;</span>:   time.StampNano,</span><br><span class="line"><span class="string">&quot;DateTime&quot;</span>:    <span class="string">&quot;2006-01-02 15:04:05&quot;</span>,</span><br><span class="line"><span class="string">&quot;DateOnly&quot;</span>:    <span class="string">&quot;2006-01-02&quot;</span>,</span><br><span class="line"><span class="string">&quot;TimeOnly&quot;</span>:    <span class="string">&quot;15:04:05&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> name, layout := <span class="keyword">range</span> layouts &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;%-12s : %s\n&quot;</span>, name, now.Format(layout))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="string">&quot;\n-- UTC 格式化 --&quot;</span>)</span><br><span class="line"><span class="keyword">for</span> name, layout := <span class="keyword">range</span> layouts &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;%-12s : %s\n&quot;</span>, name, now.UTC().Format(layout))</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestName</span><br><span class="line">ANSIC        : Sat Sep  6 14:03:17 2025</span><br><span class="line">UnixDate     : Sat Sep  6 14:03:17 CST 2025</span><br><span class="line">RubyDate     : Sat Sep 06 14:03:17 +0800 2025</span><br><span class="line">RFC822Z      : 06 Sep 25 14:03 +0800</span><br><span class="line">RFC850       : Saturday, 06-Sep-25 14:03:17 CST</span><br><span class="line">RFC1123Z     : Sat, 06 Sep 2025 14:03:17 +0800</span><br><span class="line">RFC3339      : 2025-09-06T14:03:17+08:00</span><br><span class="line">RFC3339Nano  : 2025-09-06T14:03:17.9708592+08:00</span><br><span class="line">Kitchen      : 2:03PM</span><br><span class="line">Stamp        : Sep  6 14:03:17</span><br><span class="line">StampNano    : Sep  6 14:03:17.970859200</span><br><span class="line">DateTime     : 2025-09-06 14:03:17</span><br><span class="line">DateOnly     : 2025-09-06</span><br><span class="line">TimeOnly     : 14:03:17</span><br><span class="line">Layout       : 09/06 02:03:17PM &#x27;25 +0800</span><br><span class="line">RFC822       : 06 Sep 25 14:03 CST</span><br><span class="line">RFC1123      : Sat, 06 Sep 2025 14:03:17 CST</span><br><span class="line">StampMilli   : Sep  6 14:03:17.970</span><br><span class="line">StampMicro   : Sep  6 14:03:17.970859</span><br><span class="line"></span><br><span class="line">-- UTC 格式化 --</span><br><span class="line">UnixDate     : Sat Sep  6 06:03:17 UTC 2025</span><br><span class="line">RubyDate     : Sat Sep 06 06:03:17 +0000 2025</span><br><span class="line">RFC822Z      : 06 Sep 25 06:03 +0000</span><br><span class="line">RFC850       : Saturday, 06-Sep-25 06:03:17 UTC</span><br><span class="line">RFC1123Z     : Sat, 06 Sep 2025 06:03:17 +0000</span><br><span class="line">RFC3339      : 2025-09-06T06:03:17Z</span><br><span class="line">RFC3339Nano  : 2025-09-06T06:03:17.9708592Z</span><br><span class="line">Kitchen      : 6:03AM</span><br><span class="line">Stamp        : Sep  6 06:03:17</span><br><span class="line">StampNano    : Sep  6 06:03:17.970859200</span><br><span class="line">DateTime     : 2025-09-06 06:03:17</span><br><span class="line">DateOnly     : 2025-09-06</span><br><span class="line">TimeOnly     : 06:03:17</span><br><span class="line">Layout       : 09/06 06:03:17AM &#x27;25 +0000</span><br><span class="line">RFC822       : 06 Sep 25 06:03 UTC</span><br><span class="line">RFC1123      : Sat, 06 Sep 2025 06:03:17 UTC</span><br><span class="line">StampMilli   : Sep  6 06:03:17.970</span><br><span class="line">StampMicro   : Sep  6 06:03:17.970859</span><br><span class="line">ANSIC        : Sat Sep  6 06:03:17 2025</span><br><span class="line">--- PASS: TestName (0.01s)</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Go%E5%86%85%E7%BD%AE%E6%97%B6%E9%97%B4%E5%B8%B8%E9%87%8F%E6%A0%BC%E5%BC%8F%EF%BC%88time%E5%8C%85%EF%BC%89%E5%AF%B9%E7%85%A7%E8%A1%A8/</id>
    <link href="https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Go%E5%86%85%E7%BD%AE%E6%97%B6%E9%97%B4%E5%B8%B8%E9%87%8F%E6%A0%BC%E5%BC%8F%EF%BC%88time%E5%8C%85%EF%BC%89%E5%AF%B9%E7%85%A7%E8%A1%A8/"/>
    <published>2025-09-06T05:43:56.000Z</published>
    <summary>
      <![CDATA[<h2 id="Go-时间布局格式"><a href="#Go-时间布局格式" class="headerlink" title="Go 时间布局格式"></a>Go 时间布局格式</h2><p>Go 使用 <strong>参考时间</strong> <code>Mon Jan]]>
    </summary>
    <title>Go内置时间常量格式（time包）对照表</title>
    <updated>2025-09-06T05:43:56.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="学习笔记" scheme="https://cooooing.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="linux" scheme="https://cooooing.github.io/tags/linux/"/>
    <content>
      <![CDATA[<h2 id="top-命令"><a href="#top-命令" class="headerlink" title="top 命令"></a>top 命令</h2><p>top 命令是 Linux 常用的实时系统监控工具。它默认每隔 3 秒刷新一次。</p><h3 id="启动参数（命令行选项）"><a href="#启动参数（命令行选项）" class="headerlink" title="启动参数（命令行选项）"></a>启动参数（命令行选项）</h3><table><thead><tr><th>参数</th><th>全称</th><th>作用</th></tr></thead><tbody><tr><td><code>-d seconds</code></td><td><strong>delay-time</strong></td><td>刷新间隔（默认 3 秒），例如 <code>top -d 1</code> 每 1 秒刷新一次</td></tr><tr><td><code>-n count</code></td><td><strong>number-of-iterations</strong></td><td>运行多少次后退出，例如 <code>top -n 5</code> 显示 5 次后退出</td></tr><tr><td><code>-p pid</code></td><td><strong>process-id</strong></td><td>仅监控指定进程，例如 <code>top -p 1234</code></td></tr><tr><td><code>-u user</code></td><td><strong>user-name</strong></td><td>仅显示指定用户的进程，例如 <code>top -u root</code></td></tr><tr><td><code>-U user</code></td><td><strong>UID-based</strong></td><td>类似 <code>-u</code>，但基于 UID</td></tr><tr><td><code>-b</code></td><td><strong>batch-mode</strong></td><td>批处理模式（无交互，常用于重定向到文件：<code>top -b -n 1 &gt; out.txt</code>）</td></tr><tr><td><code>-H</code></td><td><strong>threads-mode</strong></td><td>显示线程而非进程</td></tr><tr><td><code>-i</code></td><td><strong>idle-process toggle</strong></td><td>启动时忽略空闲任务（&#x3D; 运行时按 <code>i</code>）</td></tr><tr><td><code>-c</code></td><td><strong>command-line toggle</strong></td><td>显示完整命令行（&#x3D; 运行时按 <code>c</code>）</td></tr></tbody></table><h3 id="交互式快捷键（运行时输入）"><a href="#交互式快捷键（运行时输入）" class="headerlink" title="交互式快捷键（运行时输入）"></a>交互式快捷键（运行时输入）</h3><h4 id="显示控制"><a href="#显示控制" class="headerlink" title="显示控制"></a>显示控制</h4><table><thead><tr><th>按键</th><th>全称</th><th>作用</th></tr></thead><tbody><tr><td><code>h</code></td><td><strong>help</strong></td><td>显示帮助（所有快捷键说明）</td></tr><tr><td><code>q</code></td><td><strong>quit</strong></td><td>退出 <code>top</code></td></tr><tr><td><code>z</code></td><td><strong>color&#x2F;mono toggle</strong></td><td>切换彩色&#x2F;单色模式</td></tr><tr><td><code>B</code></td><td><strong>bold toggle</strong></td><td>是否加粗高亮</td></tr><tr><td><code>l</code></td><td><strong>load-average toggle</strong></td><td>显示&#x2F;隐藏顶部的负载信息</td></tr><tr><td><code>t</code></td><td><strong>tasks toggle</strong></td><td>显示&#x2F;隐藏任务和 CPU 使用情况</td></tr><tr><td><code>m</code></td><td><strong>memory toggle</strong></td><td>显示&#x2F;隐藏内存&#x2F;Swap 行</td></tr><tr><td><code>1</code></td><td><strong>cpu-per-core</strong></td><td>展开&#x2F;收起所有 CPU 核心的使用率</td></tr></tbody></table><hr><h4 id="排序与过滤"><a href="#排序与过滤" class="headerlink" title="排序与过滤"></a>排序与过滤</h4><table><thead><tr><th>按键</th><th>全称</th><th>作用</th></tr></thead><tbody><tr><td><code>P</code></td><td><strong>sort by CPU</strong></td><td>按 CPU 使用率排序（默认）</td></tr><tr><td><code>M</code></td><td><strong>sort by Memory</strong></td><td>按内存使用率排序</td></tr><tr><td><code>T</code></td><td><strong>sort by Time</strong></td><td>按运行时间排序</td></tr><tr><td><code>N</code></td><td><strong>sort by PID</strong></td><td>按 PID 排序</td></tr><tr><td><code>R</code></td><td><strong>reverse sort</strong></td><td>反向排序</td></tr><tr><td><code>O</code>（大写）</td><td><strong>change sort field</strong></td><td>按其他字段排序（进入交互菜单选择）</td></tr><tr><td><code>o</code>（小写）</td><td><strong>filter by field</strong></td><td>过滤显示（例如 <code>COMMAND=nginx</code>）</td></tr><tr><td><code>u</code></td><td><strong>filter by user</strong></td><td>只显示某个用户的进程</td></tr></tbody></table><hr><h4 id="进程控制"><a href="#进程控制" class="headerlink" title="进程控制"></a>进程控制</h4><table><thead><tr><th>按键</th><th>全称</th><th>作用</th></tr></thead><tbody><tr><td><code>k</code></td><td><strong>kill</strong></td><td>杀死进程（输入 PID 和信号，默认 15 <code>SIGTERM</code>）</td></tr><tr><td><code>r</code></td><td><strong>renice</strong></td><td>调整进程 Nice 值（输入 PID 和新 nice 值）</td></tr><tr><td><code>s</code></td><td><strong>set-delay</strong></td><td>修改刷新间隔（秒）</td></tr><tr><td><code>W</code></td><td><strong>write config</strong></td><td>保存当前配置，下次启动生效（写入 <code>~/.toprc</code>）</td></tr></tbody></table><hr><h4 id="进程显示切换"><a href="#进程显示切换" class="headerlink" title="进程显示切换"></a>进程显示切换</h4><table><thead><tr><th>按键</th><th>全称</th><th>作用</th></tr></thead><tbody><tr><td><code>c</code></td><td><strong>command-line toggle</strong></td><td>显示完整命令行 vs 仅命令名</td></tr><tr><td><code>i</code></td><td><strong>idle toggle</strong></td><td>显示&#x2F;隐藏空闲进程</td></tr><tr><td><code>H</code></td><td><strong>threads toggle</strong></td><td>显示线程而不是进程</td></tr><tr><td><code>x</code></td><td><strong>highlight sort column</strong></td><td>高亮当前排序列</td></tr><tr><td><code>y</code></td><td><strong>highlight running tasks</strong></td><td>高亮正在运行的任务</td></tr><tr><td><code>u</code></td><td><strong>user filter</strong></td><td>只显示指定用户的进程</td></tr><tr><td><code>V</code></td><td><strong>forest view</strong></td><td>树状显示进程（类似 <code>pstree</code>）</td></tr></tbody></table><h2 id="显示信息"><a href="#显示信息" class="headerlink" title="显示信息"></a>显示信息</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">top - 13:24:16 up 3 days, 22:41,  0 user,  load average: 0.14, 0.14, 0.11</span><br><span class="line">Tasks: 163 total,   1 running, 162 sleeping,   0 stopped,   0 zombie</span><br><span class="line">%Cpu(s):  1.3 us,  0.8 sy,  0.0 ni, 97.1 id,  0.7 wa,  0.0 hi,  0.1 si,  0.0 st </span><br><span class="line">MiB Mem :   7940.7 total,    222.3 free,   6399.8 used,   1590.8 buff/cache     </span><br><span class="line">MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1540.9 avail Mem </span><br><span class="line"></span><br><span class="line">  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                    </span><br><span class="line"> 1197 root      20   0 1963420 640468  36736 S   4.0   7.9 282:35.87 kube-apiserver                                                                             </span><br><span class="line"> 2327 root      20   0 2237824  49708  24064 S   1.3   0.6  80:30.91 calico-node                                                                                </span><br><span class="line">  771 root       0 -20   11.3g  89348  23552 S   1.0   1.1  79:04.21 etcd                                                                                       </span><br><span class="line"> 1228 root      20   0 1412044  84508  21376 S   1.0   1.0  78:41.97 kube-controller                                                                            </span><br><span class="line">32616 root      20   0 2227064  92080  50560 S   1.0   1.1  79:00.74 kubelet                                                                                    </span><br><span class="line"> 1212 root      20   0 1283348  32972  13056 S   0.3   0.4  15:25.14 kube-scheduler                                                                             </span><br><span class="line"> 1648 root      20   0  746604  16832   6400 S   0.3   0.2   9:40.87 node-cache                                                                                 </span><br><span class="line">15415 root      20   0       0      0      0 I   0.3   0.0   0:00.28 kworker/1:1-events                                                                         </span><br><span class="line">31742 root      20   0 2398412  60844  29696 S   0.3   0.7  26:17.82 containerd                                                                                 </span><br><span class="line">43556 ubuntu    20   0   13228   6528   4352 R   0.3   0.1   0:00.18 top                                                                                        </span><br><span class="line">    1 root      20   0   23084  12288   7680 S   0.0   0.2   1:12.93 systemd                                                                                    </span><br><span class="line">    2 root      20   0       0      0      0 S   0.0   0.0   0:00.05 kthreadd                                                                                   </span><br><span class="line">    3 root      20   0       0      0      0 S   0.0   0.0   0:00.00 pool_workqueue_release                                                                     </span><br><span class="line">    4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-rcu_g                                                                            </span><br><span class="line">    5 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-rcu_p                                                                            </span><br><span class="line">    6 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-slub_                                                                            </span><br><span class="line">    7 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-netns                                                                            </span><br><span class="line">    9 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/0:0H-events_highpri                                                                </span><br><span class="line">   12 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-mm_pe                                                                            </span><br><span class="line">   13 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_kthread                                                                          </span><br><span class="line">   14 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_rude_kthread                                                                     </span><br><span class="line">   15 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_trace_kthread                                                                    </span><br><span class="line">   16 root      20   0       0      0      0 S   0.0   0.0   0:13.07 ksoftirqd/0                                                                                </span><br><span class="line">   17 root      20   0       0      0      0 I   0.0   0.0   1:02.67 rcu_preempt                                                                                </span><br><span class="line">   18 root      rt   0       0      0      0 S   0.0   0.0   0:02.89 migration/0                                                                                </span><br><span class="line">   19 root     -51   0       0      0      0 S   0.0   0.0   0:00.00 idle_inject/0                                                                              </span><br><span class="line">   20 root      20   0       0      0      0 S   0.0   0.0   0:00.00 cpuhp/0                                                                                    </span><br><span class="line">   21 root      20   0       0      0      0 S   0.0   0.0   0:00.00 cpuhp/2                                                                                    </span><br><span class="line">   22 root     -51   0       0      0      0 S   0.0   0.0   0:00.00 idle_inject/2                                                                              </span><br><span class="line">   23 root      rt   0       0      0      0 S   0.0   0.0   0:02.40 migration/2     </span><br></pre></td></tr></table></figure><h3 id="顶部状态"><a href="#顶部状态" class="headerlink" title="顶部状态"></a>顶部状态</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">top - 13:24:16 up 3 days, 22:41,  0 user,  load average: 0.14, 0.14, 0.11</span><br></pre></td></tr></table></figure><ul><li><strong>13:24:16</strong> → 当前系统时间</li><li><strong>up 3 days, 22:41</strong> → 系统已运行 3 天 22 小时 41 分钟</li><li><strong>0 user</strong> → 当前登录用户数（这里没有用户直接登录）</li><li><strong>load average: 0.14, 0.14, 0.11</strong> 系统 1 分钟、5 分钟、15 分钟的平均负载。数字越接近 CPU 核心数，说明负载越合理。比如 4 核 CPU，load average 小于 4 基本健康。</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Tasks: 163 total,   1 running, 162 sleeping,   0 stopped,   0 zombie</span><br></pre></td></tr></table></figure><ul><li><strong>163 total</strong> → 系统中共有 163 个进程</li><li><strong>1 running</strong> → 正在运行的进程 1 个</li><li><strong>162 sleeping</strong> → 休眠状态的进程 162 个（大部分进程常处于此状态，等待事件触发）</li><li><strong>0 stopped</strong> → 停止的进程（通过信号暂停）</li><li><strong>0 zombie</strong> → 僵尸进程（子进程结束但父进程未回收资源，数量多时是问题信号）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%Cpu(s):  1.3 us,  0.8 sy,  0.0 ni, 97.1 id,  0.7 wa,  0.0 hi,  0.1 si,  0.0 st</span><br></pre></td></tr></table></figure><ul><li><strong>1.3 us (user space)</strong> → 用户态进程占用 CPU 1.3%</li><li><strong>0.8 sy (system)</strong> → 内核态占用 CPU 0.8%</li><li><strong>0.0 ni (nice)</strong> → 调整过 nice 优先级的进程占用 0%</li><li><strong>97.1 id (idle)</strong> → CPU 空闲 97.1%</li><li><strong>0.7 wa (I&#x2F;O wait)</strong> → 等待 I&#x2F;O 的 CPU 时间（磁盘&#x2F;网络）</li><li><strong>0.0 hi (hardware interrupt)</strong> → 硬件中断占用 CPU 时间百分比</li><li><strong>0.1 si (software interrupt)</strong> → 软件中断占用 CPU 时间百分比</li><li><strong>0.0 st (steal time)</strong> → 被虚拟机管理程序（Hypervisor）“偷走”的 CPU 时间百分比（虚拟化场景有意义）</li></ul><blockquote><p>一般关注 us+sy（实际负载），如果 wa 很高，说明 I&#x2F;O 瓶颈。</p></blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MiB Mem :   7940.7 total,    222.3 free,   6399.8 used,   1590.8 buff/cache</span><br></pre></td></tr></table></figure><ul><li><strong>7940.7 total</strong> → 总内存 7.9 GB</li><li><strong>222.3 free</strong> → 空闲内存 222 MB（未使用部分，Linux 下通常较小）</li><li><strong>6399.8 used</strong> → 被程序使用的内存 6.4 GB（包含应用+缓存）</li><li><strong>1590.8 buff&#x2F;cache</strong> → 缓冲和缓存内存（用于磁盘缓存和文件缓存，可回收）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1540.9 avail Mem</span><br></pre></td></tr></table></figure><ul><li><strong>total 0.0</strong> → 没有配置 swap 分区</li><li><strong>free 0.0</strong> → 空闲 Swap</li><li><strong>used 0.0</strong> → 已用 Swap</li><li><strong>avail Mem 1540.9</strong> → 实际可用内存 1.5 GB（考虑缓存和可回收内存），比 free 更准确</li></ul><blockquote><p>如果 Swap used 很高，说明物理内存不足。</p></blockquote><h3 id="进程列表"><a href="#进程列表" class="headerlink" title="进程列表"></a>进程列表</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND </span><br></pre></td></tr></table></figure><ul><li><strong>PID (Process ID)</strong> → 进程 ID（唯一标识）</li><li><strong>USER</strong> → 进程所有者（用户名）</li><li><strong>PR (Priority)</strong> → （内核调度的优先级，数值越小优先级越高）</li><li><strong>NI (Nice)</strong> → nice 值（用户可调节，-20 ~ 19，影响优先级）</li><li><strong>VIRT (Virtual Memory)</strong> → 进程使用的虚拟内存 (KB)，进程申请的总地址空间，包括共享库、映射文件等</li><li><strong>RES (Resident Memory)</strong> → 进程常驻物理内存 (KB)，实际占用的物理内存</li><li><strong>SHR (Shared Memory)</strong> → 共享内存 (KB)，与其他进程共享的内存</li><li><strong>S (State)</strong> → 进程状态<ul><li><code>R</code> → running</li><li><code>S</code> → sleeping</li><li><code>I</code> → idle</li><li><code>D</code> → 不可中断睡眠</li><li><code>Z</code> → 僵尸</li></ul></li><li><strong>%CPU (CPU usage)</strong> → CPU 使用率（按刷新周期计算）</li><li><strong>%MEM (Memory usage)</strong> → 内存使用率（占总内存百分比）</li><li><strong>TIME+ (CPU Time)</strong> → 进程累计使用的 CPU 时间，格式 <em>分钟:秒.百分秒</em></li><li><strong>COMMAND</strong> → 进程的命令或程序名（默认显示简写，可按 <code>c</code> 显示完整命令行）</li></ul><h2 id="一些指标的概念"><a href="#一些指标的概念" class="headerlink" title="一些指标的概念"></a>一些指标的概念</h2><h3 id="Nice-值-Niceness-Value"><a href="#Nice-值-Niceness-Value" class="headerlink" title="Nice 值 (Niceness Value)"></a>Nice 值 (Niceness Value)</h3><p>调节进程的<strong>优先级 (Priority, PR)</strong>，影响 CPU 调度的先后顺序。</p><ul><li><p><strong>取值范围</strong>：<code>-20 ~ 19</code></p><ul><li><strong>-20</strong> → 最高优先级（最“自私”，会多抢占 CPU）</li><li><strong>0</strong> → 默认值</li><li><strong>19</strong> → 最低优先级（最“友好”，让出 CPU 机会）</li></ul></li><li><p><strong>关系</strong>：</p><ul><li>最终调度优先级由 <strong>PR &#x3D; base_priority + NI</strong> 决定</li><li><code>top</code> 里 <strong>PR</strong> 越小，进程调度优先级越高</li></ul></li><li><p><strong>修改方式</strong>：</p><ul><li>启动时指定：<code>nice -n 10 my_program</code></li><li>运行时调整：<code>renice -n -5 -p 1234</code> （调整 PID&#x3D;1234 的进程）</li></ul></li><li><p>大型数据分析进程可以 <code>nice=10</code> → 不阻塞关键服务</p></li><li><p>系统关键进程可能会设置低 nice 值（-5 ~ -20），保证调度优先级</p></li></ul><hr><h3 id="交换空间-Swap-Space"><a href="#交换空间-Swap-Space" class="headerlink" title="交换空间 (Swap Space)"></a>交换空间 (Swap Space)</h3><p>当物理内存不足时，Linux 会把部分<strong>不常用的内存页</strong>写到磁盘上的 Swap 区域，以释放 RAM。<br>可以让系统避免“内存不足直接崩溃”。但磁盘速度远低于内存，频繁使用 Swap 会导致性能下降（俗称“抖动 &#x2F; thrashing”）。</p><ul><li>在 <code>top</code> 的 <strong>Swap used</strong> 升高时，说明内存吃紧</li><li>进程会明显变慢</li></ul><p><strong>Kubernetes 中默认不允许使用交换内存</strong></p><p>Kubernetes 的调度和资源控制主要依赖于 <strong>cgroup 的 CPU、内存限制</strong>。</p><ul><li><strong>内存限制</strong>：Pod 被限制多少内存，超过就会触发 <strong>OOMKill</strong>。</li><li>如果允许 swap，那么：<ul><li>Pod 占用的物理内存可能被换出到 swap，而 kubelet 和 cgroup 并不知道。</li><li>容器可能“假装”没超内存，实际上性能已经变得非常差。</li><li>这会导致 <strong>资源隔离失真</strong>，调度器也无法准确分配资源。</li></ul></li></ul><p>因此，Kubernetes 默认要求节点运行在 <strong>no swap</strong> 的状态下，以保证资源控制的可预测性。</p><p>但 Kubernetes 在后续版本有支持开启交换内存的功能：</p><p><a class="link"   href="https://kubernetes.io/blog/2021/08/09/run-nodes-with-swap-alpha/" >New in Kubernetes v1.22: alpha support for using swap memory<i class="fas fa-external-link-alt"></i></a><br><a class="link"   href="https://kubernetes.io/zh-cn/blog/2023/08/24/swap-linux-beta/" >Kubernetes 1.28: Beta support for using swap on Linux<i class="fas fa-external-link-alt"></i></a></p><h3 id="虚拟内存-Virtual-Memory-VIRT"><a href="#虚拟内存-Virtual-Memory-VIRT" class="headerlink" title="虚拟内存 (Virtual Memory, VIRT)"></a>虚拟内存 (Virtual Memory, VIRT)</h3><p>每个进程拥有<strong>独立的地址空间</strong>（虚拟内存），由操作系统内核管理并映射到实际物理内存或磁盘。</p><p>包含内容：</p><ol><li>程序代码</li><li>已分配但未实际使用的内存</li><li>动态库</li><li>内存映射文件（mmap）</li><li>可能被换出到 Swap 的部分</li></ol><p>指标含义：</p><ul><li><code>top</code> 里的 <strong>VIRT</strong> → 进程可寻址的虚拟内存总量（不等于真实占用 RAM）</li><li><code>RES</code> → 真实常驻物理内存</li><li><code>SHR</code> → 共享内存</li></ul><p>举例：一个 Python 程序加载 TensorFlow，<code>VIRT</code> 可能几十 GB，但 <code>RES</code> 可能只有几百 MB，因为很多库只是映射，并没有真正加载到内存。</p><h3 id="中断-Interrupt"><a href="#中断-Interrupt" class="headerlink" title="中断 (Interrupt)"></a>中断 (Interrupt)</h3><p>中断是 CPU 在运行用户代码时，被“打断”去处理某个事件的机制。分为<strong>硬件中断</strong>和<strong>软件中断</strong>。</p><h4 id="硬件中断-Hardware-Interrupt-hi"><a href="#硬件中断-Hardware-Interrupt-hi" class="headerlink" title="硬件中断 (Hardware Interrupt, hi)"></a>硬件中断 (Hardware Interrupt, hi)</h4><p>来源：外部硬件设备向 CPU 发出信号<br>例子：</p><ul><li>键盘输入（按下一个键）</li><li>网络接口卡收到数据包</li><li>磁盘 I&#x2F;O 完成通知</li></ul><p>类比：你正在写字（运行程序），突然电话响了（键盘输入），你必须停下去接电话（处理硬件中断）。</p><h4 id="软件中断-Software-Interrupt-si"><a href="#软件中断-Software-Interrupt-si" class="headerlink" title="软件中断 (Software Interrupt, si)"></a>软件中断 (Software Interrupt, si)</h4><p>来源：内核为处理高频硬件事件，把一些工作转交给“软中断”机制（通常由内核线程完成）。<br>例子：</p><ul><li>网络包的协议栈处理（TCP&#x2F;IP 解析）</li><li>大量磁盘 IO 的后续处理</li></ul><p>类比：电话太多了，你雇了秘书（内核线程）来代接一部分，这就是软件中断。</p>]]>
    </content>
    <id>https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/top%E5%91%BD%E4%BB%A4/</id>
    <link href="https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/top%E5%91%BD%E4%BB%A4/"/>
    <published>2025-09-03T02:01:14.000Z</published>
    <summary>
      <![CDATA[<h2 id="top-命令"><a href="#top-命令" class="headerlink" title="top 命令"></a>top 命令</h2><p>top 命令是 Linux 常用的实时系统监控工具。它默认每隔 3 秒刷新一次。</p>
<h3 id="启]]>
    </summary>
    <title>top命令</title>
    <updated>2025-09-03T02:01:14.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="学习笔记" scheme="https://cooooing.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="并发" scheme="https://cooooing.github.io/tags/%E5%B9%B6%E5%8F%91/"/>
    <category term="锁" scheme="https://cooooing.github.io/tags/%E9%94%81/"/>
    <content>
      <![CDATA[<p>在并发编程中，锁是用于同步线程、避免数据竞争和确保线程安全的重要机制。</p><h2 id="互斥锁（Mutex）"><a href="#互斥锁（Mutex）" class="headerlink" title="互斥锁（Mutex）"></a>互斥锁（Mutex）</h2><p>互斥锁是最基本的锁类型，确保同一时间只有一个线程可以访问共享资源。</p><ul><li>线程获取锁后，其他尝试获取锁的线程会被阻塞，直到锁被释放。</li><li>通常用于保护临界区（Critical Section）。</li><li>实现简单，但可能导致线程阻塞和上下文切换开销。</li></ul><blockquote><p>用于需要确保独占访问共享资源的场景。</p></blockquote><h2 id="读写锁（Read-Write-Lock）"><a href="#读写锁（Read-Write-Lock）" class="headerlink" title="读写锁（Read-Write Lock）"></a>读写锁（Read-Write Lock）</h2><p>允许多个线程同时读取共享资源，但写操作需独占访问。</p><ul><li><strong>读锁</strong>：多个线程可同时持有读锁（共享锁）。</li><li><strong>写锁</strong>：写锁是独占的，写时不允许其他读或写操作。</li><li>提高并发性能，尤其在读多写少的场景。</li></ul><p>是互斥锁的一种扩展，区分读写操作以提升性能。</p><blockquote><p>用于读操作频繁、写操作较少的场景，如数据库缓存。</p></blockquote><h2 id="条件锁（Condition-Lock）"><a href="#条件锁（Condition-Lock）" class="headerlink" title="条件锁（Condition Lock）"></a>条件锁（Condition Lock）</h2><p>结合条件变量，用于线程间的协作，线程在特定条件满足时才继续执行。</p><ul><li>通常与互斥锁配合使用，线程等待条件满足时进入休眠，条件满足时被唤醒。</li><li>提高效率，避免忙等待。</li></ul><p>条件锁依赖互斥锁，互斥锁保护共享条件变量。</p><blockquote><p>用于生产者-消费者模型、线程同步等待。</p></blockquote><h2 id="信号量（Semaphore）"><a href="#信号量（Semaphore）" class="headerlink" title="信号量（Semaphore）"></a>信号量（Semaphore）</h2><p>一种计数器机制，控制多个线程对有限资源的访问。</p><ul><li>允许指定数量的线程同时访问资源（计数&gt;1）。</li><li>当计数为1时，行为类似于互斥锁。</li></ul><p>信号量是互斥锁的泛化，支持多线程并发访问。互斥锁限制为单一线程访问。</p><blockquote><p>用于限制并发访问数量，如连接池管理。</p></blockquote><h2 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h2><p>在分布式系统中，用于协调多个进程或节点对共享资源的访问。</p><ul><li>跨机器实现，通常基于外部存储（如Redis、ZooKeeper、Etcd）。</li><li>需考虑网络延迟、节点故障等复杂情况。</li></ul><p>分布式锁是互斥锁的分布式扩展，适用于跨进程或跨机器场景。本地锁（如Mutex）仅限于单机多线程。</p><blockquote><p>用于分布式系统中协调跨进程或跨节点的资源访问，如分布式任务调度。</p></blockquote><h2 id="悲观锁和乐观锁（加锁策略）"><a href="#悲观锁和乐观锁（加锁策略）" class="headerlink" title="悲观锁和乐观锁（加锁策略）"></a>悲观锁和乐观锁（加锁策略）</h2><h3 id="悲观锁（Pessimistic-Lock）"><a href="#悲观锁（Pessimistic-Lock）" class="headerlink" title="悲观锁（Pessimistic Lock）"></a>悲观锁（Pessimistic Lock）</h3><p>悲观锁假设并发操作中冲突（数据竞争）发生的概率较高，因此<strong>在访问共享资源之前，总是先获取锁</strong>，确保独占访问。其他线程在锁被释放前会被阻塞。</p><blockquote><p>适用于写操作频繁、冲突概率高、数据一致性要求严格的场景。<br>如互斥锁、读写锁、数据库锁（行锁、表锁）。</p></blockquote><h3 id="乐观锁（Optimistic-Lock）"><a href="#乐观锁（Optimistic-Lock）" class="headerlink" title="乐观锁（Optimistic Lock）"></a>乐观锁（Optimistic Lock）</h3><p>乐观锁假设并发操作中冲突发生的概率较低，<strong>允许线程先执行操作，在提交时检查数据是否被修改</strong>。<br>如果未被修改，则提交成功；否则，回滚并重试。<strong>乐观锁通常不使用传统锁机制，而是依赖版本控制或原子操作</strong>。</p><p>乐观锁是非阻塞的，线程直接操作共享资源，而无需等待锁。<br>通过版本号、时间戳或CAS（Compare-And-Swap）检查数据是否被修改。</p><blockquote><p>适用于读多场景，高并发低冲突。<br>如缓存更新、计数器</p></blockquote><h2 id="阻塞锁和非阻塞锁（等待机制）"><a href="#阻塞锁和非阻塞锁（等待机制）" class="headerlink" title="阻塞锁和非阻塞锁（等待机制）"></a>阻塞锁和非阻塞锁（等待机制）</h2><h3 id="阻塞锁（Blocking-Lock）"><a href="#阻塞锁（Blocking-Lock）" class="headerlink" title="阻塞锁（Blocking Lock）"></a>阻塞锁（Blocking Lock）</h3><p>阻塞锁是指当线程尝试获取锁时，如果锁已被其他线程占用，当前线程会进入阻塞状态（挂起），等待锁释放。<br>阻塞状态通常由操作系统管理，线程被放入等待队列，暂停执行，直到被唤醒。</p><p><strong>锁不可用时，线程被挂起，释放CPU资源。</strong><br>但通常涉及线程的上下文切换（从用户态到内核态），开销较高。<br>线程唤醒和重新调度可能引入延迟。</p><blockquote><p>适用于需要强一致性、长时间持有锁或高冲突场景。<br>互斥锁、读写锁、条件锁都属于阻塞锁。</p></blockquote><h3 id="非阻塞锁（Non-Blocking-Lock）"><a href="#非阻塞锁（Non-Blocking-Lock）" class="headerlink" title="非阻塞锁（Non-Blocking Lock）"></a>非阻塞锁（Non-Blocking Lock）</h3><p>非阻塞锁是指当线程尝试获取锁时，如果锁不可用，线程不会进入阻塞状态，而是立即返回（失败）或通过忙等待（busy-waiting）继续尝试。<br>非阻塞锁通常基于原子操作实现，尽量避免操作系统介入。</p><p><strong>锁不可用时，线程要么立即返回，要么短暂自旋（循环尝试）。</strong><br>基于原子操作（如CAS、Test-and-Set），通常在用户态完成。<br>自旋可能浪费CPU资源，但避免上下文切换。</p><blockquote><p>适用于需要高吞吐量、短锁持有时间、冲突概率较低的场景。<br>自旋锁、乐观锁属于非阻塞锁。TryLock机制用于快速失败（尝试获取锁，失败则立即返回）。</p></blockquote><h2 id="可重入锁和非可重入锁（重入性）"><a href="#可重入锁和非可重入锁（重入性）" class="headerlink" title="可重入锁和非可重入锁（重入性）"></a>可重入锁和非可重入锁（重入性）</h2><p><strong>基于锁是否允许同一线程多次获取的特性进行分类。</strong><br>它们在实现线程安全和避免死锁方面有显著差异。</p><h3 id="可重入锁（Reentrant-Lock）"><a href="#可重入锁（Reentrant-Lock）" class="headerlink" title="可重入锁（Reentrant Lock）"></a>可重入锁（Reentrant Lock）</h3><p>可重入锁允许同一线程多次获取同一把锁而不会导致死锁。每次获取锁时，锁内部会记录重入次数，线程必须释放相同次数的锁才能完全解锁。</p><p>同一线程可多次调用Lock()，每次增加锁的计数器。<br>释放时需调用Unlock()与Lock()次数相同，计数器减为0时锁被释放。</p><blockquote><p>适用于复杂逻辑中，同一线程多次进入临界区（递归函数或嵌套调用）的场景。</p></blockquote><h3 id="非可重入锁（Non-Reentrant-Lock）"><a href="#非可重入锁（Non-Reentrant-Lock）" class="headerlink" title="非可重入锁（Non-Reentrant Lock）"></a>非可重入锁（Non-Reentrant Lock）</h3><p>非可重入锁不允许同一线程多次获取同一把锁。如果线程尝试重复加锁，会导致死锁或异常。</p><blockquote><p>适用于简单互斥场景。</p></blockquote><h2 id="公平锁和非公平锁（分配策略）"><a href="#公平锁和非公平锁（分配策略）" class="headerlink" title="公平锁和非公平锁（分配策略）"></a>公平锁和非公平锁（分配策略）</h2><p><strong>根据锁的分配策略分类。</strong><br>区别在于当多个线程（或goroutine）竞争锁时，锁是否按照线程请求的顺序（通常是先到先得）分配。</p><h3 id="公平锁（Fair-Lock）"><a href="#公平锁（Fair-Lock）" class="headerlink" title="公平锁（Fair Lock）"></a>公平锁（Fair Lock）</h3><p>公平锁确保线程按照请求锁的顺序获取锁，通常采用先到先得（FIFO，First-In-First-Out）策略。<br>当锁释放时，等待队列中最先请求的线程优先获得锁。</p><p>线程按请求顺序获取锁，避免线程饥饿（某些线程长期无法获取锁）。<br>维护了一个队列，用于记录线程的请求顺序。在锁被释放时，唤醒队列头部的线程。</p><blockquote><p>适用于需要严格公平性、避免线程饥饿的场景。<br>需要维护队列，性能相对较低。</p></blockquote><h3 id="非公平锁（Non-Fair-Lock）"><a href="#非公平锁（Non-Fair-Lock）" class="headerlink" title="非公平锁（Non-Fair Lock）"></a>非公平锁（Non-Fair Lock）</h3><p><strong>非公平锁不保证线程按请求顺序获取锁。</strong><br>当锁释放时，等待线程和新请求线程竞争锁，操作系统或运行时决定哪个线程获得锁，可能导致后请求的线程优先获取。</p><p>不保证FIFO，可能导致线程饥饿（某些线程长期无法获取锁）。</p><blockquote><p>适用于追求高性能、允许一定程度不公平的场景。</p></blockquote><h2 id="锁会引发的问题"><a href="#锁会引发的问题" class="headerlink" title="锁会引发的问题"></a>锁会引发的问题</h2><h3 id="死锁（deadlock）"><a href="#死锁（deadlock）" class="headerlink" title="死锁（deadlock）"></a>死锁（deadlock）</h3><p>多个线程互相持有对方需要的锁，导致所有线程无法继续执行。</p><p>死锁产生的四个必要条件（梦回操作系统课）：</p><ul><li>互斥条件：一个资源每次只能被一个进程使用。</li><li>请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。</li><li>不剥夺条件：进程已获得的资源，在没使用完之前，不能强行剥夺。</li><li>循环等待条件：多个进程之间形成一种互相循环等待资源的关系。</li></ul><p>发生场景：</p><ul><li>多个锁的嵌套获取，且顺序不一致。</li><li>非可重入锁（如sync.Mutex）在同一线程重复加锁。</li></ul><p>解决方法（破坏四个必要条件）：</p><ul><li>统一线程的请求锁的顺序，以保证线程能获取到所需的全部资源而不阻塞，进而保证不发生死锁。</li><li>避免线程持有锁并等待锁。在请求锁时，如果锁被其他线程占用，不等待，而是将之前持有的锁释放，保证其他线程顺利执行。</li></ul><h3 id="活锁（Livelock）"><a href="#活锁（Livelock）" class="headerlink" title="活锁（Livelock）"></a>活锁（Livelock）</h3><p>线程不断尝试获取锁但无法成功，处于活跃但无进展的状态。<br>活锁其实是避免死锁的一种方式——避免线程持续尝试获取锁，而是通过等待或随机退避来避免。</p><p>举个例子：<br>在一个比较窄但允许两人同行的马路上，两个人相对而行，如果两个人相撞<br>死锁是：两个人僵持不动，谁都无法往前走<br>活锁是：两个人都很客气的让路给对方，但是两人同时移动到另一侧，又继续相撞，再移动回来又相撞，一直这样持续下去，那么就会发生活锁</p><p>活锁发生的概率是非常非常低的，两人的移动必须一直保持完全同步才可以，不然很快就可以解锁</p><p>发生场景：</p><ul><li>非阻塞锁（如自旋锁）在高竞争下持续重试。</li><li>两个线程互相礼让锁（如CAS失败后退避）。</li></ul><p>解决方法：</p><ul><li>引入随机退避（如指数退避）。</li><li>限制重试次数，切换到阻塞锁。</li></ul><h3 id="线程饥饿（Starvation）"><a href="#线程饥饿（Starvation）" class="headerlink" title="线程饥饿（Starvation）"></a>线程饥饿（Starvation）</h3><p>死锁或活锁描述的是多个线程的整体状态，线程饥饿描述的是单个线程的状态。<br>饥饿是某些线程长期无法获取锁，导致无法执行。</p><p>发生场景：</p><ul><li>非公平锁（如sync.Mutex）优先新请求线程。</li><li>高优先级线程抢占锁。</li></ul><p>解决方法：</p><ul><li>使用公平锁。</li><li>调整线程优先级或调度策略。</li></ul><h3 id="优先级反转（Priority-Inversion）"><a href="#优先级反转（Priority-Inversion）" class="headerlink" title="优先级反转（Priority Inversion）"></a>优先级反转（Priority Inversion）</h3><p>高优先级线程因等待低优先级线程释放共享资源（如锁）而被阻塞，而低优先级线程可能被其他中优先级线程抢占，导致高优先级线程的执行延迟。<br>这种现象违背了优先级调度原则，可能导致实时系统无法满足时间要求。</p><p>发生场景：</p><ul><li>实时系统中，低优先级线程持有锁，高优先级线程被阻塞。</li></ul><p>解决方法：</p><ul><li>优先级继承（低优先级线程临时提升优先级，确保低优先级的线程优先于中优先级线程运行，尽快释放锁。）</li><li>优先级提升（为每个锁设置最高优先级，持有锁的线程提升到该优先级）</li></ul><h3 id="伪唤醒（Spurious-Wakeup）"><a href="#伪唤醒（Spurious-Wakeup）" class="headerlink" title="伪唤醒（Spurious Wakeup）"></a>伪唤醒（Spurious Wakeup）</h3><p>伪唤醒是指线程在等待条件变量（如条件锁）时，被操作系统或运行时无故唤醒，而条件并未满足。</p><p>伪唤醒是操作系统或并发库实现条件变量时的一种副作用，其原因主要与底层实现和优化相关。<br>操作系统可能设计为允许伪唤醒，在<code>Signal()</code>或<code>Broadcast()</code>时唤醒多个等待线程，而不是精确唤醒一个。以提高效率或简化同步原语的实现，防止复杂场景下的死锁或错误。<br>伪唤醒是条件变量实现的“不可避免副作用”，标准（如POSIX、C++）明确允许其存在。<br>设计上，条件变量不保证“仅当条件满足时唤醒”，因此在使用条件锁时需要额外的处理。</p><p>解决方法：</p><ul><li>使用循环检查条件</li></ul><p>由单次检查条件改为循环检查条件：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line"><span class="keyword">if</span> !condition() &#123;</span><br><span class="line">    cond.Wait()</span><br><span class="line">&#125;</span><br><span class="line">mu.Unlock()</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mu.Lock()</span><br><span class="line"><span class="keyword">for</span> !condition() &#123;</span><br><span class="line">    cond.Wait()</span><br><span class="line">&#125;</span><br><span class="line">mu.Unlock()</span><br></pre></td></tr></table></figure><h3 id="ABA问题"><a href="#ABA问题" class="headerlink" title="ABA问题"></a>ABA问题</h3><p>ABA问题是指在并发环境中，线程读取共享变量的值为A，准备通过CAS更新时，变量可能被其他线程修改为B后再改回A。由于CAS只检查值是否为A，线程误认为变量未被修改，执行更新操作，导致逻辑错误。</p><ul><li>可能导致数据不一致（如无锁栈中重复使用已释放的内存）。</li><li>破坏程序逻辑，尤其在涉及指针或资源管理的场景。</li></ul><p>任何基于CAS操作的并发控制机制，比如非阻塞锁（自旋锁、乐观锁）、无锁数据结构等，都会引发ABA问题。<br>涉及内存重用（如指针回收后重新分配相同地址）也会引发。</p><p>ABA问题的核心是CAS无法区分“值未变”和“值变回原值”。</p><p>解决方法：</p><ul><li>版本号&#x2F;时间戳：CAS检查值和版本，两次CAS，分别比较。</li><li>双字CAS（Compare-And-Swap-Double），同时比较两个值（如值和版本号）：单次CAS，需要硬件支持DCAS。</li><li>垃圾回收：垃圾回收可以避免指针立即重用，降低ABA问题风险。</li><li>Tagged Pointers：在指针中嵌入版本信息，CAS操作检查整个指针（值+版本），避免ABA问题，复杂实现。</li><li>避免指针重用：使用新分配内存或对象池，避免值恢复原状。</li><li>阻塞锁：如sync.Mutex，完全避免ABA。</li></ul>]]>
    </content>
    <id>https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%B8%B8%E7%94%A8%E7%9A%84%E9%94%81/</id>
    <link href="https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%B8%B8%E7%94%A8%E7%9A%84%E9%94%81/"/>
    <published>2025-07-31T06:22:54.000Z</published>
    <summary>
      <![CDATA[<p>在并发编程中，锁是用于同步线程、避免数据竞争和确保线程安全的重要机制。</p>
<h2 id="互斥锁（Mutex）"><a href="#互斥锁（Mutex）" class="headerlink" title="互斥锁（Mutex）"></a>互斥锁（Mutex）</h]]>
    </summary>
    <title>常用的锁</title>
    <updated>2025-07-31T06:22:54.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="编程记录" scheme="https://cooooing.github.io/categories/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="定时任务" scheme="https://cooooing.github.io/tags/%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/"/>
    <category term="算法" scheme="https://cooooing.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="时间轮" scheme="https://cooooing.github.io/tags/%E6%97%B6%E9%97%B4%E8%BD%AE/"/>
    <content>
      <![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>时间轮（Timing Wheel）是一种高效的时间管理数据结构，广泛应用于网络协议、操作系统、定时任务调度等领域。<br>时间轮的核心思想是将时间划分为多个“槽”（slot），每个槽对应一个时间单位，并通过指针的旋转来管理定时任务。</p><h2 id="时间轮的两种设计"><a href="#时间轮的两种设计" class="headerlink" title="时间轮的两种设计"></a>时间轮的两种设计</h2><p>时间轮由多个时间槽组成，每个时间槽对应一个时间单位（如1毫秒、1秒等），是它所支持的最小粒度。<br>时间轮的核心是一个环形数组，数组的每个元素代表一个时间槽。时间轮有一个指针，指向当前的时间槽，随着时间的推进，指针会顺时针移动，类似于钟表的指针。</p><ol><li><strong>初始化</strong>：时间轮初始化时，所有时间槽为空。</li><li><strong>添加任务</strong>：当需要添加一个定时任务时，系统会根据任务的延迟时间计算出它应该被放置在哪个时间槽中。例如，如果当前指针指向第 <code>n</code> 个槽，任务的延迟时间为 <code>t</code>，每个槽的时间单位为 <code>Δt</code>，则任务应被放置在 <code>(n + t / Δt) % N</code> 个槽中（其中 <code>N</code> 是时间轮的总槽数）。</li><li><strong>指针移动</strong>：每隔一个时间单位（如1毫秒），指针向前移动一个槽。</li><li><strong>任务执行</strong>：当指针到达某个槽时，该槽中的所有任务都会被执行。</li></ol><p>为了支持更长的定时任务，有两个拓展时间轮的设计。多层时间轮和记录圈数的时间轮。</p><p><strong>多层时间轮模仿了现实世界中的时钟结构：秒针、分针、时针分别代表不同层级的时间单位。每一层时间轮负责一个时间单位（如秒、分、小时），低层时间轮每完成一圈，上层时间轮前进一格。</strong></p><p>例如：<br>第一层：每个槽代表1秒，共60槽（代表1分钟）<br>第二层：每个槽代表1分钟，共60槽（代表1小时）<br>第三层：每个槽代表1小时，共24槽（代表1天）</p><p>每隔一个时间单位（如1秒），最底层时间轮指针移动<br>如果最底层完成一圈，第二层移动一格<br>如果第二层完成一圈，第三层移动一格<br>每当最底层的指针移动到某个槽时，执行该槽中的所有任务。<br>当其他层的指针移动到某个槽时，将该槽中的任务加入下一层的时间轮。<strong>高层轮的任务只是“占位符”，真正的执行在最底层（秒轮）进行。</strong></p><p>例如一个任务在 1小时2分钟3秒后执行。当前时间为21:30:02，任务在22:32:05执行。</p><table><thead><tr><th>层级</th><th>槽号</th><th>说明</th></tr></thead><tbody><tr><td>时轮</td><td>22</td><td>1小时后触发，触发后将任务推进到分轮</td></tr><tr><td>分轮</td><td>32</td><td>32分钟后触发，触发后将任务推进到秒轮</td></tr><tr><td>秒轮</td><td>5</td><td>5秒后执行最终任务</td></tr></tbody></table><p>它支持非常长延时的任务，并且内存占用较低，时间复杂度为O(1)。但实现复杂，需要处理多级时间轮联动。</p><p><strong>记录圈数的时间轮在每个槽中不仅记录任务，还记录该任务需要等待的圈数（round）。指针每移动一圈，所有任务的圈数减一，当圈数为零时执行任务。</strong></p><p>它是一个固定大小的时间轮（如32或64个槽）。每个槽是一个任务列表，每个任务额外记录圈数。</p><p>每次指针移动一个槽<br>遍历当前槽中所有任务：如果任务的round &gt; 0，round -&#x3D; 1，如果 round &#x3D;&#x3D; 0，执行任务</p><p>它的结构简单易于实现，支持较长的延迟，时间复杂度也为O(1)。但它每次移动指针时需要遍历当前槽中的所有任务。</p><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p>这里实现记录圈数方式的时间轮。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> timewheel</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;container/list&quot;</span></span><br><span class="line"><span class="string">&quot;errors&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/orcaman/concurrent-map/v2&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/panjf2000/ants/v2&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/sirupsen/logrus&quot;</span></span><br><span class="line"><span class="string">&quot;runtime/debug&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// TimeWheel 核心结构体</span></span><br><span class="line"><span class="keyword">type</span> TimeWheel <span class="keyword">struct</span> &#123;</span><br><span class="line">interval          time.Duration <span class="comment">// 时间轮的精度</span></span><br><span class="line">slots             []*list.List  <span class="comment">// 时间轮每个位置存储的Task列表</span></span><br><span class="line">ticker            *time.Ticker  <span class="comment">// 时间轮的计时器</span></span><br><span class="line">currentPos        <span class="type">int</span>           <span class="comment">// 时间轮当前的位置</span></span><br><span class="line">slotNums          <span class="type">int</span>           <span class="comment">// 时间轮的齿轮数 interval*slotNums就是时间轮转一圈走过的时间</span></span><br><span class="line">addTaskChannel    <span class="keyword">chan</span> *Task</span><br><span class="line">removeTaskChannel <span class="keyword">chan</span> *Task</span><br><span class="line">stopChannel       <span class="keyword">chan</span> <span class="type">bool</span></span><br><span class="line">taskRecords       cmap.ConcurrentMap[<span class="type">string</span>, *list.Element] <span class="comment">// Map结构来存储Task对象，key是Task.key，value是Task在双向链表中的存储对象list.Element</span></span><br><span class="line">isRunning         <span class="type">bool</span></span><br><span class="line">pool              *ants.Pool <span class="comment">// 协程池</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Job 需要执行的Job的函数结构体</span></span><br><span class="line"><span class="keyword">type</span> Job <span class="function"><span class="keyword">func</span><span class="params">(task *Task)</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Task 时间轮上需要执行的任务</span></span><br><span class="line"><span class="keyword">type</span> Task <span class="keyword">struct</span> &#123;</span><br><span class="line">Key         <span class="type">string</span>        <span class="comment">// 用来标识task对象，是唯一的</span></span><br><span class="line">Interval    time.Duration <span class="comment">// 任务周期</span></span><br><span class="line">Times       <span class="type">int</span>           <span class="comment">// 任务需要执行的次数，如果需要一直执行，设置成-1</span></span><br><span class="line">Job         Job           <span class="comment">// 任务需要执行的Job</span></span><br><span class="line">createdTime time.Time     <span class="comment">// 任务的创建时间</span></span><br><span class="line">pos         <span class="type">int</span>           <span class="comment">// 任务在轮的位置</span></span><br><span class="line">circle      <span class="type">int</span>           <span class="comment">// 任务需要在轮走多少圈才能执行</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ErrDuplicateTaskKey is an definedError for duplicate task key</span></span><br><span class="line"><span class="keyword">var</span> ErrDuplicateTaskKey = errors.New(<span class="string">&quot;duplicate task key&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ErrTaskKeyNotFount is an definedError when task key is not found</span></span><br><span class="line"><span class="keyword">var</span> ErrTaskKeyNotFount = errors.New(<span class="string">&quot;task key doesn&#x27;t existed in task list, please check your input&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// NewTimeWheel 初始化一个TimeWheel对象</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewTimeWheel</span><span class="params">(interval time.Duration, slotNums <span class="type">int</span>)</span></span> *TimeWheel &#123;</span><br><span class="line"><span class="keyword">if</span> interval &lt;= <span class="number">0</span> || slotNums &lt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line">pool, err := ants.NewPool(</span><br><span class="line"><span class="number">16</span>,</span><br><span class="line">ants.WithPreAlloc(<span class="literal">true</span>),</span><br><span class="line">ants.WithNonblocking(<span class="literal">true</span>),</span><br><span class="line">)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">logrus.Errorf(<span class="string">&quot;init [timewhell] ants pool failed: %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">tw := &amp;TimeWheel&#123;</span><br><span class="line">interval:          interval,</span><br><span class="line">slots:             <span class="built_in">make</span>([]*list.List, slotNums),</span><br><span class="line">currentPos:        <span class="number">0</span>,</span><br><span class="line">slotNums:          slotNums,</span><br><span class="line">addTaskChannel:    <span class="built_in">make</span>(<span class="keyword">chan</span> *Task, <span class="number">16</span>),</span><br><span class="line">removeTaskChannel: <span class="built_in">make</span>(<span class="keyword">chan</span> *Task, <span class="number">16</span>),</span><br><span class="line">stopChannel:       <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">bool</span>),</span><br><span class="line">taskRecords:       cmap.New[*list.Element](),</span><br><span class="line">isRunning:         <span class="literal">false</span>,</span><br><span class="line">pool:              pool,</span><br><span class="line">&#125;</span><br><span class="line">tw.initSlots()</span><br><span class="line"><span class="keyword">return</span> tw</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Start 启动时间轮</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> Start() &#123;</span><br><span class="line">tw.ticker = time.NewTicker(tw.interval)</span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">(tw *TimeWheel)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-tw.ticker.C:</span><br><span class="line">tw.checkAndRunTask()</span><br><span class="line"><span class="keyword">case</span> task := &lt;-tw.addTaskChannel:</span><br><span class="line">tw.addTask(task, <span class="literal">false</span>)</span><br><span class="line"><span class="keyword">case</span> task := &lt;-tw.removeTaskChannel:</span><br><span class="line">tw.removeTask(task)</span><br><span class="line"><span class="keyword">case</span> &lt;-tw.stopChannel:</span><br><span class="line">tw.ticker.Stop()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;(tw)</span><br><span class="line">tw.isRunning = <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Stop 关闭时间轮</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> Stop() &#123;</span><br><span class="line">tw.stopChannel &lt;- <span class="literal">true</span></span><br><span class="line">tw.isRunning = <span class="literal">false</span></span><br><span class="line">tw.pool.Release()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// IsRunning 检查全局时间轮是否在正常运行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> IsRunning() <span class="type">bool</span> &#123;</span><br><span class="line"><span class="keyword">return</span> tw.isRunning</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Exist 检查任务是否存在</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> Exist(key <span class="type">string</span>) <span class="type">bool</span> &#123;</span><br><span class="line">_, ok := tw.taskRecords.Get(key)</span><br><span class="line"><span class="keyword">return</span> ok</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// GetTaskTimes 获取任务剩余执行次数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> GetTaskTimes(key <span class="type">string</span>) <span class="type">int</span> &#123;</span><br><span class="line">t, ok := tw.taskRecords.Get(key)</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> t.Value.(*Task).Times</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// AddTask 向时间轮添加固定周期任务</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> AddTask(task *Task) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> task.Interval &lt;= <span class="number">0</span> || task.Key == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> errors.New(<span class="string">&quot;invalid task params&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查Task.Key是否已经存在</span></span><br><span class="line">_, ok := tw.taskRecords.Get(task.Key)</span><br><span class="line"><span class="keyword">if</span> ok &#123;</span><br><span class="line"><span class="keyword">return</span> ErrDuplicateTaskKey</span><br><span class="line">&#125;</span><br><span class="line">task.createdTime = time.Now()</span><br><span class="line">tw.addTaskChannel &lt;- task</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// RemoveTask 从时间轮删除任务</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> RemoveTask(key <span class="type">string</span>) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> key == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查该Task是否存在</span></span><br><span class="line">val, ok := tw.taskRecords.Get(key)</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line"><span class="keyword">return</span> ErrTaskKeyNotFount</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">task := val.Value.(*Task)</span><br><span class="line">tw.removeTaskChannel &lt;- task</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化时间轮，每个轮上的卡槽用一个双向队列表示，便于插入和删除</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> initSlots() &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; tw.slotNums; i++ &#123;</span><br><span class="line">tw.slots[i] = list.New()</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 检查该轮点位上的Task，看哪个需要执行</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> checkAndRunTask() &#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取该轮位置的双向链表</span></span><br><span class="line">currentList := tw.slots[tw.currentPos]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> currentList != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">for</span> item := currentList.Front(); item != <span class="literal">nil</span>; &#123;</span><br><span class="line">task, ok := item.Value.(*Task)</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line">item = item.Next()</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">next := item.Next()</span><br><span class="line"><span class="keyword">if</span> task.circle &gt; <span class="number">0</span> &#123;</span><br><span class="line">task.circle--</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">if</span> task.Job != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// 使用协程池执行任务</span></span><br><span class="line">err := tw.pool.Submit(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := <span class="built_in">recover</span>(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">stack := debug.Stack()</span><br><span class="line">logrus.Errorf(<span class="string">&quot;task %v panic: %v %s\n&quot;</span>, task.Key, err, <span class="type">string</span>(stack))</span><br><span class="line">&#125;</span><br><span class="line">&#125;()</span><br><span class="line">task.Job(task)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">logrus.Errorf(<span class="string">&quot;task %v submit failed: %v\n&quot;</span>, task.Key, err)</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">logrus.Warnf(<span class="string">&quot;The task %s don&#x27;t have job to run\n&quot;</span>, task.Key)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">tw.taskRecords.Remove(task.Key)</span><br><span class="line">currentList.Remove(item)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> task.Times &lt; <span class="number">0</span> &#123;</span><br><span class="line">tw.addTask(task, <span class="literal">true</span>) <span class="comment">// 无限次，继续添加</span></span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> task.Times &gt; <span class="number">1</span> &#123;</span><br><span class="line">task.Times--</span><br><span class="line">tw.addTask(task, <span class="literal">true</span>) <span class="comment">// 剩余次数大于1，继续添加</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">item = next</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 轮前进一步</span></span><br><span class="line">tw.currentPos = (tw.currentPos + <span class="number">1</span>) % tw.slotNums</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加任务的内部函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> addTask(task *Task, byInterval <span class="type">bool</span>) &#123;</span><br><span class="line"><span class="keyword">var</span> pos, circle <span class="type">int</span></span><br><span class="line"><span class="comment">// 使用任务周期或创建时间生成</span></span><br><span class="line"><span class="keyword">if</span> byInterval &#123;</span><br><span class="line">pos, circle = tw.getPosAndCircleByInterval(task.Interval)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">pos, circle = tw.getPosAndCircleByCreatedTime(task.createdTime, task.Interval)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">task.circle = circle</span><br><span class="line">task.pos = pos</span><br><span class="line"></span><br><span class="line">element := tw.slots[pos].PushBack(task)</span><br><span class="line">tw.taskRecords.Set(task.Key, element)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除任务的内部函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> removeTask(task *Task) &#123;</span><br><span class="line">val, ok := tw.taskRecords.Get(task.Key)</span><br><span class="line"><span class="keyword">if</span> !ok &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">tw.taskRecords.Remove(task.Key)</span><br><span class="line"><span class="keyword">if</span> t, ok := val.Value.(*Task); ok &amp;&amp; t.pos &lt; tw.slotNums &#123;</span><br><span class="line">tw.slots[t.pos].Remove(val)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 该函数通过任务的周期来计算下次执行的位置和圈数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> getPosAndCircleByInterval(d time.Duration) (<span class="type">int</span>, <span class="type">int</span>) &#123;</span><br><span class="line">delayMs := <span class="type">int</span>(d.Milliseconds())</span><br><span class="line">intervalMs := <span class="type">int</span>(tw.interval.Milliseconds())</span><br><span class="line">ticks := delayMs / intervalMs</span><br><span class="line">circle := ticks / tw.slotNums</span><br><span class="line">pos := (tw.currentPos + ticks) % tw.slotNums</span><br><span class="line"></span><br><span class="line"><span class="comment">// 特殊case，当计算的位置和当前位置重叠时，因为当前位置已经走过了，所以circle需要减一</span></span><br><span class="line"><span class="keyword">if</span> pos == tw.currentPos &amp;&amp; circle != <span class="number">0</span> &#123;</span><br><span class="line">circle--</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> pos, circle</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 该函数用任务的创建时间来计算下次执行的位置和圈数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(tw *TimeWheel)</span></span> getPosAndCircleByCreatedTime(createdTime time.Time, d time.Duration) (<span class="type">int</span>, <span class="type">int</span>) &#123;</span><br><span class="line">delayMs := <span class="type">int</span>(d.Milliseconds())</span><br><span class="line">intervalMs := <span class="type">int</span>(tw.interval.Milliseconds())</span><br><span class="line">ticksPassed := <span class="type">int</span>(time.Since(createdTime).Milliseconds()) / intervalMs</span><br><span class="line">totalTicks := delayMs / intervalMs</span><br><span class="line">remainingTicks := totalTicks - ticksPassed</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> remainingTicks &lt;= <span class="number">0</span> &#123;</span><br><span class="line">remainingTicks = <span class="number">1</span> <span class="comment">// 防止立即过期</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">circle := remainingTicks / tw.slotNums</span><br><span class="line">pos := (tw.currentPos + remainingTicks) % tw.slotNums</span><br><span class="line"></span><br><span class="line"><span class="comment">// 特殊case，当计算的位置和当前位置重叠时，因为当前位置已经走过了，所以circle需要减一</span></span><br><span class="line"><span class="keyword">if</span> pos == tw.currentPos &amp;&amp; circle != <span class="number">0</span> &#123;</span><br><span class="line">circle--</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> pos, circle</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> test</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;catventure-idle-server/internal/common/timewheel&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/sirupsen/logrus&quot;</span></span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestTimeWheel</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> err <span class="type">error</span></span><br><span class="line">tw := timewheel.NewTimeWheel(time.Second, <span class="number">60</span>)</span><br><span class="line">tw.Start()</span><br><span class="line"></span><br><span class="line">err = tw.AddTask(&amp;timewheel.Task&#123;</span><br><span class="line">Key:      <span class="string">&quot;test1&quot;</span>,</span><br><span class="line">Interval: time.Second * <span class="number">5</span>,</span><br><span class="line">Times:    <span class="number">-1</span>,</span><br><span class="line">Job: <span class="function"><span class="keyword">func</span><span class="params">(task *timewheel.Task)</span></span> &#123;</span><br><span class="line">logrus.Infof(<span class="string">&quot;task %s run at %s\n&quot;</span>, task.Key, time.Now().Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line">&#125;,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">logrus.Errorf(<span class="string">&quot;add task failed: %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">err = tw.AddTask(&amp;timewheel.Task&#123;</span><br><span class="line">Key:      <span class="string">&quot;test2&quot;</span>,</span><br><span class="line">Interval: time.Second * <span class="number">1</span>,</span><br><span class="line">Times:    <span class="number">10</span>,</span><br><span class="line">Job: <span class="function"><span class="keyword">func</span><span class="params">(task *timewheel.Task)</span></span> &#123;</span><br><span class="line">logrus.Infof(<span class="string">&quot;task %s run at %s\n&quot;</span>, task.Key, time.Now().Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>))</span><br><span class="line">&#125;,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">logrus.Errorf(<span class="string">&quot;add task failed: %v&quot;</span>, err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">time.Sleep(time.Second * <span class="number">30</span>) <span class="comment">// 休眠30s，查看运行结果</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">=== RUN   TestTimeWheel</span><br><span class="line">time=&quot;2025-07-24T22:24:57+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:24:57\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:24:58+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:24:58\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:24:59+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:24:59\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:00+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:25:00\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:00+08:00&quot; level=info msg=&quot;task test1 run at 2025-07-24 22:25:00\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:01+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:25:01\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:02+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:25:02\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:03+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:25:03\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:04+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:25:04\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:05+08:00&quot; level=info msg=&quot;task test2 run at 2025-07-24 22:25:05\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:05+08:00&quot; level=info msg=&quot;task test1 run at 2025-07-24 22:25:05\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:10+08:00&quot; level=info msg=&quot;task test1 run at 2025-07-24 22:25:10\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:15+08:00&quot; level=info msg=&quot;task test1 run at 2025-07-24 22:25:15\n&quot;</span><br><span class="line">time=&quot;2025-07-24T22:25:20+08:00&quot; level=info msg=&quot;task test1 run at 2025-07-24 22:25:20\n&quot;</span><br><span class="line">--- PASS: TestTimeWheel (30.00s)</span><br><span class="line">PASS</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E6%97%B6%E9%97%B4%E8%BD%AE/</id>
    <link href="https://cooooing.github.io/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E7%BC%96%E7%A8%8B%E8%AE%B0%E5%BD%95/%E6%97%B6%E9%97%B4%E8%BD%AE/"/>
    <published>2025-07-23T14:29:39.000Z</published>
    <summary>
      <![CDATA[<h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><p>时间轮（Timing Wheel）是一种高效的时间管理数据结构，广泛应用于网络协议、操作系统、定时任务调度等领域。<br>时间轮的核心思想是]]>
    </summary>
    <title>时间轮</title>
    <updated>2025-07-23T14:29:39.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="记录生活" scheme="https://cooooing.github.io/categories/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/"/>
    <category term="旅游" scheme="https://cooooing.github.io/tags/%E6%97%85%E6%B8%B8/"/>
    <category term="西湖" scheme="https://cooooing.github.io/tags/%E8%A5%BF%E6%B9%96/"/>
    <content>
      <![CDATA[<p>来杭州一个月了，总算是去了次西湖。<br>五月中到杭州来，碰到朋友换工作六月中来杭州。算是他乡遇故知了，缘分。<br>于是在一个周日的雨天，一起去西湖。（为什么是雨天呢，我也不知道。可能雨中的西湖更有意境吧</p><p>在江城站会合，先去吃了片川儿垫垫肚子。<br>然后准备去鼓楼。</p><p>这是望仙阁上拍的，可以看到吴山上的寺庙和鼓楼堂（基督教堂）。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E6%9C%9B%E4%BB%99%E9%98%811.jpg"                        alt="望仙阁1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E6%9C%9B%E4%BB%99%E9%98%812.jpg"                        alt="望仙阁2"                 ></p><p>后面是鼓楼，进去是南宋书房，有很多文创和冰箱贴。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E5%8D%97%E5%AE%8B%E4%B9%A6%E6%88%BF1.jpg"                        alt="南宋书房1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E5%8D%97%E5%AE%8B%E4%B9%A6%E6%88%BF2.jpg"                        alt="南宋书房2"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E9%BC%93%E6%A5%BC.jpg"                        alt="鼓楼"                 ></p><p>再往里就是河坊街，正常景区的商业街。很多小吃，还有中药店（叶种德堂）。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E5%8F%B6%E7%A7%8D%E5%BE%B7%E5%A0%82.jpg"                        alt="叶种德堂"                 ></p><p>朱炳仁铜雕艺术博物馆，全是铜雕的工艺品。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E6%9C%B1%E7%82%B3%E4%BB%81%E9%93%9C%E9%9B%95%E8%89%BA%E6%9C%AF%E5%8D%9A%E7%89%A9%E9%A6%86.jpg"                        alt="朱炳仁铜雕艺术博物馆"                 ></p><p>杭州博物馆，杭州出土的文物，历史等。杭州现在有大大小小两百多家博物馆。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E6%9D%AD%E5%B7%9E%E5%8D%9A%E7%89%A9%E9%A6%861.jpg"                        alt="杭州博物馆1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E6%9D%AD%E5%B7%9E%E5%8D%9A%E7%89%A9%E9%A6%862.jpg"                        alt="杭州博物馆2"                 ></p><p>博物馆逛了很久，出来后两三点左右，去吃了蟹黄面。第一口很鲜，非常不错。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%9F%B9%E9%BB%84%E9%9D%A21.jpg"                        alt="蟹黄面1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%9F%B9%E9%BB%84%E9%9D%A22.jpg"                        alt="蟹黄面2"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%9F%B9%E9%BB%84%E9%9D%A23.jpg"                        alt="蟹黄面3"                 ></p><p>再然后就是西湖了。断桥残雪、苏堤。雨天人还是挺多，不过雨天的意境肯定也不一样。</p><p><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%A5%BF%E6%B9%961.jpg"                        alt="西湖1"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%A5%BF%E6%B9%962.jpg"                        alt="西湖2"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%A5%BF%E6%B9%963.jpg"                        alt="西湖3"                 ><br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E8%A5%BF%E6%B9%964.jpg"                        alt="西湖4"                 ></p><p>烟雨朦胧的感觉<br>最后，全都淋湿了。不过也很久没有这么淋过雨了，还是挺舒服的。</p><p>20公里！<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/%E6%AD%A5%E6%95%B0.png"                        alt="步数"                 ></p>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/</id>
    <link href="https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E7%83%9F%E9%9B%A8%E8%A5%BF%E6%B9%96/"/>
    <published>2025-06-29T09:57:54.000Z</published>
    <summary>
      <![CDATA[<p>来杭州一个月了，总算是去了次西湖。<br>五月中到杭州来，碰到朋友换工作六月中来杭州。算是他乡遇故知了，缘分。<br>于是在一个周日的雨天，一起去西湖。（为什么是雨天呢，我也不知道。可能雨中的西湖更有意境吧</p>
<p>在江城站会合，先去吃了片川儿垫垫肚子。<br>然后准]]>
    </summary>
    <title>烟雨西湖</title>
    <updated>2025-06-29T09:57:54.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="记录生活" scheme="https://cooooing.github.io/categories/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/"/>
    <category term="工作" scheme="https://cooooing.github.io/tags/%E5%B7%A5%E4%BD%9C/"/>
    <content>
      <![CDATA[<p>很久没有写博客了，回顾下近况吧。</p><p>最大的变动就是工作了，劳动节前提了离职，5月16号上完最后一天半。然后就开始了两周的休息。<br>第一周主要去医院做了体检，和朋友吃了个饭。也是告别待了快两年的南京（两年了也没怎么把南京的景点玩玩，有点可惜<br>周一体检，周四拿到体检报告。除了口腔有点问题（智齿有点阻生，浅龋和1度牙结石），其他都非常正常。也许得找个时间去看看牙，之前根管治疗的牙还没有补。</p><p>回到工作相关，没记错的话，是在23年9月26号开始实习，24年7月1号签的劳动合同，到今年5月16号离职结束。也是待了快二十个月（对于一份工作来说，感觉是有些短<br>跟同事相处的感觉也是很不错了，和同事一起租房住了十个月（血亏两个月房租<br>下面是娟姐送的抱枕<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%A6%BB%E8%81%8C/%E6%8A%B1%E6%9E%95.jpg"                        alt="抱枕"                 ></p><p>关于什么时候决定离开，比实际要早的很多。<br>最现实的就是我能在南京拿到的薪资比其他的城市低（上海杭州这些互联网发达的地方）。<br>其次重要的是个人的成长，其实早就一眼看到头了。或许我继续待在那里三年五年，多少会接触到新的东西，但对于我来说有些太慢了。<br>四月份左右去用户现场碰到我的 leader，他还会问我最近学了什么。说<strong>人的技术热情在刚工作的两三年是最高的，成长也是最快的。后面就不会再有了。</strong><br>这段我还是比较认同的，在刚工作的时候，接触到企业协作，与个人做事是完全不一样的。越大的企业确实如此吧。<br>为了适应工作，肯定会多去学习。但随着工作的深入，如果不是有兴趣支持，这份热情很快就会消磨。（当兴趣爱好成为工作，很容易消磨掉兴趣。肯定有人说过不要将兴趣爱好当成工作的话</p><p>话说回来，当时在看 Kafka（也记了两篇笔记）。<strong>他和我说的另一个主题大概就是要关注架构层面的设计，使用层面都差不多，就是如何使用API，看着文档很快就能上手。</strong><br>这个怎么说呢，我觉得架构层面需要服务具体的业务，同时也跟并发编程一样，需要长时间的运行才能暴露出足够多的问题。<br>但更加关注架构层面肯定也是没有问题的，和并发编程一样，思考各种可能出现的情况，会有什么问题，怎么解决。<br>架构层面其实很难离开分布式和微服务，虽然我认为大部分公司单体的架构已经足够使用。<br>这些都多少会影响我后面的学习方向，go的学习应该是很早之前了，真正想java转go可能只是今年年初。但语言只是微不足道的一部分…（这里省略吧，后续的方向暂时还没有想好</p><p>关于新的工作，它其实没有我想象中那样。<br>新的工作是在杭州的一个小公司，相比上家，在人员配置和开发流程上差的是非常大的。感觉是第一次组建开发团队。<br>用go写了两周，不得不感慨java生态上的全面。java的繁琐其实也不是java本身，印象中有句话是这么说的：<strong>复杂度不会消失，只会被转移到系统的各个地方。</strong><br>大概是<a class="link"   href="https://en.wikipedia.org/wiki/Law_of_conservation_of_complexity" >泰斯勒定律（Tesler’s Law），又称复杂度守恒定律（Law of Conservation of Complexity）<i class="fas fa-external-link-alt"></i></a><br>spring是个非常厉害的框架，它的抽象程度也是非常之高（当初还去读源码，太不自量力了）。它将系统配置、拓展性等大部分业务无关的复杂度都隐藏在了框架内部，使得对业务的开发变得非常简单，关注的点也非常少。</p><p>新同事是三年经验的go开发，最近和他的话题主要在 go、docker 和 kubernetes。<br>今天下班的路上，也许是看出了我的迷茫，跟我说了一些云原生的方向。kubernetes、服务网格 istio、链路之类的东西，都在云原生方向之中。<br>看来 kubernetes 是要深度使用的了，虽然我觉得大部分业务都用不上。我现在倒是更倾向单体架构，到单机扛不住的时候可以上分布式（这个需要单体开发时就注意分布式部分，否则改造会比较麻烦），到分布式服务太多运维管理上顶不住的时候才是上kubernetes的时候。<br>倒也是符合架构的发展历史，不管是架构还是框架、中间件都是为了解决对应具体问题而出现的解决方案。分布式与微服务还是有区别的，微服务大概就是服务划分、拆分之后的各种服务的分布式，服务间的依赖通过rpc解耦。（描述得比较抽象了</p><p>最后，写到这里已经不早了，该结束了。<br>对于以后，大方向已经定下来了。具体什么方向可能还得探索下。<br>另，希望新工作顺利。也许会留在杭州。</p>]]>
    </content>
    <id>https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%A6%BB%E8%81%8C/</id>
    <link href="https://cooooing.github.io/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E5%BD%95%E7%94%9F%E6%B4%BB/%E8%AE%B0%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%A6%BB%E8%81%8C/"/>
    <published>2025-06-11T15:14:33.000Z</published>
    <summary>
      <![CDATA[<p>很久没有写博客了，回顾下近况吧。</p>
<p>最大的变动就是工作了，劳动节前提了离职，5月16号上完最后一天半。然后就开始了两周的休息。<br>第一周主要去医院做了体检，和朋友吃了个饭。也是告别待了快两年的南京（两年了也没怎么把南京的景点玩玩，有点可惜<br>周一体检，周]]>
    </summary>
    <title>记第一次离职</title>
    <updated>2025-06-11T15:14:33.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="学习笔记" scheme="https://cooooing.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Kubernetes" scheme="https://cooooing.github.io/tags/Kubernetes/"/>
    <content>
      <![CDATA[<h2 id="基础环境准备（可选）"><a href="#基础环境准备（可选）" class="headerlink" title="基础环境准备（可选）"></a>基础环境准备（可选）</h2><h3 id="虚拟机配置"><a href="#虚拟机配置" class="headerlink" title="虚拟机配置"></a>虚拟机配置</h3><p>基础环境这里选择 multipass 虚拟机。（但只是使用它创建虚拟机）<br>当然，如果有真实物理机，并且他们网络是直通的，那可以省去很多配置的麻烦。这里大部分关于虚拟机的配置应该都可以省略。<br>但很可惜，我并没有那么多机器或者云服务器。但好在我的电脑（windows11）内存有64G，足够我随意的折腾。</p><p>创建三台虚拟机（一主二从）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">multipass launch --name=master --cpus=2 --m=4096MiB -d 20G 24.04</span><br><span class="line">multipass launch --name=worker1 --cpus=2 --m=4096MiB -d 20G 24.04</span><br><span class="line">multipass launch --name=worker2 --cpus=2 --m=4096MiB -d 20G 24.04</span><br></pre></td></tr></table></figure><h3 id="基础网络配置（固定ip配置）"><a href="#基础网络配置（固定ip配置）" class="headerlink" title="基础网络配置（固定ip配置）"></a>基础网络配置（固定ip配置）</h3><p>这里我需要为每台虚拟机都设置一个固定ip，防止机器重启后ip会发生变化，导致需要频繁改一些配置。<strong>Multipass 默认通过 NAT 网络 + DHCP 动态分配 IP</strong><br>将宿主机ip固定为192.168.1.5<br>master 节点的ip为 192.168.1.10<br>worker1 节点的ip为 192.168.1.11<br>worker2 节点的ip为 192.168.1.12</p><p>这里以 master 节点为例，展示配置过程。</p><p>首先打开<strong>Hyper-V 管理器</strong>，右侧<strong>虚拟交换机管理器</strong>，新建虚拟交换机。如下图：<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Kubernetes%E6%9C%AC%E5%9C%B0%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E5%8F%8A%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2/%E6%96%B0%E5%BB%BA%E8%99%9A%E6%8B%9F%E4%BA%A4%E6%8D%A2%E6%9C%BA.png"                        alt="新建虚拟交换机.png"                 ></p><p>保存之后，来到<strong>网络适配器</strong>，为刚创建的虚拟交换机设置一个固定的ip。<br><img                         lazyload                       alt="image"                       data-src="https://cooooing.github.io/images/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Kubernetes%E6%9C%AC%E5%9C%B0%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E5%8F%8A%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2/%E7%BD%91%E7%BB%9C%E9%80%82%E9%85%8D%E5%99%A8.png"                        alt="网络适配器.png"                 ></p><p>修改<code>/etc/netplan/50-cloud-init.yaml</code>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">network:</span></span><br><span class="line">  <span class="attr">version:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">ethernets:</span></span><br><span class="line">    <span class="attr">eth0:</span></span><br><span class="line">      <span class="attr">dhcp4:</span> <span class="literal">no</span></span><br><span class="line">      <span class="attr">addresses:</span> [ <span class="number">192.168</span><span class="number">.1</span><span class="number">.10</span><span class="string">/24</span> ]</span><br><span class="line">      <span class="attr">routes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">to:</span> <span class="string">default</span></span><br><span class="line">          <span class="attr">via:</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.1</span></span><br><span class="line">      <span class="attr">nameservers:</span></span><br><span class="line">        <span class="attr">addresses:</span> [ <span class="number">192.168</span><span class="number">.1</span><span class="number">.1</span> ]</span><br></pre></td></tr></table></figure><p>应用更改并验证</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">应用更改</span></span><br><span class="line">sudo netplan apply</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">应用更改</span></span><br><span class="line">sudo netplan --debug apply</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">检查 IP 是否生效</span></span><br><span class="line">ip a show eth0</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">测试网络连通性</span></span><br><span class="line">ping 192.168.1.1</span><br></pre></td></tr></table></figure><p>输出如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:/etc/netplan$ ip a show eth0</span><br><span class="line">2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP group default qlen 1000</span><br><span class="line">    link/ether 52:54:00:29:3e:e9 brd ff:ff:ff:ff:ff:ff</span><br><span class="line">    inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0</span><br><span class="line">       valid_lft forever preferred_lft forever</span><br><span class="line">    inet6 2409:8a20:2a0:6c60:5054:ff:fe29:3ee9/64 scope global dynamic mngtmpaddr noprefixroute</span><br><span class="line">       valid_lft 198387sec preferred_lft 111987sec</span><br><span class="line">    inet6 fe80::5054:ff:fe29:3ee9/64 scope link</span><br><span class="line">       valid_lft forever preferred_lft forever</span><br><span class="line">ubuntu@master:/etc/netplan$ ping 192.168.1.1</span><br><span class="line">PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.</span><br><span class="line">64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=7.33 ms</span><br><span class="line">64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=3.45 ms</span><br><span class="line">64 bytes from 192.168.1.1: icmp_seq=3 ttl=64 time=3.77 ms</span><br><span class="line">64 bytes from 192.168.1.1: icmp_seq=4 ttl=64 time=4.16 ms</span><br><span class="line">^C</span><br><span class="line">--- 192.168.1.1 ping statistics ---</span><br><span class="line">4 packets transmitted, 4 received, 0% packet loss, time 3006ms</span><br><span class="line">rtt min/avg/max/mdev = 3.449/4.675/7.326/1.550 ms</span><br></pre></td></tr></table></figure><h3 id="配置域名"><a href="#配置域名" class="headerlink" title="配置域名"></a>配置域名</h3><p>在三台虚拟机的 <code>/etc/hosts</code> 文件末尾添加：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">192.168.1.10 master</span><br><span class="line">192.168.1.11 worker1</span><br><span class="line">192.168.1.12 worker2</span><br></pre></td></tr></table></figure><h3 id="配置ssh登录"><a href="#配置ssh登录" class="headerlink" title="配置ssh登录"></a>配置ssh登录</h3><p>这里配置ssh密码登录，以方便外部宿主机连接使用。（因为修改为固定ip后，multipass将无法连接到虚拟机）</p><p>设置密码：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo passwd ubuntu</span><br></pre></td></tr></table></figure><p>修改<code>/etc/ssh/sshd_config</code>配置中的以下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># 是否允许使用密码登录 SSH</span><br><span class="line">PasswordAuthentication yes</span><br><span class="line"># 是否允许使用公钥认证登录 SSH</span><br><span class="line">PubkeyAuthentication yes</span><br><span class="line"># 是否启用 PAM（Pluggable Authentication Modules，可插拔认证模块）</span><br><span class="line">UsePAM no</span><br></pre></td></tr></table></figure><blockquote><p>注意：是否包含<code>Include /etc/ssh/sshd_config.d/*.conf</code>配置<br>它支持包含其他配置文件，从而实现配置的模块化和可维护性。<strong>所有配置会合并，顺序按文件名排序加载，后加载的内容会覆盖前面的设置。</strong></p></blockquote><p>重启 ssh 服务应用更改后，就可以在宿主机使用配好的固定ip进行ssh登录了：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl restart ssh</span><br></pre></td></tr></table></figure><h3 id="配置全局代理"><a href="#配置全局代理" class="headerlink" title="配置全局代理"></a>配置全局代理</h3><p>Linux中，设置全系统代理（包括 GUI 图形界面 + CLI），需要修改<code>/etc/environment</code>文件；仅为 shell 用户（终端）设置代理，需要修改<code>/etc/profile</code>文件；为单用户设置代理，需要修改<code>~/.bashrc</code>文件。</p><p><code>/etc/environment</code>文件添加：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">http_proxy=&quot;http://192.168.1.5:7890&quot;</span><br><span class="line">https_proxy=&quot;http://192.168.1.5:7890&quot;</span><br><span class="line">all_proxy=&quot;socks5://192.168.1.5:7890&quot;</span><br><span class="line">no_proxy=&quot;localhost,127.0.0.1,::1&quot;</span><br></pre></td></tr></table></figure><p>重启应用更改。</p><p><code>/etc/profile</code>或者<code>~/.bashrc</code>文件添加：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">export http_proxy=http://192.168.1.5:7890</span><br><span class="line">export https_proxy=http://192.168.1.5:7890</span><br><span class="line">export all_proxy=socks5://192.168.1.5:7890</span><br><span class="line">export no_proxy=&quot;localhost,127.0.0.1,::1&quot;</span><br></pre></td></tr></table></figure><p><code>source /etc/profile</code>或者<code>source ~/.bashrc</code>应用更改。</p><p><strong>取消代理：<code>unset http_proxy https_proxy all_proxy</code></strong></p><p>验证代理是否生效，可以使用<code>curl -v https://www.google.com</code><br>会返回包含类似<code>Connected to 192.168.1.5 (192.168.1.5) port 7890</code>的内容，如下（上述命令返回过长，这里以本地服务为例）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~$ curl -v http://192.168.1.5:8000/api/user/v1/helloworld/ubuntu</span><br><span class="line">* Uses proxy env variable no_proxy == &#x27;localhost,127.0.0.1,::1&#x27;</span><br><span class="line">* Uses proxy env variable http_proxy == &#x27;http://192.168.1.5:7890&#x27;</span><br><span class="line">*   Trying 192.168.1.5:7890...</span><br><span class="line">* Connected to 192.168.1.5 (192.168.1.5) port 7890</span><br><span class="line">&gt; GET http://192.168.1.5:8000/api/user/v1/helloworld/ubuntu HTTP/1.1</span><br><span class="line">&gt; Host: 192.168.1.5:8000</span><br><span class="line">&gt; User-Agent: curl/8.5.0</span><br><span class="line">&gt; Accept: */*</span><br><span class="line">&gt; Proxy-Connection: Keep-Alive</span><br><span class="line">&gt;</span><br><span class="line">&lt; HTTP/1.1 200 OK</span><br><span class="line">&lt; Content-Length: 26</span><br><span class="line">&lt; Connection: keep-alive</span><br><span class="line">&lt; Content-Type: application/json</span><br><span class="line">&lt; Date: Wed, 23 Apr 2025 15:34:03 GMT</span><br><span class="line">&lt; Keep-Alive: timeout=4</span><br><span class="line">&lt; Proxy-Connection: keep-alive</span><br><span class="line">&lt;</span><br><span class="line">* Connection #0 to host 192.168.1.5 left intact</span><br><span class="line">&#123;&quot;message&quot;:&quot;Hello ubuntu&quot;&#125;</span><br></pre></td></tr></table></figure><h2 id="kubernetes-环境搭建"><a href="#kubernetes-环境搭建" class="headerlink" title="kubernetes 环境搭建"></a>kubernetes 环境搭建</h2><h3 id="部署-Server"><a href="#部署-Server" class="headerlink" title="部署 Server"></a>部署 Server</h3><p>这里使用 <a class="link"   href="https://docs.k3s.io/zh/" >K3s - 轻量级 Kubernetes<i class="fas fa-external-link-alt"></i></a> 部署。<br>与 Kubernetes 不同，这里的 Master 节点叫 Server 节点，而 Slave 节点叫 Agent 节点。</p><p>部署 Server 节点 <code>curl -sfL https://get.k3s.io | sh -</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~$ curl -sfL https://get.k3s.io | sh -</span><br><span class="line">[INFO]  Finding release for channel stable</span><br><span class="line">[INFO]  Using v1.32.3+k3s1 as release</span><br><span class="line">[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.32.3+k3s1/sha256sum-amd64.txt</span><br><span class="line">[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.32.3+k3s1/k3s</span><br><span class="line">[INFO]  Verifying binary download</span><br><span class="line">[INFO]  Installing k3s to /usr/local/bin/k3s</span><br><span class="line">[INFO]  Skipping installation of SELinux RPM</span><br><span class="line">[INFO]  Creating /usr/local/bin/kubectl symlink to k3s</span><br><span class="line">[INFO]  Creating /usr/local/bin/crictl symlink to k3s</span><br><span class="line">[INFO]  Creating /usr/local/bin/ctr symlink to k3s</span><br><span class="line">[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh</span><br><span class="line">[INFO]  Creating uninstall script /usr/local/bin/k3s-uninstall.sh</span><br><span class="line">[INFO]  env: Creating environment file /etc/systemd/system/k3s.service.env</span><br><span class="line">[INFO]  systemd: Creating service file /etc/systemd/system/k3s.service</span><br><span class="line">[INFO]  systemd: Enabling k3s unit</span><br><span class="line">Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.</span><br><span class="line">[INFO]  systemd: Starting k3s</span><br></pre></td></tr></table></figure><p>查看节点运行状态<code>kubectl get node</code>：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~$ sudo kubectl get node</span><br><span class="line">NAME     STATUS   ROLES                  AGE   VERSION</span><br><span class="line">master   Ready    control-plane,master   78s   v1.32.3+k3s1</span><br></pre></td></tr></table></figure><p>获取 node-token <code>cat /var/lib/rancher/k3s/server/node-token</code> 在部署 Agent 节点的时候需要用到：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~$ sudo cat /var/lib/rancher/k3s/server/node-token</span><br><span class="line">K104869223bb6e5c3c1bb95bc7dcc505cb2d5576cf2253e9bd0df1b3a7852f91397::server:368a64fba85ad0718fe841967df1717f</span><br></pre></td></tr></table></figure><h3 id="部署-Agent"><a href="#部署-Agent" class="headerlink" title="部署 Agent"></a>部署 Agent</h3><p>部署 Agent 节点 <code>curl -sfL https://get.k3s.io | K3S_URL=https://server:6443 K3S_TOKEN=token sh -</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@worker1:~$ curl -sfL https://get.k3s.io | K3S_URL=https://master:6443 K3S_TOKEN=K104869223bb6e5c3c1bb95bc7dcc505cb2d5576cf2253e9bd0df1b3a7852f91397::server:368a64fba85ad0718fe841967df1717f sh -</span><br><span class="line">[INFO]  Finding release for channel stable</span><br><span class="line">[INFO]  Using v1.32.3+k3s1 as release</span><br><span class="line">[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.32.3+k3s1/sha256sum-amd64.txt</span><br><span class="line">[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.32.3+k3s1/k3s</span><br><span class="line">[INFO]  Verifying binary download</span><br><span class="line">[INFO]  Installing k3s to /usr/local/bin/k3s</span><br><span class="line">[INFO]  Skipping installation of SELinux RPM</span><br><span class="line">[INFO]  Creating /usr/local/bin/kubectl symlink to k3s</span><br><span class="line">[INFO]  Creating /usr/local/bin/crictl symlink to k3s</span><br><span class="line">[INFO]  Creating /usr/local/bin/ctr symlink to k3s</span><br><span class="line">[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh</span><br><span class="line">[INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh</span><br><span class="line">[INFO]  env: Creating environment file /etc/systemd/system/k3s-agent.service.env</span><br><span class="line">[INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service</span><br><span class="line">[INFO]  systemd: Enabling k3s-agent unit</span><br><span class="line">Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service.</span><br><span class="line">[INFO]  systemd: Starting k3s-agent</span><br></pre></td></tr></table></figure><p>节点都部署完后，节点状态如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~$ sudo kubectl get node</span><br><span class="line">NAME      STATUS   ROLES                  AGE   VERSION</span><br><span class="line">master    Ready    control-plane,master   18m   v1.32.3+k3s1</span><br><span class="line">worker1   Ready    &lt;none&gt;                 29s   v1.32.3+k3s1</span><br><span class="line">worker2   Ready    &lt;none&gt;                 5s    v1.32.3+k3s1</span><br></pre></td></tr></table></figure><p>设置 K3S_URL 参数会使 K3s 以 worker 模式运行。 K3s agent 会在所提供的 URL 上向监听的 K3s 服务器注册。</p><p>到这里，Kubernetes 环境搭建完成。</p><h2 id="创建-Deployment"><a href="#创建-Deployment" class="headerlink" title="创建 Deployment"></a>创建 Deployment</h2><p>创建<code>nginx-deployment.yaml</code>：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 定义 Deployment 基本信息</span></span><br><span class="line"><span class="attr">metadata.name:</span> <span class="string">给这个</span> <span class="string">Deployment</span> <span class="string">起个名字。</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span> <span class="comment"># Deployment 所使用的 API 版本，K8s 1.9+ 都是 apps/v1。</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span> <span class="comment"># 资源类型是 Deployment。</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx</span> <span class="comment"># Deployment 的名字。</span></span><br><span class="line"><span class="comment"># 定义副本数量和 Pod 选择器</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span> <span class="comment"># 创建副本数量</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx</span> <span class="comment"># Deployment 会管理所有 带有标签 app=nginx 的 Pod。它必须和后面的模板里的 labels 保持一致。</span></span><br><span class="line">  <span class="comment"># 定义 Pod 模板（template）</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx</span> <span class="comment"># 为 Pod 打上 app=nginx 标签，方便 selector 识别。</span></span><br><span class="line">    <span class="comment"># 定义容器信息</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx</span> <span class="comment"># 容器名称</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">nginx:latest</span> <span class="comment"># 镜像名称</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span> <span class="comment"># 容器端口</span></span><br></pre></td></tr></table></figure><p>使用 <code>kubectl apply</code> 命令创建 Deployment：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~/apps$ sudo kubectl apply -f nginx-deployment.yaml</span><br><span class="line">deployment.apps/nginx created</span><br></pre></td></tr></table></figure><p>使用<code>kubectl get deployment</code>查看运行状态，使用<code>kubectl get pods</code>查看 Pod 状态：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~/apps$ sudo kubectl get deployment</span><br><span class="line">NAME    READY   UP-TO-DATE   AVAILABLE   AGE</span><br><span class="line">nginx   2/2     2            2           17s</span><br><span class="line">ubuntu@master:~/apps$ sudo kubectl get pods -l app=nginx</span><br><span class="line">NAME                   READY   STATUS    RESTARTS   AGE</span><br><span class="line">nginx-96b9d695-gfjvq   1/1     Running   0          25s</span><br><span class="line">nginx-96b9d695-w5qxx   1/1     Running   0          25s</span><br></pre></td></tr></table></figure><p>可以看到 Deployment 创建 Pod 的命名规则：<code>${DeploymentName}-${DeploymentUid}-${Hash}</code>。</p><p>使用 <code>kubectl get pod -o wide</code> 查看 Pod 运行状态和 IP 地址，可以看到 nginx 被创建了两个 Pod 副本，分别部署在了 worker1 和 worker2 上面：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~/apps$ sudo kubectl get pod -o wide</span><br><span class="line">NAME                   READY   STATUS    RESTARTS   AGE     IP          NODE      NOMINATED NODE   READINESS GATES</span><br><span class="line">nginx-96b9d695-gfjvq   1/1     Running   0          8m42s   10.42.2.3   worker2   &lt;none&gt;           &lt;none&gt;</span><br><span class="line">nginx-96b9d695-w5qxx   1/1     Running   0          8m42s   10.42.1.3   worker1   &lt;none&gt;           &lt;none&gt;</span><br></pre></td></tr></table></figure><p>此时的 nginx 还不能被外部访问，他们的 IP 是 Cluster 的内部私有 IP，只能在集群内部访问。<br><strong>并且这些 IP 是浮动的，Pod 重启后，IP 也会变化。</strong><br>这时就需要创建 Service 来解决这个问题。</p><h2 id="创建-Service"><a href="#创建-Service" class="headerlink" title="创建 Service"></a>创建 Service</h2><p>Service 是 Kubernetes 中一种网络抽象，用于为一组 Pod 提供统一访问入口。<br>简单来说，Pod 是会变化的（比如被删除、替换），而 Service 提供一个 固定 IP &#x2F; 名称 &#x2F; 端口，始终指向一组后端 Pod。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">apiVersion: v1 # 使用 v1 API，Service 资源的标准版本</span><br><span class="line">kind: Service # 声明该资源类型为 Service</span><br><span class="line">metadata:</span><br><span class="line">  name: nginx-service # Service 的名字，供集群中引用、DNS 名称生成等使用</span><br><span class="line">spec:</span><br><span class="line">  type: NodePort  # 设置 Service 类型为 NodePort，支持通过任意节点的 IP + nodePort 端口访问服务</span><br><span class="line">  selector:</span><br><span class="line">    app: nginx    # 选择所有标签为 app=nginx 的 Pod</span><br><span class="line">  ports:</span><br><span class="line">    - port: 80         # Service 自己监听的端口，供集群内其他 Pod 调用这个服务时使用</span><br><span class="line">      targetPort: 80   # 转发到后端 Pod 的哪个端口（nginx 监听的端口）</span><br><span class="line">      nodePort: 30080  # 外部访问时的端口（可不写，系统会自动分配 30000~32767）</span><br></pre></td></tr></table></figure><p>使用 <code>kubectl apply</code> 命令创建 Service，使用 <code>kubectl get service</code> 查看运行状态：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~/apps/nginx$ sudo kubectl apply -f nginx-service.yaml</span><br><span class="line">service/nginx-service created</span><br><span class="line">ubuntu@master:~/apps/nginx$ sudo kubectl get service</span><br><span class="line">NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE</span><br><span class="line">kubernetes      ClusterIP   10.43.0.1       &lt;none&gt;        443/TCP        72m</span><br><span class="line">nginx-service   NodePort    10.43.119.114   &lt;none&gt;        80:30080/TCP   2m57s</span><br></pre></td></tr></table></figure><p>到这里，已经可以使用节点 node 外部的 IP + nodePort 访问到 nginx 了。<br>但是，访问 nginx 的时候，IP 是 node 的 IP，没有一个统一的入口。</p><h2 id="配置应用打包流程"><a href="#配置应用打包流程" class="headerlink" title="配置应用打包流程"></a>配置应用打包流程</h2><p>经过上面 nginx 部署的测试，现在k8s集群已经是可用状态了。下面要将本地编写的应用打包为镜像，并上传到镜像仓库，再通过 yaml 配置部署到 k8s 集群中。</p><p>这里使用的本地项目使用 go-kratos 构建的微服务项目。<br>这是一个大仓项目，目录结构如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">BBS</span><br><span class="line">├── .github</span><br><span class="line">├── app</span><br><span class="line">│   ├── backend</span><br><span class="line">│   │   ├── common</span><br><span class="line">│   │   │   ├── api</span><br><span class="line">│   │   │   │   ├── common</span><br><span class="line">│   │   │   │   │   ├── v1</span><br><span class="line">│   │   │   │   │   │   ├── error_reason.pb.go</span><br><span class="line">│   │   │   │   │   │   └── error_reason.proto</span><br><span class="line">│   │   │   │   ├── user</span><br><span class="line">│   │   │   │   │   ├── v1</span><br><span class="line">│   │   │   │   │   │   ├── demo.pb.go</span><br><span class="line">│   │   │   │   │   │   ├── demo.proto</span><br><span class="line">│   │   │   │   │   │   ├── demo_grpc.pb.go</span><br><span class="line">│   │   │   │   │   │   └── demo_http.pb.go</span><br><span class="line">│   │   │   ├── third_party</span><br><span class="line">│   │   ├── user</span><br><span class="line">│   │   │   ├── cmd</span><br><span class="line">│   │   │   │   ├── user</span><br><span class="line">│   │   │   │   │   ├── main.go</span><br><span class="line">│   │   │   │   │   ├── wire.go</span><br><span class="line">│   │   │   │   │   └── wire_gen.go</span><br><span class="line">│   │   │   ├── configs</span><br><span class="line">│   │   │   │   ├── config.yaml</span><br><span class="line">│   │   │   ├── internal</span><br><span class="line">│   │   │   ├── Dockerfile</span><br><span class="line">│   │   │   ├── Makefile</span><br><span class="line">│   │   ├── go.mod</span><br><span class="line">│   │   ├── go.sum</span><br><span class="line">│   │   ├── openapi.yaml</span><br><span class="line">│   ├── frontend</span><br><span class="line">└── ...</span><br></pre></td></tr></table></figure><p>基于这个目录结构，来修改 应用构建文件<code>Makefile</code>、Docker镜像构建文件<code>Dockerfile</code>、github action 文件<code>user-build.yaml</code>。</p><p>首先是 <code>Makefile</code> 文件：</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 全局变量定义</span></span><br><span class="line">GOHOSTOS := <span class="variable">$(<span class="built_in">shell</span> go env GOHOSTOS)</span></span><br><span class="line">GOPATH := <span class="variable">$(<span class="built_in">shell</span> go env GOPATH)</span></span><br><span class="line">VERSION := latest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 项目目录结构定义 - 使用更可靠的路径获取方式</span></span><br><span class="line">ROOT_DIR := <span class="variable">$(<span class="built_in">realpath</span> $(<span class="built_in">dir</span> $(<span class="built_in">lastword</span> <span class="variable">$(MAKEFILE_LIST)</span>)</span>)/..)</span><br><span class="line">COMMON_DIR := <span class="variable">$(ROOT_DIR)</span>/common</span><br><span class="line">USER_DIR := <span class="variable">$(ROOT_DIR)</span>/user</span><br><span class="line">API_DIR := <span class="variable">$(COMMON_DIR)</span>/api</span><br><span class="line">THIRD_PARTY_DIR := <span class="variable">$(COMMON_DIR)</span>/third_party</span><br><span class="line">INTERNAL_DIR := <span class="variable">$(ROOT_DIR)</span>/user/internal</span><br><span class="line"></span><br><span class="line"><span class="comment"># 根据操作系统设置查找命令</span></span><br><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(GOHOSTOS)</span>, windows)</span><br><span class="line">    Git_Bash := <span class="variable">$(<span class="built_in">subst</span> \,/,$(<span class="built_in">subst</span> cmd\,bin\bash.exe,$(<span class="built_in">dir</span> $(<span class="built_in">shell</span> where git)</span>)))</span><br><span class="line">    FIND_CMD := <span class="variable">$(Git_Bash)</span> -c <span class="string">&quot;find&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    FIND_CMD := find</span><br><span class="line"><span class="keyword">endif</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Proto 文件查找</span></span><br><span class="line">INTERNAL_PROTO_FILES := <span class="variable">$(<span class="built_in">shell</span> <span class="variable">$(FIND_CMD)</span> <span class="variable">$(INTERNAL_DIR)</span> -name &#x27;*.proto&#x27; 2&gt;/dev/null)</span></span><br><span class="line">API_PROTO_FILES := <span class="variable">$(<span class="built_in">shell</span> <span class="variable">$(FIND_CMD)</span> <span class="variable">$(API_DIR)</span> -name &#x27;*.proto&#x27; 2&gt;/dev/null)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 工具安装</span></span><br><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: init</span></span><br><span class="line"><span class="section">init:</span></span><br><span class="line">@echo <span class="string">&quot;Installing required tools...&quot;</span></span><br><span class="line">go install google.golang.org/protobuf/cmd/protoc-gen-go@latest</span><br><span class="line">go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest</span><br><span class="line">go install github.com/go-kratos/kratos/cmd/kratos/v2@latest</span><br><span class="line">go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest</span><br><span class="line">go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest</span><br><span class="line">go install github.com/google/wire/cmd/wire@latest</span><br><span class="line"></span><br><span class="line"><span class="comment">## 内部 Proto 生成</span></span><br><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: config</span></span><br><span class="line"><span class="section">config:</span></span><br><span class="line">@echo <span class="string">&quot;Generating internal protobuf files...&quot;</span></span><br><span class="line">@test -n <span class="string">&quot;<span class="variable">$(INTERNAL_PROTO_FILES)</span>&quot;</span> || (echo <span class="string">&quot;No proto files found in <span class="variable">$(INTERNAL_DIR)</span>&quot;</span> &amp;&amp; exit 1)</span><br><span class="line">protoc --proto_path=<span class="variable">$(INTERNAL_DIR)</span> \</span><br><span class="line">       --proto_path=<span class="variable">$(THIRD_PARTY_DIR)</span> \</span><br><span class="line">       --go_out=paths=source_relative:<span class="variable">$(INTERNAL_DIR)</span> \</span><br><span class="line">       <span class="variable">$(INTERNAL_PROTO_FILES)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## API Proto 生成</span></span><br><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: api</span></span><br><span class="line"><span class="section">api:</span></span><br><span class="line">@echo <span class="string">&quot;Generating API protobuf files...&quot;</span></span><br><span class="line">@test -n <span class="string">&quot;<span class="variable">$(API_PROTO_FILES)</span>&quot;</span> || (echo <span class="string">&quot;No proto files found in <span class="variable">$(API_DIR)</span>&quot;</span> &amp;&amp; exit 1)</span><br><span class="line">protoc --proto_path=<span class="variable">$(API_DIR)</span> \</span><br><span class="line">       --proto_path=<span class="variable">$(THIRD_PARTY_DIR)</span> \</span><br><span class="line">       --go_out=paths=source_relative:<span class="variable">$(API_DIR)</span> \</span><br><span class="line">       --go-http_out=paths=source_relative:<span class="variable">$(API_DIR)</span> \</span><br><span class="line">       --go-grpc_out=paths=source_relative:<span class="variable">$(API_DIR)</span> \</span><br><span class="line">       --openapi_out=fq_schema_naming=true,default_response=false:<span class="variable">$(API_DIR)</span> \</span><br><span class="line">       <span class="variable">$(API_PROTO_FILES)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 构建应用</span></span><br><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: build</span></span><br><span class="line"><span class="section">build:</span></span><br><span class="line">@echo <span class="string">&quot;Building application...&quot;</span></span><br><span class="line">@mkdir -p <span class="variable">$(ROOT_DIR)</span>/bin/</span><br><span class="line">@cd <span class="variable">$(USER_DIR)</span> &amp;&amp; \</span><br><span class="line">go build -ldflags <span class="string">&quot;-X main.Version=<span class="variable">$(VERSION)</span>&quot;</span> -o <span class="variable">$(ROOT_DIR)</span>/bin/server ./cmd/user/...</span><br><span class="line">@echo <span class="string">&quot;Output binary: <span class="variable">$(ROOT_DIR)</span>/bin/server&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 代码生成</span></span><br><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: generate</span></span><br><span class="line"><span class="section">generate:</span></span><br><span class="line">@echo <span class="string">&quot;Generating code...&quot;</span></span><br><span class="line">@cd <span class="variable">$(USER_DIR)</span> &amp;&amp; \</span><br><span class="line">go generate ./... &amp;&amp; \</span><br><span class="line">go mod tidy</span><br><span class="line"></span><br><span class="line"><span class="comment">## 执行全部任务</span></span><br><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: all</span></span><br><span class="line"><span class="section">all: init api config build generate</span></span><br></pre></td></tr></table></figure><p>这里的 VERSION 暂时选择写死了，正常会通过 <code>VERSION := $(shell git describe --tags --always)</code> git 命令来获取最新的标签（tag）或提交哈希（commit hash）。</p><p>然后是 DockerFile 文件：</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 第一阶段：构建Go应用</span></span><br><span class="line"><span class="keyword">FROM</span> golang:<span class="number">1.23</span> as builder</span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /build</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制 go.mod 和 go.sum，提前拉依赖</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> go.mod go.sum ./</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> go mod download</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 复制 user 和 common 源码</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> user/ ./user/</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> common/ ./common/</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 进入 user 目录进行构建</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /build/user</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 编译</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> make init &amp;&amp; make generate &amp;&amp; make build</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 第二阶段：制作小体积运行环境</span></span><br><span class="line"><span class="keyword">FROM</span> debian:stable-slim</span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt-get update &amp;&amp; apt-get install -y --no-install-recommends \</span></span><br><span class="line"><span class="language-bash">    ca-certificates netbase &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    <span class="built_in">rm</span> -rf /var/lib/apt/lists/*</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 把可执行文件拷贝进来</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /build/bin/ /app/</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /build/user/configs/ /app/configs/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">8000</span> <span class="number">9000</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;/app/server&quot;</span>, <span class="string">&quot;-conf&quot;</span>, <span class="string">&quot;/app/configs/config.yaml&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>最后是 github action 文件：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Docker</span> <span class="string">Build</span> <span class="string">and</span> <span class="string">Push</span> <span class="string">for</span> <span class="string">Go</span> <span class="string">User</span> <span class="string">App</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">workflow_dispatch:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build-and-push:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="comment"># Step 1: Checkout the code</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">Code</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># Step 2: Set up Go environment + cache</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">up</span> <span class="string">Go</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-go@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">go-version:</span> <span class="string">&#x27;1.23&#x27;</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Cache</span> <span class="string">Go</span> <span class="string">Modules</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/cache@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            ~/.cache/go-build</span></span><br><span class="line"><span class="string">            ~/go/pkg/mod</span></span><br><span class="line"><span class="string"></span>          <span class="attr">key:</span> <span class="string">$&#123;&#123;</span> <span class="string">runner.os</span> <span class="string">&#125;&#125;-go-$&#123;&#123;</span> <span class="string">hashFiles(&#x27;**/go.sum&#x27;)</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">restore-keys:</span> <span class="string">|</span></span><br><span class="line"><span class="string">            $&#123;&#123; runner.os &#125;&#125;-go-</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="comment"># Step 3: Install protoc</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Protobuf</span> <span class="string">Compiler</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          sudo apt-get update</span></span><br><span class="line"><span class="string">          sudo apt-get install -y protobuf-compiler</span></span><br><span class="line"><span class="string">          protoc --version</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="comment"># Step 4: Docker Login</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Docker</span> <span class="string">Login</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/login-action@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">registry:</span> <span class="string">registry.cn-hangzhou.aliyuncs.com</span></span><br><span class="line">          <span class="attr">username:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_USERNAME</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="attr">password:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.DOCKER_PASSWORD</span> <span class="string">&#125;&#125;</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># Step 5: Build and Push Docker Image</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Build</span> <span class="string">and</span> <span class="string">Push</span> <span class="string">Docker</span> <span class="string">Image</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">docker/build-push-action@v5</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">context:</span> <span class="string">app/backend</span></span><br><span class="line">          <span class="attr">file:</span> <span class="string">app/backend/user/Dockerfile</span></span><br><span class="line">          <span class="attr">push:</span> <span class="literal">true</span></span><br><span class="line">          <span class="attr">tags:</span> <span class="string">|</span></span><br><span class="line">            <span class="string">registry.cn-hangzhou.aliyuncs.com/docker-learn-cooooing/user:latest</span></span><br></pre></td></tr></table></figure><p>其中 secrets.DOCKER_USERNAME 和 secrets.DOCKER_PASSWORD 是 GitHub Secrets，需要在项目设置中配置，用于登录阿里的镜像仓库。<br>目前配置的是手动触发，后续可修改为 push 或者 release 时触发。自动构建最新的镜像推送到阿里的镜像仓库。</p><p>最后回到 kubernetes ，通过 yaml 文件创建 deployment：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">user-service</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">user-service</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">user-service</span></span><br><span class="line">  <span class="attr">template:</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">labels:</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">user-service</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">imagePullSecrets:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">aliyun-registry-secret</span></span><br><span class="line">      <span class="attr">containers:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">user</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">registry.cn-hangzhou.aliyuncs.com/docker-learn-cooooing/user:latest</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">8000</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">9000</span></span><br><span class="line">          <span class="attr">volumeMounts:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span></span><br><span class="line">              <span class="attr">mountPath:</span> <span class="string">/data/conf</span></span><br><span class="line">          <span class="attr">resources:</span></span><br><span class="line">            <span class="attr">limits:</span></span><br><span class="line">              <span class="attr">cpu:</span> <span class="string">&quot;500m&quot;</span></span><br><span class="line">              <span class="attr">memory:</span> <span class="string">&quot;512Mi&quot;</span></span><br><span class="line">            <span class="attr">requests:</span></span><br><span class="line">              <span class="attr">cpu:</span> <span class="string">&quot;100m&quot;</span></span><br><span class="line">              <span class="attr">memory:</span> <span class="string">&quot;128Mi&quot;</span></span><br><span class="line"><span class="comment">#          readinessProbe: # 就绪探针，确保容器准备好才流量转发</span></span><br><span class="line"><span class="comment">#            httpGet:</span></span><br><span class="line"><span class="comment">#              path: /healthz</span></span><br><span class="line"><span class="comment">#              port: 8000</span></span><br><span class="line"><span class="comment">#            initialDelaySeconds: 5</span></span><br><span class="line"><span class="comment">#            periodSeconds: 10</span></span><br><span class="line"><span class="comment">#          livenessProbe: # 存活探针，崩了自动重启</span></span><br><span class="line"><span class="comment">#            httpGet:</span></span><br><span class="line"><span class="comment">#              path: /healthz</span></span><br><span class="line"><span class="comment">#              port: 8000</span></span><br><span class="line"><span class="comment">#            initialDelaySeconds: 15</span></span><br><span class="line"><span class="comment">#            periodSeconds: 20</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">config-volume</span></span><br><span class="line">          <span class="attr">emptyDir:</span> &#123; &#125; <span class="comment"># 这里可以替换成挂载 ConfigMap</span></span><br><span class="line"></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">user-service</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">user-service</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">http</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">8000</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8000</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">grpc</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">9000</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">9000</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">ClusterIP</span></span><br></pre></td></tr></table></figure><p>它会从阿里镜像仓库拉取镜像，并创建 pod 和 service。<br>其中关于探针的部分被注释掉了，<strong>探针（Probes）是一种用于检测容器健康状况的机制。探针允许Kubernetes定期检查容器是否仍在运行并且按预期工作。根据探针的检查结果，Kubernetes可以决定是否需要重启容器或者进行其他操作。</strong><br>探针需要服务实现一个HTTP端点，Kubernetes 将定期对这个端点发送GET请求。如果端点返回一个成功的状态码（通常是200-399范围内），Kubernetes就会认为容器是健康的。</p><p>这里 service 并没有配置 NodePort，所以目前服务只暴露在 Kubernetes 集群内部，外部访问需要通过 ingress 配置。</p><h2 id="配置-Helm"><a href="#配置-Helm" class="headerlink" title="配置 Helm"></a>配置 Helm</h2><p>Helm 是 Kubernetes 的包管理器。安装过程参考<a class="link"   href="https://helm.sh/zh/docs/" >Helm 文档<i class="fas fa-external-link-alt"></i></a>，这里省略。</p><p>Helm 默认会访问 localhost 的 8080 端口，但是这里使用 k3s，默认的 API Server 地址和端口通常是<code>https://&lt;K3S_SERVER_IP&gt;:6443</code>，所以访问 localhost 的 8080 端口会报错：<br><code>Error: INSTALLATION FAILED: Kubernetes cluster unreachable: Get &quot;http://localhost:8080/version&quot;: dial tcp [::1]:8080: connect: connection refused</code><br>所以需要一些设置：</p><p>Helm 默认会读取 <code>~/.kube/config</code> 文件，但 <strong>K3s 的 <code>kubeconfig</code> 默认存储在 <code>/etc/rancher/k3s/k3s.yaml</code></strong>，需要复制到本地并设置权限：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p ~/.kube</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> /etc/rancher/k3s/k3s.yaml ~/.kube/config</span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> <span class="variable">$USER</span> ~/.kube/config</span><br></pre></td></tr></table></figure><p>使用 <code>helm ls --all-namespaces</code> 测试 Helm 是否能访问 K3s：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ubuntu@master:~$ helm ls --all-namespaces</span><br><span class="line">NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                           APP VERSION</span><br><span class="line">traefik         kube-system     1               2025-04-25 08:22:17.95057216 +0000 UTC  deployed        traefik-34.2.1+up34.2.0         v3.3.2     </span><br><span class="line">traefik-crd     kube-system     1               2025-04-25 08:22:02.482297704 +0000 UTC deployed        traefik-crd-34.2.1+up34.2.0     v3.3.2     </span><br></pre></td></tr></table></figure><h2 id="创建-Ingress"><a href="#创建-Ingress" class="headerlink" title="创建 Ingress"></a>创建 Ingress</h2><p>使用 Kubernetes 的 Ingress 来创建一个统一的负载均衡器，从而实现当用户访问不同的域名时，访问后端不同的服务。<br><strong>Ingress 的功能其实很容易理解：所谓 Ingress 就是 Service 的“Service”，这就是它们两者的关系。</strong></p><p>在 Kubernetes（K8s）中，Ingress Controller 默认是 Traefik，它为云原生和动态环境设计，可以监听k8s的资源变化。<br>但这里还是使用 Nginx 作为流量入口管理器，虽然它比较偏静态。</p><p>首先卸载默认的 Traefik：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">helm uninstall traefik -n kube-system</span><br><span class="line">helm uninstall traefik-crd -n kube-system</span><br></pre></td></tr></table></figure><p>然后使用 Helm 安装 Nginx：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx</span><br><span class="line">helm repo update</span><br><span class="line">helm install nginx-ingress ingress-nginx/ingress-nginx -n kube-system</span><br></pre></td></tr></table></figure><p>或者使用 kubectl 安装：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.1/deploy/static/provider/baremetal/deploy.yaml</span><br></pre></td></tr></table></figure><p>使用以下<code>ingress.yaml</code>创建 Ingress 资源：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">apiVersion: networking.k8s.io/v1</span><br><span class="line">kind: Ingress</span><br><span class="line">metadata:</span><br><span class="line">  name: ingress</span><br><span class="line">  annotations:</span><br><span class="line">    nginx.ingress.kubernetes.io/rewrite-target: /$2 # 路径重写，去掉 /user</span><br><span class="line">    nginx.ingress.kubernetes.io/upstream-hash-by: &quot;$remote_addr&quot; # 负载均衡</span><br><span class="line">spec:</span><br><span class="line">  rules:</span><br><span class="line">    - host: &quot;&quot;  # 留空，表示使用 IP 地址</span><br><span class="line">      http:</span><br><span class="line">        paths:</span><br><span class="line">          - path: /user(/|$)(.*)</span><br><span class="line">            pathType: ImplementationSpecific</span><br><span class="line">            backend:</span><br><span class="line">              service:</span><br><span class="line">                name: user-service  # 目标服务名</span><br><span class="line">                port:</span><br><span class="line">                  number: 8000  # user-service 服务的 HTTP 端口</span><br></pre></td></tr></table></figure><p>使用 <code>kubectl apply -f ingress.yaml</code> 创建 Ingress 资源后，就可以通过 <code>http://master/user</code> 访问到后端服务了。</p><p>到这里基本应用的部署就完成了。</p>]]>
    </content>
    <id>https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Kubernetes%E6%9C%AC%E5%9C%B0%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E5%8F%8A%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2/</id>
    <link href="https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/Kubernetes%E6%9C%AC%E5%9C%B0%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA%E5%8F%8A%E5%BA%94%E7%94%A8%E9%83%A8%E7%BD%B2/"/>
    <published>2025-04-23T14:01:51.000Z</published>
    <summary>
      <![CDATA[<h2 id="基础环境准备（可选）"><a href="#基础环境准备（可选）" class="headerlink" title="基础环境准备（可选）"></a>基础环境准备（可选）</h2><h3 id="虚拟机配置"><a href="#虚拟机配置" class="he]]>
    </summary>
    <title>Kubernetes本地环境搭建及应用部署</title>
    <updated>2025-04-23T14:01:51.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>咕咕咕</name>
    </author>
    <category term="学习笔记" scheme="https://cooooing.github.io/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
    <category term="Go" scheme="https://cooooing.github.io/tags/Go/"/>
    <category term="算法" scheme="https://cooooing.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    <category term="缓存" scheme="https://cooooing.github.io/tags/%E7%BC%93%E5%AD%98/"/>
    <category term="LRU" scheme="https://cooooing.github.io/tags/LRU/"/>
    <category term="LFU" scheme="https://cooooing.github.io/tags/LFU/"/>
    <content>
      <![CDATA[<h2 id="缓存淘汰算法概述"><a href="#缓存淘汰算法概述" class="headerlink" title="缓存淘汰算法概述"></a>缓存淘汰算法概述</h2><p>缓存淘汰算法用于在缓存空间不足时决定哪些数据应该被移除，以腾出空间存储新数据。<br>两种最常用的算法是：</p><ul><li><strong>LRU (Least Recently Used)</strong> - 最近最少使用，根据数据的历史访问记录来进行淘汰数据</li><li><strong>LFU (Least Frequently Used)</strong> - 最不经常使用，根据数据的历史访问频率来淘汰数据</li></ul><h2 id="LRU-最近最少使用-算法"><a href="#LRU-最近最少使用-算法" class="headerlink" title="LRU (最近最少使用) 算法"></a>LRU (最近最少使用) 算法</h2><p>LRU 基于时间局部性原理，认为最近被访问的数据在将来更有可能被再次访问。当缓存满时，LRU 会淘汰最久未被访问的数据。</p><h3 id="Go-实现"><a href="#Go-实现" class="headerlink" title="Go 实现"></a>Go 实现</h3><ul><li>使用哈希表实现 O(1) 的查找</li><li>使用双向链表维护访问顺序</li><li>每次访问数据时，将其移动到链表头部</li><li>淘汰时从链表尾部移除数据</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> utils</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;container/list&quot;</span></span><br><span class="line"><span class="string">&quot;sync&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// LRUCache 泛型结构体</span></span><br><span class="line"><span class="keyword">type</span> LRUCache[K comparable, V any] <span class="keyword">struct</span> &#123;</span><br><span class="line">capacity <span class="type">int</span></span><br><span class="line">cache    <span class="keyword">map</span>[K]*list.Element</span><br><span class="line">list     *list.List</span><br><span class="line">mu       sync.RWMutex</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// entry 用于存储键值对</span></span><br><span class="line"><span class="keyword">type</span> entry[K comparable, V any] <span class="keyword">struct</span> &#123;</span><br><span class="line">key   K</span><br><span class="line">value V</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// NewLRUCache 创建一个新的泛型LRUCache</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewLRUCache</span>[<span class="title">K</span> <span class="title">comparable</span>, <span class="title">V</span> <span class="title">any</span>]<span class="params">(capacity <span class="type">int</span>)</span></span> *LRUCache[K, V] &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;LRUCache[K, V]&#123;</span><br><span class="line">capacity: capacity,</span><br><span class="line">cache:    <span class="built_in">make</span>(<span class="keyword">map</span>[K]*list.Element),</span><br><span class="line">list:     list.New(),</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get 获取键的值，如果不存在返回零值和false</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LRUCache[K, V])</span></span> Get(key K) (V, <span class="type">bool</span>) &#123;</span><br><span class="line">l.mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> l.mu.Unlock()</span><br><span class="line"><span class="keyword">if</span> elem, ok := l.cache[key]; ok &#123;</span><br><span class="line">l.list.MoveToFront(elem)</span><br><span class="line"><span class="keyword">return</span> elem.Value.(*entry[K, V]).value, <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> zero V</span><br><span class="line"><span class="keyword">return</span> zero, <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Put 插入或更新键值对</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LRUCache[K, V])</span></span> Put(key K, value V) <span class="type">bool</span> &#123;</span><br><span class="line">l.mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> l.mu.Unlock()</span><br><span class="line"><span class="keyword">if</span> l.capacity &lt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> elem, ok := l.cache[key]; ok &#123;</span><br><span class="line">elem.Value.(*entry[K, V]).value = value</span><br><span class="line">l.list.MoveToFront(elem)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> l.list.Len() &gt;= l.capacity &#123;</span><br><span class="line"><span class="comment">// 移除最久未使用的元素</span></span><br><span class="line">back := l.list.Back()</span><br><span class="line"><span class="keyword">if</span> back != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">delete</span>(l.cache, back.Value.(*entry[K, V]).key)</span><br><span class="line">l.list.Remove(back)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">newEntry := &amp;entry[K, V]&#123;key, value&#125;</span><br><span class="line">elem := l.list.PushFront(newEntry)</span><br><span class="line">l.cache[key] = elem</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Len 返回缓存中元素的数量</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LRUCache[K, V])</span></span> Len() <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> l.list.Len()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> utils</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_EmptyCache</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLRUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试空缓存</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;nonexistent&quot;</span>); ok || val != <span class="number">0</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get from empty cache should return zero value, got %v, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> l := cache.Len(); l != <span class="number">0</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Len of empty cache should be 0, got %d&quot;</span>, l)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_SingleItem</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLRUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试单个元素</span></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试替换</span></span><br><span class="line">cache.Put(<span class="string">&quot;two&quot;</span>, <span class="number">2</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) should be evicted, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;two&quot;</span>); !ok || val != <span class="number">2</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;two&#x27;) = %d, %v, want 2, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_EvictionPolicy</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLRUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始填充</span></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line">cache.Put(<span class="string">&quot;two&quot;</span>, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问one使其成为最近使用的</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加新元素，two应该被淘汰</span></span><br><span class="line">cache.Put(<span class="string">&quot;three&quot;</span>, <span class="number">3</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;two&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;two&#x27;) should be evicted, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;three&quot;</span>); !ok || val != <span class="number">3</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;three&#x27;) = %d, %v, want 3, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_UpdateExisting</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLRUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">11</span>) <span class="comment">// 更新</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">11</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 11, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> l := cache.Len(); l != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Len should be 1 after update, got %d&quot;</span>, l)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_ZeroCapacity</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLRUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get from zero-capacity cache should return nothing, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_CustomTypes</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">type</span> customKey <span class="keyword">struct</span> &#123;</span><br><span class="line">id   <span class="type">int</span></span><br><span class="line">name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">cache := NewLRUCache[customKey, <span class="type">string</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">key1 := customKey&#123;<span class="number">1</span>, <span class="string">&quot;apple&quot;</span>&#125;</span><br><span class="line">key2 := customKey&#123;<span class="number">2</span>, <span class="string">&quot;banana&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">cache.Put(key1, <span class="string">&quot;red&quot;</span>)</span><br><span class="line">cache.Put(key2, <span class="string">&quot;yellow&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(key1); !ok || val != <span class="string">&quot;red&quot;</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(key1) = %s, %v, want &#x27;red&#x27;, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试淘汰</span></span><br><span class="line">cache.Put(customKey&#123;<span class="number">3</span>, <span class="string">&quot;cherry&quot;</span>&#125;, <span class="string">&quot;pink&quot;</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(key2); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(key2) should be evicted, got %s, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLRUCache_ConcurrentAccess</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLRUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">100</span>)</span><br><span class="line">done := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">bool</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 并发写入</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++ &#123;</span><br><span class="line">cache.Put(<span class="string">&quot;key&quot;</span>, i)</span><br><span class="line">&#125;</span><br><span class="line">done &lt;- <span class="literal">true</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 并发读取</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++ &#123;</span><br><span class="line">cache.Get(<span class="string">&quot;key&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">done &lt;- <span class="literal">true</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line">&lt;-done</span><br><span class="line">&lt;-done</span><br><span class="line"></span><br><span class="line"><span class="comment">// 最终检查</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;key&quot;</span>); !ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Key should exist after concurrent access&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> val &lt; <span class="number">0</span> || val &gt;= <span class="number">10000</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Unexpected value after concurrent access: %d&quot;</span>, val)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="LFU-最不经常使用-算法"><a href="#LFU-最不经常使用-算法" class="headerlink" title="LFU (最不经常使用) 算法"></a>LFU (最不经常使用) 算法</h2><p>LFU 基于访问频率，认为访问次数最少的数据在未来被访问的可能性也最小。当缓存满时，LFU 会淘汰访问频率最低的数据。如果有多个数据具有相同的最低频率，则淘汰其中最久未被访问的数据。</p><h3 id="Go-实现-1"><a href="#Go-实现-1" class="headerlink" title="Go 实现"></a>Go 实现</h3><ul><li>使用两个哈希表：一个存储键值对，一个存储频率到键列表的映射</li><li>使用双向链表维护相同频率下的访问顺序</li><li>需要维护一个最小频率变量</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> utils</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;container/list&quot;</span></span><br><span class="line"><span class="string">&quot;sync&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// LFUCache 泛型结构体</span></span><br><span class="line"><span class="keyword">type</span> LFUCache[K comparable, V any] <span class="keyword">struct</span> &#123;</span><br><span class="line">capacity <span class="type">int</span></span><br><span class="line">minFreq  <span class="type">int</span></span><br><span class="line">items    <span class="keyword">map</span>[K]*list.Element     <span class="comment">// 存储键到元素的映射</span></span><br><span class="line">freqs    <span class="keyword">map</span>[<span class="type">int</span>]*list.List      <span class="comment">// 存储频率到双向链表的映射</span></span><br><span class="line">entries  <span class="keyword">map</span>[K]*cacheEntry[K, V] <span class="comment">// 存储键到entry的映射(辅助快速访问)</span></span><br><span class="line">mu       sync.RWMutex</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// cacheEntry 存储缓存值和频率信息</span></span><br><span class="line"><span class="keyword">type</span> cacheEntry[K comparable, V any] <span class="keyword">struct</span> &#123;</span><br><span class="line">key    K</span><br><span class="line">value  V</span><br><span class="line">freq   <span class="type">int</span></span><br><span class="line">parent *list.Element</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// NewLFUCache 创建一个新的泛型LFUCache</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewLFUCache</span>[<span class="title">K</span> <span class="title">comparable</span>, <span class="title">V</span> <span class="title">any</span>]<span class="params">(capacity <span class="type">int</span>)</span></span> *LFUCache[K, V] &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;LFUCache[K, V]&#123;</span><br><span class="line">capacity: capacity,</span><br><span class="line">items:    <span class="built_in">make</span>(<span class="keyword">map</span>[K]*list.Element),</span><br><span class="line">freqs:    <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">int</span>]*list.List),</span><br><span class="line">entries:  <span class="built_in">make</span>(<span class="keyword">map</span>[K]*cacheEntry[K, V]),</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Get 获取键的值，如果不存在返回零值和false</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LFUCache[K, V])</span></span> Get(key K) (V, <span class="type">bool</span>) &#123;</span><br><span class="line">l.mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> l.mu.Unlock()</span><br><span class="line"><span class="keyword">if</span> elem, ok := l.items[key]; ok &#123;</span><br><span class="line">entry := elem.Value.(*cacheEntry[K, V])</span><br><span class="line">l.incrementFreq(elem, entry)</span><br><span class="line"><span class="keyword">return</span> entry.value, <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> zero V</span><br><span class="line"><span class="keyword">return</span> zero, <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Put 插入或更新键值对</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LFUCache[K, V])</span></span> Put(key K, value V) <span class="type">bool</span> &#123;</span><br><span class="line">l.mu.Lock()</span><br><span class="line"><span class="keyword">defer</span> l.mu.Unlock()</span><br><span class="line"><span class="keyword">if</span> l.capacity &lt;= <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果键已存在，更新值并增加频率</span></span><br><span class="line"><span class="keyword">if</span> elem, ok := l.items[key]; ok &#123;</span><br><span class="line">entry := elem.Value.(*cacheEntry[K, V])</span><br><span class="line">entry.value = value</span><br><span class="line">l.incrementFreq(elem, entry)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果缓存已满，移除最不经常使用且最久未使用的项</span></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(l.items) &gt;= l.capacity &#123;</span><br><span class="line">l.removeMinFreqItem()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建新条目</span></span><br><span class="line">entry := &amp;cacheEntry[K, V]&#123;</span><br><span class="line">key:   key,</span><br><span class="line">value: value,</span><br><span class="line">freq:  <span class="number">1</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加到频率为1的列表中</span></span><br><span class="line"><span class="keyword">if</span> l.freqs[<span class="number">1</span>] == <span class="literal">nil</span> &#123;</span><br><span class="line">l.freqs[<span class="number">1</span>] = list.New()</span><br><span class="line">&#125;</span><br><span class="line">elem := l.freqs[<span class="number">1</span>].PushFront(entry)</span><br><span class="line">entry.parent = elem</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新映射</span></span><br><span class="line">l.items[key] = elem</span><br><span class="line">l.entries[key] = entry</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新最小频率</span></span><br><span class="line">l.minFreq = <span class="number">1</span></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// incrementFreq 增加条目的频率</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LFUCache[K, V])</span></span> incrementFreq(elem *list.Element, entry *cacheEntry[K, V]) &#123;</span><br><span class="line"><span class="comment">// 从当前频率列表中移除</span></span><br><span class="line">l.freqs[entry.freq].Remove(elem)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新频率</span></span><br><span class="line">entry.freq++</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加到新频率列表中</span></span><br><span class="line"><span class="keyword">if</span> l.freqs[entry.freq] == <span class="literal">nil</span> &#123;</span><br><span class="line">l.freqs[entry.freq] = list.New()</span><br><span class="line">&#125;</span><br><span class="line">newElem := l.freqs[entry.freq].PushFront(entry)</span><br><span class="line">entry.parent = newElem</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新items映射</span></span><br><span class="line">l.items[entry.key] = newElem</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果旧频率是最小频率且该频率列表现在为空，更新最小频率</span></span><br><span class="line"><span class="keyword">if</span> entry.freq<span class="number">-1</span> == l.minFreq &amp;&amp; l.freqs[entry.freq<span class="number">-1</span>].Len() == <span class="number">0</span> &#123;</span><br><span class="line">l.minFreq = entry.freq</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// removeMinFreqItem 移除最小频率的项</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LFUCache[K, V])</span></span> removeMinFreqItem() &#123;</span><br><span class="line">minList := l.freqs[l.minFreq]</span><br><span class="line"><span class="keyword">if</span> minList == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除列表中的最后一个元素(最久未使用)</span></span><br><span class="line">back := minList.Back()</span><br><span class="line"><span class="keyword">if</span> back == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">entry := back.Value.(*cacheEntry[K, V])</span><br><span class="line">minList.Remove(back)</span><br><span class="line"><span class="built_in">delete</span>(l.items, entry.key)</span><br><span class="line"><span class="built_in">delete</span>(l.entries, entry.key)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Len 返回缓存中元素的数量</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(l *LFUCache[K, V])</span></span> Len() <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">len</span>(l.items)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="测试-1"><a href="#测试-1" class="headerlink" title="测试"></a>测试</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> utils</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;testing&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_EmptyCache</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试空缓存</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;nonexistent&quot;</span>); ok || val != <span class="number">0</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get from empty cache should return zero value, got %v, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> l := cache.Len(); l != <span class="number">0</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Len of empty cache should be 0, got %d&quot;</span>, l)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_SingleItem</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试单个元素</span></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 测试替换</span></span><br><span class="line">cache.Put(<span class="string">&quot;two&quot;</span>, <span class="number">2</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) should be evicted, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;two&quot;</span>); !ok || val != <span class="number">2</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;two&#x27;) = %d, %v, want 2, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_EvictionPolicy</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始填充</span></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line">cache.Put(<span class="string">&quot;two&quot;</span>, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问one增加其频率</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加新元素，two应该被淘汰(频率较低)</span></span><br><span class="line">cache.Put(<span class="string">&quot;three&quot;</span>, <span class="number">3</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;two&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;two&#x27;) should be evicted, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;three&quot;</span>); !ok || val != <span class="number">3</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;three&#x27;) = %d, %v, want 3, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 再访问three使其频率与one相同</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;three&quot;</span>); !ok || val != <span class="number">3</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;three&#x27;) = %d, %v, want 3, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加新元素，one应该被淘汰(相同频率但更久未使用)</span></span><br><span class="line">cache.Put(<span class="string">&quot;four&quot;</span>, <span class="number">4</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) should be evicted, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;three&quot;</span>); !ok || val != <span class="number">3</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;three&#x27;) = %d, %v, want 3, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;four&quot;</span>); !ok || val != <span class="number">4</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;four&#x27;) = %d, %v, want 4, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_UpdateExisting</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">11</span>) <span class="comment">// 更新</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">11</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 11, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> l := cache.Len(); l != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Len should be 1 after update, got %d&quot;</span>, l)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_ZeroCapacity</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get from zero-capacity cache should return nothing, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_MinFrequencyUpdate</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始填充</span></span><br><span class="line">cache.Put(<span class="string">&quot;one&quot;</span>, <span class="number">1</span>)</span><br><span class="line">cache.Put(<span class="string">&quot;two&quot;</span>, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问one增加其频率</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); !ok || val != <span class="number">1</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) = %d, %v, want 1, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加新元素，two应该被淘汰(频率较低)</span></span><br><span class="line">cache.Put(<span class="string">&quot;three&quot;</span>, <span class="number">3</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在minFreq应该是1(three的频率)</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;three&quot;</span>); !ok || val != <span class="number">3</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;three&#x27;) = %d, %v, want 3, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 再次访问three使其频率增加到2</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;three&quot;</span>); !ok || val != <span class="number">3</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;three&#x27;) = %d, %v, want 3, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加新元素，应该淘汰one(因为three频率更高)</span></span><br><span class="line">cache.Put(<span class="string">&quot;four&quot;</span>, <span class="number">4</span>)</span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;one&quot;</span>); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(&#x27;one&#x27;) should be evicted, got %d, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_CustomTypes</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line"><span class="keyword">type</span> customKey <span class="keyword">struct</span> &#123;</span><br><span class="line">id   <span class="type">int</span></span><br><span class="line">name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">cache := NewLFUCache[customKey, <span class="type">string</span>](<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">key1 := customKey&#123;<span class="number">1</span>, <span class="string">&quot;apple&quot;</span>&#125;</span><br><span class="line">key2 := customKey&#123;<span class="number">2</span>, <span class="string">&quot;banana&quot;</span>&#125;</span><br><span class="line"></span><br><span class="line">cache.Put(key1, <span class="string">&quot;red&quot;</span>)</span><br><span class="line">cache.Put(key2, <span class="string">&quot;yellow&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问key1增加其频率</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(key1); !ok || val != <span class="string">&quot;red&quot;</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(key1) = %s, %v, want &#x27;red&#x27;, true&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加新元素，key2应该被淘汰(频率较低)</span></span><br><span class="line">cache.Put(customKey&#123;<span class="number">3</span>, <span class="string">&quot;cherry&quot;</span>&#125;, <span class="string">&quot;pink&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(key2); ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Get(key2) should be evicted, got %s, %v&quot;</span>, val, ok)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">TestLFUCache_ConcurrentAccess</span><span class="params">(t *testing.T)</span></span> &#123;</span><br><span class="line">cache := NewLFUCache[<span class="type">string</span>, <span class="type">int</span>](<span class="number">100</span>)</span><br><span class="line">done := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">bool</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 并发写入</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++ &#123;</span><br><span class="line">cache.Put(<span class="string">&quot;key&quot;</span>, i)</span><br><span class="line">&#125;</span><br><span class="line">done &lt;- <span class="literal">true</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 并发读取</span></span><br><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++ &#123;</span><br><span class="line">cache.Get(<span class="string">&quot;key&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">done &lt;- <span class="literal">true</span></span><br><span class="line">&#125;()</span><br><span class="line"></span><br><span class="line">&lt;-done</span><br><span class="line">&lt;-done</span><br><span class="line"></span><br><span class="line"><span class="comment">// 最终检查</span></span><br><span class="line"><span class="keyword">if</span> val, ok := cache.Get(<span class="string">&quot;key&quot;</span>); !ok &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Key should exist after concurrent access&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> val &lt; <span class="number">0</span> || val &gt;= <span class="number">10000</span> &#123;</span><br><span class="line">t.Errorf(<span class="string">&quot;Unexpected value after concurrent access: %d&quot;</span>, val)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="LRU-和-LFU-的比较"><a href="#LRU-和-LFU-的比较" class="headerlink" title="LRU 和 LFU 的比较"></a>LRU 和 LFU 的比较</h2><table><thead><tr><th>特性</th><th>LRU</th><th>LFU</th></tr></thead><tbody><tr><td>淘汰策略</td><td>淘汰最久未使用的</td><td>淘汰使用频率最低的</td></tr><tr><td>实现复杂度</td><td>相对简单</td><td>相对复杂</td></tr><tr><td>适用场景</td><td>访问模式随时间变化不大</td><td>访问频率差异明显的场景</td></tr><tr><td>对突发流量</td><td>可能淘汰热点数据</td><td>对新数据不友好</td></tr><tr><td>内存消耗</td><td>较低</td><td>较高</td></tr></tbody></table><ul><li><strong>LRU</strong> 实现简单，适合大多数通用场景，对突发流量友好。</li><li><strong>LFU</strong> 适合访问模式相对稳定的场景，能更好地保留高频访问数据。</li></ul><p>另外，还有 LRU 和 LFU 的变体和混合算法以及FIFO（First in First out），先进先出，最先进入的数据，最先被淘汰等算法，以不同的淘汰策略适应不同的应用场景。</p>]]>
    </content>
    <id>https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E7%BC%93%E5%AD%98%E6%B7%98%E6%B1%B0%E7%AE%97%E6%B3%95LRU%E3%80%81LFU/</id>
    <link href="https://cooooing.github.io/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E7%BC%93%E5%AD%98%E6%B7%98%E6%B1%B0%E7%AE%97%E6%B3%95LRU%E3%80%81LFU/"/>
    <published>2025-03-31T03:50:57.000Z</published>
    <summary>
      <![CDATA[<h2 id="缓存淘汰算法概述"><a href="#缓存淘汰算法概述" class="headerlink" title="缓存淘汰算法概述"></a>缓存淘汰算法概述</h2><p>缓存淘汰算法用于在缓存空间不足时决定哪些数据应该被移除，以腾出空间存储新数据。<br>两种最]]>
    </summary>
    <title>缓存淘汰算法LRU、LFU</title>
    <updated>2025-03-31T03:50:57.000Z</updated>
  </entry>
</feed>
