<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hacker Cube</title>
  
  
  <link href="https://phoenix500526.github.io/atom.xml" rel="self"/>
  
  <link href="https://phoenix500526.github.io/"/>
  <updated>2026-05-27T05:02:59.525Z</updated>
  <id>https://phoenix500526.github.io/</id>
  
  <author>
    <name>Phoenix</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>C++并发编程之内存模型</title>
    <link href="https://phoenix500526.github.io/2020/12/29/C-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/"/>
    <id>https://phoenix500526.github.io/2020/12/29/C-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E4%B9%8B%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/</id>
    <published>2020-12-29T21:22:41.000Z</published>
    <updated>2026-05-27T05:02:59.525Z</updated>
    
    <content type="html"><![CDATA[<h3 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h3><p>本文旨在对 C++11 中引入的内存模型，特别是内存序的部分进行一些深入的探讨，主要包含了以下内容</p><blockquote><ul><li>关于 C++11 的内存模型<ul><li>内存模型的重要性</li><li>内存模型是什么</li></ul></li><li>C++ 内存模型之内存布局</li><li>C++ 内存模型之内存序<ul><li>操作间关系<ul><li>sequenced-before 关系</li><li>synchronizes-with 关系</li><li>happens-before 关系</li></ul></li><li>修改顺序和 Visible side-effects</li><li>C++ 内存序模型<ul><li>Relaxed Ordering</li><li>Release-Acquire Ordering</li><li>Sequential Consistent Ordering</li><li>总结</li></ul></li></ul></li><li>内存栅栏(Memory Fence)<ul><li>Acquire Fence</li><li>Release Fence</li><li>Full Fence</li></ul></li><li>Reference</li></ul></blockquote><span id="more"></span><h3 id="关于-C-11-的内存模型"><a href="#关于-C-11-的内存模型" class="headerlink" title="关于 C++11 的内存模型"></a>关于 C++11 的内存模型</h3><h5 id="内存模型的重要性"><a href="#内存模型的重要性" class="headerlink" title="内存模型的重要性"></a>内存模型的重要性</h5><p>纵观计算机的发展史，多线程概念的提出要远慢于操作系统和编译器的实现。在多线程提出以前，编译器和操作系统认为程序当中仅有一个执行流，很多编译器以及处理器的优化都建立在这个假设之上。而在多线程程序大行其道的今天，这种假设已经不再成立。编译器以及处理器的优化可能会对多线程程序的运行造成影响，主要体现在以下几方面：</p><blockquote><ul><li>因编译器的优化而导致的指令重排：</li><li>CPU 的乱序执行</li><li>多核 CPU 的 Cache 不一致问题<br>一些编译器，如 gcc 在编译代码时可以通过 <code>-O</code> 参数来指定优化级别，而其中就包含了指令重排的优化。例如 gcc 可以将程序中彼此没有依赖关系的访问内存操作放在一起，并为其生成相应的汇编代码。</li></ul></blockquote><p>对于 CPU 而言，存在乱序执行的一个直观例子就是计算机组成原理中，在讲解流水线时所提到的数据冒险。</p><p>而对于多核系统而言，往往多个核心之间配备有不同的 L1 Cache 和 L2 Cache，如图示：</p><div style="text-align:center"> <img src="/img/memory-model/multi-core-cache.svg"/></div><div class="image-caption" align="center">多核 CPU 结构</div><p>如果 cache 采用写回法的缓存策略，每次只在将 cache 块替换出去时才将 cache 块中的数据写回内存。那么当 core1 和 core2 访问并修改相同数据时，就会面临数据不一致的问题。</p><p>由于存在上述的优化操作，开发人员编写的多线程程序在最终运行时往往表现出和他们一开始预期的不同，这就导致了 bug 的产生。为了解决上述问题，C++ 11 引入了新的内存模型。<strong>事实上，有研究<sup>[0]</sup>表明，只有在语言层面上提出内存模型，才可以使得编译器对多线程程序进行优化变成可能</strong>。</p><p>除了对于 C++11 中引入的标准线程库具有重大意义以外，对 C++ 内存模型，特别是其中的内存序的了解，也有助于让开发者在程序中引入 Lock-Free 策略，更好地提高多线程程序的并发性<sup>[1]</sup>。</p><h5 id="内存模型是什么？"><a href="#内存模型是什么？" class="headerlink" title="内存模型是什么？"></a>内存模型是什么？</h5><p>C++11 中的内存模型<strong>本质上是一套行为规范</strong>，它包括了两个层面的内容，一个是结构层面的内容 —— 内存布局，描述了在语言视角下数据是如何存放于内存当中，另一个是并发层面的内容 —— 内存序， 描述<strong>一个线程对某个共享对象的修改何时能够被其他线程看到</strong>。而编译器在对 C++ 11 下的多线程程序进行编译及优化时，必须遵守这一套行为规范，特别是其中关于内存序的要求。</p><h3 id="C-内存模型之内存布局"><a href="#C-内存模型之内存布局" class="headerlink" title="C++ 内存模型之内存布局"></a>C++ 内存模型之内存布局</h3><p>在 C++ 中，所有的数据都是对象，而所谓的对象就是一片有属性的内存区域，其中属性包括了对象的类型以及生命周期能。不论对象的类型是什么，一个对象都必然占据一个或多个内存位置(Memory Location)。</p><p>每一个内存位置上的必定是以下两种情况之一：</p><blockquote><ul><li>标量类型，包括数字类型、指针类型、枚举类型以及 <code>nullptr_t</code></li><li>相邻位域的最大序列</li></ul></blockquote><p>例如以下代码：</p><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">my_data</span>&#123;</span><br><span class="line"><span class="type">int</span> var_1;</span><br><span class="line">    <span class="type">double</span> var_2;</span><br><span class="line">    <span class="type">unsigned</span> var_3:<span class="number">10</span>;</span><br><span class="line">    <span class="type">int</span> var_4:<span class="number">25</span>;</span><br><span class="line">    <span class="type">int</span>:<span class="number">0</span>;</span><br><span class="line">    <span class="type">int</span> var_6;</span><br><span class="line">    <span class="type">char</span>* var_7;</span><br><span class="line">    std::string var_8;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><div style="text-align:center"> <img src="/img/memory-model/memory-layout.svg"/></div><div class="image-caption" align="center">Memory Layout</div><h3 id="C-内存模型之内存序"><a href="#C-内存模型之内存序" class="headerlink" title="C++ 内存模型之内存序"></a>C++ 内存模型之内存序</h3><h5 id="操作间的关系"><a href="#操作间的关系" class="headerlink" title="操作间的关系"></a>操作间的关系</h5><h6 id="sequenced-before-关系"><a href="#sequenced-before-关系" class="headerlink" title="sequenced-before 关系"></a>sequenced-before 关系</h6><p>sequenced-before 是一种非对称，可传递的成对关系，它描述了在同一线程下，表达值求值的顺序。有以下几种可能：</p><ul><li>A sequenced-before B：A 的求值将会在 B 的求值开始之前完成</li><li>A 与 B 之间不存在任何方向的 sequenced-before 关系：<ul><li>A 与 B 的求值之间不仅是无序的，且彼此之间可以重叠(CPU 交替执行 A 与 B 的求值指令)。</li><li>A 与 B 的求值之间是无序的，但彼此之间不可重叠。</li></ul></li></ul><p>通常而言，同一线程下的顺序语句在语义上总是符合 sequenced-before 关系的。例如 A 语句位于 B 语句之前，那么语义上满足 A sequenced-before B，但由于编译器的优化，实际执行中可能是 B 语句先执行。例如：</p><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="comment">// 语义上符合 ① sequenced-before ②</span></span><br><span class="line"><span class="comment">// 但实际上，最终执行结果可能是先执行 ②; 再执行 ①</span></span><br><span class="line"><span class="type">int</span> a = <span class="number">10</span>;<span class="comment">// ①</span></span><br><span class="line"><span class="type">int</span> b = <span class="number">100</span>;<span class="comment">// ②</span></span><br><span class="line"><span class="comment">// 语义上符合 ③ sequenced-before ④，且由于存在依赖关系，因此 ③ 会在 ④ 之前执行</span></span><br><span class="line"><span class="type">int</span> var = <span class="number">1024</span>;<span class="comment">// ③</span></span><br><span class="line">++var;<span class="comment">// ④</span></span><br></pre></td></tr></table></figure><h6 id="synchronizes-with-关系"><a href="#synchronizes-with-关系" class="headerlink" title="synchronizes-with 关系"></a>synchronizes-with 关系</h6><p>synchronizes-with 描述的是一种线程间的同步关系，<strong>如果 A synchronizes-with B， 那么可以确认 A 状态在 B 执行之前一定是可见的</strong>。像原子操作的 acquire-release、mutex 的 lock 与 unlock，thread 的 create 与 join 之间都可以构成 synchronizes-with 关系。使用 synchronizes-with 可以用来构成 inter-threads happen-before 关系</p><h6 id="happens-before-关系"><a href="#happens-before-关系" class="headerlink" title="happens-before 关系"></a>happens-before 关系</h6><p>A、B 是两个在多核 CPU 上执行的操作。<strong>如果 A happens-before B，那么 A 所产生的内存变化会在 B 执行之前被看到（visible）</strong>。happens-before 主要包含了两种情况：</p><ul><li>如果 A 和 B 位于同一线程内，当 A sequenced-before B 时，可以说 A happens-before B</li><li>如果 A 和 B 位于不同的线程内，当 A inter-threads happens-before B 时，可以说 A happens-before B</li></ul><p>inter-threads happens-before 关系，顾名思义，就是发生在线程间的 happens-before 关系。它包含但又不限于前面所提到的 synchronizes-with 关系。例如</p><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><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="comment">// write thread</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">writer_thread</span><span class="params">()</span></span>&#123;</span><br><span class="line"> data.<span class="built_in">push_back</span>(<span class="number">42</span>); <span class="comment">// ①</span></span><br><span class="line"> data_ready=<span class="literal">true</span>; <span class="comment">// ②</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// read thread</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">read_thread</span><span class="params">()</span></span>&#123;</span><br><span class="line"> <span class="keyword">while</span>(!data_ready.<span class="built_in">load</span>())<span class="comment">// ③</span></span><br><span class="line"> std::this_thread::<span class="built_in">sleep</span>(std::<span class="built_in">milliseconds</span>(<span class="number">1</span>));</span><br><span class="line"> std::cout&lt;&lt;”The answer=”&lt;&lt;data[<span class="number">0</span>]&lt;&lt;”\n”; <span class="comment">//④</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中，① sequenced-before ②、③ sequenced-before ④、② synchronizes-with ④，因此我们有 ① happens-before ④， ② happens-before ④。</p><h5 id="修改顺序-和-Visible-side-effects"><a href="#修改顺序-和-Visible-side-effects" class="headerlink" title="修改顺序 和 Visible side-effects"></a>修改顺序 和 Visible side-effects</h5><p>C++ 标准中对 side-effects 的定义如下：</p><blockquote><p>[intro.execution]:</p><p>Reading an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.</p></blockquote><p><strong>简单地说，所谓的 side-effect 就是指当执行完一个对表达式的求值操作后，导致了被操作对象状态的改变</strong>。例如 执行完 <code>int var = 10; int i = var++;</code>后，表达式 <code>var++</code>的结果是 10，但 var 的状态发生了改变(从10 变成11)，那么 <code>var++</code>就产生了一个 side-effect.</p><p>在 C++ 中对于某个特定的原子变量的所有访问操作都存在一个修改序列。对于程序的每次运行而言，这一修改序列可能不同，但针对具体的某一次运行，都必然有一个确定的顺序。而<strong>所谓的修改顺序的一致性，就是指所有访问该原子变量的线程都只会看到同一个修改序列</strong>。换句话讲，<strong>一旦这个原子变量的状态发生改变，那么这个改变后的状态应当同步给所有的线程，这样就避免了在访问同个原子变量的多个线程之间出现数据不一致的问题</strong>。</p><h5 id="C-内存序模型"><a href="#C-内存序模型" class="headerlink" title="C++ 内存序模型"></a>C++ 内存序模型</h5><p>内存序的定义：</p><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">typedef</span> <span class="keyword">enum</span> <span class="title class_">memory_order</span> &#123;</span><br><span class="line">    memory_order_relaxed,</span><br><span class="line">    memory_order_consume,</span><br><span class="line">    memory_order_acquire,</span><br><span class="line">    memory_order_release,</span><br><span class="line">    memory_order_acq_rel,</span><br><span class="line">    memory_order_seq_cst</span><br><span class="line">&#125; memory_order;</span><br></pre></td></tr></table></figure><p>注意：由于 C++ 17 中重新修订了 release-consume ordering 的规范，C++ 并不提倡使用 <code>memory_order_consume</code>，因此在接下来的讨论中，我们将不会涉及 <code>memory_order_consume</code>的讨论。</p><p>C++ 中提供了三种不同的内存序，按照约束条件强弱划分，由弱到强可分为：Relaxed Ordering、Release-Acquire Ordering 以及 Sequential Consistent Ordering。它们和 C++ 中的 memory_order 之间的关系如下：</p><div style="text-align:center"> <img src="/img/memory-model/memory-order-mapping.svg" width=800 height=400/></div><div class="image-caption" align="center">原子操作、内存序模型与memory_order</div><p>为了更清晰的说明问题，我将 memory_order 和相关的内存序模型放在一起讨论。</p><h6 id="Relaxed-Ordering"><a href="#Relaxed-Ordering" class="headerlink" title="Relaxed Ordering"></a>Relaxed Ordering</h6><p>Relaxed Ordering: 这一模型是 C++ 内存序模型中约束条件最宽松的。对于指定了 <code>memory_order_relaxed</code>的原子操作，C++ 语言做出了以下两个承诺(注意黑体字)：</p><blockquote><ul><li>操作是原子性的</li><li>对于<strong>某个特定的原子变量</strong>，保证其在<strong>线程内的</strong>修改一致性，对于<strong>跨线程的</strong>修改一致性不做任何保证。</li></ul></blockquote><p>我们可以看一下下面这段代码：</p><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><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="meta">#<span class="keyword">include</span> <span class="string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;thread&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;assert.h&gt;</span></span></span><br><span class="line">std::atomic&lt;<span class="type">bool</span>&gt; x,y;</span><br><span class="line">std::atomic&lt;<span class="type">int</span>&gt; z;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">write_x_then_y</span><span class="params">()</span></span>&#123;</span><br><span class="line">x.<span class="built_in">store</span>(<span class="literal">true</span>,std::memory_order_relaxed);<span class="comment">// ①</span></span><br><span class="line">y.<span class="built_in">store</span>(<span class="literal">true</span>,std::memory_order_relaxed); <span class="comment">// ②</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">read_y_then_x</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">while</span>(!y.<span class="built_in">load</span>(std::memory_order_relaxed)); <span class="comment">// ③</span></span><br><span class="line"><span class="keyword">if</span>(x.<span class="built_in">load</span>(std::memory_order_relaxed)) <span class="comment">// ④</span></span><br><span class="line">++z;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">x=<span class="literal">false</span>;</span><br><span class="line">y=<span class="literal">false</span>;</span><br><span class="line">z=<span class="number">0</span>;</span><br><span class="line"><span class="function">std::thread <span class="title">a</span><span class="params">(write_x_then_y)</span></span>;</span><br><span class="line"><span class="function">std::thread <span class="title">b</span><span class="params">(read_y_then_x)</span></span>;</span><br><span class="line">a.<span class="built_in">join</span>();</span><br><span class="line">b.<span class="built_in">join</span>();</span><br><span class="line"><span class="built_in">assert</span>(z.<span class="built_in">load</span>()!=<span class="number">0</span>); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在下面这段代码中，<code>assert(z.load()!=0);</code> 是有可能会被触发的。这是因为对于线程 a 而言，交换 ① 和 ② 并不违背 Relaxed Ordering 所作出的承诺：① 和 ② 分别操作的是 x 和 y 两个原子变量，且 x 和 y 各自的修改序列中就只包含了一个操作，因此<strong>C++ 允许编译器或 CPU 对 ① 或 ② 进行指令重排</strong>。一旦 ① 和 ② 的指令顺序对调，且线程 a 与 b 的执行序列变成 ② =&gt; ③ =&gt; ④ =&gt; ①，那么 <code>assert(z.load()!=0);</code> 就会发生。</p><h6 id="Release-Acquire-Ordering"><a href="#Release-Acquire-Ordering" class="headerlink" title="Release-Acquire Ordering"></a>Release-Acquire Ordering</h6><p>在讨论 Release-Acquire ordering 之前，我们需要先知道以下两个概念以及一个例子：</p><blockquote><ul><li><p>Acquire Operation: 被标记为 memory_order_acquire 或 memory_order_acq_rel 的 load 操作，或者 mutex 的 lock 操作，相当于读操作</p></li><li><p>Release Operation: 被标记为 memory_order_release 或 memory_order_acq_rel 的 store 操作，或者 mutex 的 unlock 操作，相当于写操作</p></li></ul></blockquote><p>假设现有两个线程 A 和 B，以及一个全局的原子变量 var，如果 A 先对 var 执行了 release 操作(store操作)，而 B 后对 var 执行了 acquire 操作(load操作)，那么当 B 执行完 load 操作后，它将能够看到 A 在 store 操作之前所执行的所有写操作(不管是对非原子变量还是其他的原子变量)，如下图：</p><div style="text-align:center"> <img src="/img/memory-model/acquire-release-demo.svg" width=800 height=400/></div><div class="image-caption" align="center">Acquire-Release Ordering Demo</div><p>从上面的例子可以看出，Release-Acquire Ordering 为不同线程中对同一原子变量的一对 acquire 操作和 release 操作之间引入了 synchronizes-with 关系，它做出如下承诺：</p><blockquote><ul><li>任何发生在 release 操作之前的读写操作都不会被重排到 release 操作之后</li><li>任何发生在 acquire 操作之后的读写操作都不会被重排到 acquire 操作之前</li></ul></blockquote><p>另外，这层关系<strong>只存在于</strong>执行 acquire 操作和 release 操作的两个线程 A 和 B 之间。换言之，如果有其他线程(比如 C)正在运行，那么 Release-Acquire Ordering 并不对 C 所看到的修改序列做任何保证。换句话讲，C 看到的 A 对变量的修改序列既有可能和 B 看到 A 的修改序列相同，也有可能不同，语言不做保证，具体的情况由编译器或 CPU 的优化结果决定。</p><p>对于前面 Relaxed Ordering 中的例子而言，我们只需要指定 y 的 load 和 store 操作序列为 Release_Acquire Ordering 就可以确保<code>assert(z.load()!=0);</code>不会被触发。</p><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><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">write_x_then_y</span><span class="params">()</span></span>&#123;</span><br><span class="line">x.<span class="built_in">store</span>(<span class="literal">true</span>,std::memory_order_relaxed);<span class="comment">// ①</span></span><br><span class="line">y.<span class="built_in">store</span>(<span class="literal">true</span>,std::memory_order_release); <span class="comment">// ②</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">read_y_then_x</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">while</span>(!y.<span class="built_in">load</span>(std::memory_order_acquire)); <span class="comment">// ③</span></span><br><span class="line"><span class="keyword">if</span>(x.<span class="built_in">load</span>(std::memory_order_relaxed)) <span class="comment">// ④</span></span><br><span class="line">++z;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，在一些重视指令执行序列(strongly-ordered)的硬件平台，如 x86 等，大多数操作都是符合 Release-Acquire Ordering 的，编译器在编译程序时无需生成额外的指令来保证 Release-Acquire Ordering。而在一些弱序性(weakly-ordered)的硬件平台，如 ARM 上，则需要通过内存栅栏(memory fence)来实现。</p><p>总的来说，<strong>Release-Acquire Ordering 相当于提供了两条”基线”，位于 release 线之前的相关指令不能越过这条线到这条线之后去执行，而位于 acquire 线之后的相关指令则不能越过 acquire 线跑到前面去执行</strong>。另外，由于 std::mutex 本身的 lock 与 unlock 也属于 acquire 和 release 操作，因此也符合 Release-Acquire Ordering，其中临界区的概念就是被 acquire 线和 release 线所包围的那片区域。</p><h6 id="Sequential-Consistent-Ordering"><a href="#Sequential-Consistent-Ordering" class="headerlink" title="Sequential Consistent Ordering"></a>Sequential Consistent Ordering</h6><p><strong>Sequential Consistent Ordering 是C++ 所提供的内存序中约束性最强的一种，它将 Release-Acquire Ordering 中，对同一原子变量的修改序列从只在执行 release 操作的线程和执行 acquire 操作的线程之间保持同步，推广到了在全局所有线程中保持同步。</strong>换句话讲，套用 Release-Acquire Ordering 的例子，在使用 Sequential Consistent Ordering 的情况下，C 线程看到的 A 线程中对变量的修改序列和 B 线程看到的 A 线程中对变量的修改序列是完全一致的。</p><p>任何指定为 memory_order_seq_cst 或采用默认内存序的原子操作都采用此内存序。</p><p>我们可以将前面的例子扩展到多个线程，如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;atomic&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;thread&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;assert.h&gt;</span></span></span><br><span class="line">std::atomic&lt;<span class="type">bool</span>&gt; x,y;</span><br><span class="line">std::atomic&lt;<span class="type">int</span>&gt; z;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">write_x</span><span class="params">()</span></span>&#123;</span><br><span class="line">    x.<span class="built_in">store</span>(<span class="literal">true</span>, std::memory_order_seq_cst);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">write_y</span><span class="params">()</span></span>&#123;</span><br><span class="line">    y.<span class="built_in">store</span>(<span class="literal">true</span>, std::memory_order_seq_cst);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">read_x_then_y</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (!x.<span class="built_in">load</span>(std::memory_order_seq_cst));</span><br><span class="line">    <span class="keyword">if</span> (y.<span class="built_in">load</span>(std::memory_order_seq_cst)) &#123;</span><br><span class="line">        ++z;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">read_y_then_x</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (!y.<span class="built_in">load</span>(std::memory_order_seq_cst));</span><br><span class="line">    <span class="keyword">if</span> (x.<span class="built_in">load</span>(std::memory_order_seq_cst)) &#123;</span><br><span class="line">        ++z;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    x=<span class="literal">false</span>;</span><br><span class="line">y=<span class="literal">false</span>;</span><br><span class="line">z=<span class="number">0</span>;</span><br><span class="line">    <span class="function">std::thread <span class="title">a</span><span class="params">(write_x)</span></span>;</span><br><span class="line">    <span class="function">std::thread <span class="title">b</span><span class="params">(write_y)</span></span>;</span><br><span class="line">    <span class="function">std::thread <span class="title">c</span><span class="params">(read_x_then_y)</span></span>;</span><br><span class="line">    <span class="function">std::thread <span class="title">d</span><span class="params">(read_y_then_x)</span></span>;</span><br><span class="line">    a.<span class="built_in">join</span>(); b.<span class="built_in">join</span>(); c.<span class="built_in">join</span>(); d.<span class="built_in">join</span>();</span><br><span class="line">    <span class="built_in">assert</span>(z.<span class="built_in">load</span>() != <span class="number">0</span>);  <span class="comment">// will never happen</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，只有指定内存序为 Sequential Consistent Ordering 才能确保 <code>assert(z.load() != 0)</code>不会被触发。如果指定操作的内存序为 Release-Acquire Ordering，那么 c 和 d 就有可能会看到关于 x 和 y 的不同的修改序列。</p><p>在所有多核硬件系统平台上，Sequential Consistent Ordering 都需要使用 memory fence 指令来实现，因此它会将特定内存位置的访问同步到所有的核中，这可能会造成性能瓶颈。</p><p>最后，关于 Sequential Consistent Ordering 有两个值得仔细分辨的点：</p><blockquote><ul><li>全局一致的修改顺序强调的是在程序的每次运行下，所有线程都能看到某个原子变量的统一的修改顺序。但是不同次运行中，同一个变量的修改顺序不一定是相同的。例如程序第一次运行所有线程所看到的某一个变量的修改顺序和程序第二次运行所有时所有线程看到的修改顺序不同。</li><li>在很多情况下，对于同一个线程下<strong>不同</strong>原子变量的 memory_order_seq_cst 操作是可以被重排序的</li></ul></blockquote><h6 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h6><p>C++ 并发编程困难不仅在于开发人员需要捋清不同线程之间复杂的协作关系，还需要<strong>在一定程度上对抗编译器和 CPU 所做出的优化</strong>。C++ 提供三种不同的内存序，旨在能够让开发者根据自身需要自行选择，以<strong>最低限度</strong>约束编译器及 CPU 的优化。</p><p>从上述三个内存序来看，<strong>Relaxed Ordering 只保证了在同线程内的单个原子变量的修改一致性，而 Release-Acquire Ordering 则通过一对 release 和 acquire 操作，将这种修改一致性扩展到了相关的两个线程当中，而 Sequential Consistent Ordering 则将这种修改一致性扩展到了全局的所有线程当中</strong>。</p><h3 id="内存栅栏-Memory-Fence"><a href="#内存栅栏-Memory-Fence" class="headerlink" title="内存栅栏(Memory Fence)"></a>内存栅栏(Memory Fence)</h3><p>在 C++ 中，除了使用原子操作指定内存序以外，还可以通过内存栅栏来指定内存序。根据指定都能内存序的不同，fence 可以分成以下三类：</p><blockquote><ul><li>acquire fence：指定内存序为 memory_order_acquire 的 std::atomic_thread_fence</li><li>release fene：指定内存序为 memory_order_release 的 std::atomic_thread_fence</li><li>full fence：指定内存序为 memory_order_seq_cst 或者 memory_order_acq_rel 的 std::atomic_thread_fence</li></ul></blockquote><p>对于编译器及处理器的乱序重排，一共可以分为四种情况：</p><blockquote><ul><li>Load-Load(读后读)</li><li>Load-Store(读后写)</li><li>Store-Load(写后读)</li><li>Store-Store(写后写)</li></ul></blockquote><p>指令重排的结果可以用下面这张图来表示(其中文字代表操作类型，箭头表示指令重排的移动方向)：</p><div style="text-align:center"> <img src="/img/memory-model/instruction-reordering.svg"/></div><div class="image-caption" align="center">Instruction Reordering</div><p>如果理解了前面的 Release-Acquire Ordering 中关于 acquire 基线和 release 基线的描述，就可以很容易了理解接下来的内容了。</p><h5 id="Acquire-Fence"><a href="#Acquire-Fence" class="headerlink" title="Acquire Fence"></a>Acquire Fence</h5><p>如同前面所提到的 acquire 基线那样， Acquire Fence 可以防止 Fence 后的读写操作重排到 Fence 前的<strong>任意</strong> load 操作之前，即抑制 Load-Load 重排和 Load-Store 重排，如下图：</p><div style="text-align:center"> <img src="/img/memory-model/acquire-fence.svg"/></div><div class="image-caption" align="center">Acquire Fence</div><h5 id="Release-Fence"><a href="#Release-Fence" class="headerlink" title="Release Fence"></a>Release Fence</h5><p>如同前面所提到的 release 基线那样， 防止 Fence 前的读写操作重排到 Fence 后的<strong>任意</strong> store 操作之后，即抑制 Load-Store 重排和 Store-Store 重排，如下图：</p><div style="text-align:center"> <img src="/img/memory-model/release-fence.svg"/></div><div class="image-caption" align="center">Release Fence</div><p>注意：Acquire Fence 和 Release Fence 在同步关系上的约束性其实要比 Release-Acquire Ordering 中相应的”acquire 基线” 和 “release 基线”要强，我想可能是因为在 Release-Acquire Ordering 中 “acquire 基线”本身就是一个 acquire 操作，”release 基线”本身也是一个 release 操作。而 Acquire Fence 和 Release Fence 严格意义上并不算操作。</p><h5 id="Full-Fence"><a href="#Full-Fence" class="headerlink" title="Full Fence"></a>Full Fence</h5><p>Full Fence 就相当于是前面 Acquire Fence 和 Release Fence 功能的并集，它能够抑制 Load-Load、Load-Store 以及 Store-Store 重排，但是它不能抑制 Store-Load 重排。</p><div style="text-align:center"> <img src="/img/memory-model/full-fence.svg"/></div><div class="image-caption" align="center">Full Fence</div><p>回到前面 Relaxed Ordering 的例子中，我们也可以通过 Release Fence 来防止语句 ① 和语句 ② 之间的重排序，例如：</p><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><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="function"><span class="type">void</span> <span class="title">write_x_then_y</span><span class="params">()</span></span>&#123;</span><br><span class="line">x.<span class="built_in">store</span>(<span class="literal">true</span>,std::memory_order_relaxed);<span class="comment">// ①</span></span><br><span class="line">    std::<span class="built_in">atomic_thread_fence</span>(std::memory_order_release);</span><br><span class="line">y.<span class="built_in">store</span>(<span class="literal">true</span>,std::memory_order_relaxed); <span class="comment">// ②</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">read_y_then_x</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">while</span>(!y.<span class="built_in">load</span>(std::memory_order_relaxed)); <span class="comment">// ③</span></span><br><span class="line">    std::<span class="built_in">atomic_thread_fence</span>(std::memory_order_acquire);</span><br><span class="line"><span class="keyword">if</span>(x.<span class="built_in">load</span>(std::memory_order_relaxed)) <span class="comment">// ④</span></span><br><span class="line">++z;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h3><p>[0]. <a href="https://www.hpl.hp.com/techreports/2004/HPL-2004-209.pdf">《Threads Cannot be Implemented as a Library》—— Hans-J. Boehm</a></p><p>[1]. <a href="https://preshing.com/20120612/an-introduction-to-lock-free-programming/">An Introduction to Lock-Free Programming</a></p><p>[2]. <a href="https://www.amazon.com/-/zh/dp/1933988770/ref=sr_1_2?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&crid=EHHD3PRN1G0Y&dchild=1&keywords=c+++concurrency+in+action&qid=1609319303&sprefix=C+++Concurrency+,aps,365&sr=8-2">《C++ Concurrency In Action》 —— Anthony Williams</a></p><p>[3]. <a href="https://cseweb.ucsd.edu/classes/fa13/cse160-a/Lectures/Lec07.pdf">《C++11 Momery Model》 —— Scott B. Baden</a></p><p>[4]. <a href="https://en.cppreference.com/w/cpp/atomic/memory_order">cppreference.com:memory-order</a></p><p>[5]. <a href="https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence">cppreference.com:std::atomic_thread_fence</a></p>]]></content>
    
    
    <summary type="html">&lt;h3 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h3&gt;&lt;p&gt;本文旨在对 C++11 中引入的内存模型，特别是内存序的部分进行一些深入的探讨，主要包含了以下内容&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;关于 C++11 的内存模型&lt;ul&gt;
&lt;li&gt;内存模型的重要性&lt;/li&gt;
&lt;li&gt;内存模型是什么&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;C++ 内存模型之内存布局&lt;/li&gt;
&lt;li&gt;C++ 内存模型之内存序&lt;ul&gt;
&lt;li&gt;操作间关系&lt;ul&gt;
&lt;li&gt;sequenced-before 关系&lt;/li&gt;
&lt;li&gt;synchronizes-with 关系&lt;/li&gt;
&lt;li&gt;happens-before 关系&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;修改顺序和 Visible side-effects&lt;/li&gt;
&lt;li&gt;C++ 内存序模型&lt;ul&gt;
&lt;li&gt;Relaxed Ordering&lt;/li&gt;
&lt;li&gt;Release-Acquire Ordering&lt;/li&gt;
&lt;li&gt;Sequential Consistent Ordering&lt;/li&gt;
&lt;li&gt;总结&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;内存栅栏(Memory Fence)&lt;ul&gt;
&lt;li&gt;Acquire Fence&lt;/li&gt;
&lt;li&gt;Release Fence&lt;/li&gt;
&lt;li&gt;Full Fence&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reference&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="C++沉思录" scheme="https://phoenix500526.github.io/categories/C-%E6%B2%89%E6%80%9D%E5%BD%95/"/>
    
    
    <category term="C++ 并发编程" scheme="https://phoenix500526.github.io/tags/C-%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
    
    <category term="C++ 内存模型" scheme="https://phoenix500526.github.io/tags/C-%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>关于 C++ 智能指针的那些事</title>
    <link href="https://phoenix500526.github.io/2020/12/20/%E5%85%B3%E4%BA%8E-C-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/"/>
    <id>https://phoenix500526.github.io/2020/12/20/%E5%85%B3%E4%BA%8E-C-%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/</id>
    <published>2020-12-20T09:29:47.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文并不是一篇关于Smart Pointer的基础文章，它假定阅读读者至少使用过 C++11 下的 Smart Pointer，因此文章中并不涉及 Smart Pointer 的基础用法，而是希望讨论一些更深层次的东西，主要包含了以下内容</p><blockquote><ul><li>引用型 Smart Pointer 的内存布局以及相关推论</li><li>make_shared 的优劣</li><li>enable_shared_from_this 与 shared_from_this 的实现</li><li>weak_ptr 的内存布局及相关应用</li><li>unique_ptr 与 PImpl Idiom 的相关问题</li><li>Reference</li></ul></blockquote><span id="more"></span><h4 id="引用型-Smart-Pointer-的内存布局"><a href="#引用型-Smart-Pointer-的内存布局" class="headerlink" title="引用型 Smart Pointer 的内存布局"></a>引用型 Smart Pointer 的内存布局</h4><p>在 C++11 中，<code>shared_ptr</code>的大小为裸指针的两倍，它包含两个数据成员：一个指向共享资源的指针和一个指向控制块的指针。控制块中存放了引用计数、用户自定义删除器的副本以及分配器的副本(后两个要求用户显式指出)，而结构如下：</p><div style="text-align:center">    <img src="/img/smart-pointers/shared-ptr-layout.svg"></div><div class="image-caption" align="center">shared_ptr 的内存布局</div><p>从 shared_ptr 的内存布局可以看出，我们可以得出以下三个结论和两个问题：</p><p><strong>结论一：用户定义的 deleter 和 allocator 与 shared_ptr 的类型无关</strong></p><p>用户自定义的 deleter 和 allocator 并不属于 shared_ptr 类型的一部分。换言之，不论用户定义的 deleter 是函数指针、functor 抑或是 lambda，都不影响 shared_ptr 的最终大小。其次，对于指向同一类型对象，但是定义了不同 deleter 的 <code>shared_ptr</code>而言，它们之间没有类型差异，可以共存于同一个容器当中，例如下面的代码是可以通过编译的：</p><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></pre></td><td class="code"><pre><span class="line"><span class="function">shared_ptr&lt;Object&gt; <span class="title">p1</span><span class="params">(<span class="keyword">new</span> Object, customDeleter1)</span></span>;</span><br><span class="line"><span class="function">shared_ptr&lt;Object&gt; <span class="title">p2</span><span class="params">(<span class="keyword">new</span> Object, customDeleter2)</span></span>;</span><br><span class="line"><span class="function">shared_ptr&lt;Object&gt; <span class="title">p3</span><span class="params">(<span class="keyword">new</span> Object)</span></span>;</span><br><span class="line">std::vector&lt;shared_ptr&lt;Object&gt;&gt; vec&#123;p1, p2, p3&#125;;</span><br></pre></td></tr></table></figure><p><strong>结论二：对 Object 的读写无法是原子的</strong></p><p>对于 shared_ptr 而言，对对象的读写操作分成了两个部分，一个是对 <code>Object</code> 的访问，另一个则是对 <code>reference count</code>的更新操作。<strong>对于后者而言，在主流平台上的实现都是原子操作，而且没有用到锁机制，性能不俗</strong>。但是由于读写 <code>Object</code> 和更新 <code>reference count</code> 是两个不同的操作，<code>shared_ptr</code> 没有提供额外的机制来保证这两个操作的原子性，因此不论<code>Object</code>的读写是不是线程安全的，<code>shared_ptr</code>本身也不是 100% 线程安全的。因此在多线程环境下读写同一个<code>shared_ptr</code>还是需要使用 <code>mutex</code> 进行保护的。</p><p><strong>结论三：从原则上，Object 与 Control Block 在空间上不必是相邻，这在内存分配时可能会带来潜在的效率及安全问题</strong></p><p>从内存布局上可以看出, <code>shared_ptr</code>中的<code>Object</code>和<code>Control Block</code>在空间上不必是相邻的，这会带来一些运行效率上的问题，例如<code>auto ptr = shared_ptr&lt;Object&gt;(new Object)</code>可能会涉及两次内存分配，带来一些额外的开销，也可能会带来安全性的问题，例如 <code>new Object</code> 执行成功了，但是因为某种原因导致<code>new</code>出来的指针没有及时存入到<code>shared_ptr</code>中，造成了内存的泄漏。关于这个问题的解决方案，我会在后面的 <code>make_shared</code>  部分来讨论。</p><p><strong>问题一：Control Block 中的 weak count 和 reference count 有什么区别？</strong></p><p>在 C++ 中，析构与释放内存并不是同义词。对于<code>new</code>和<code>delete</code>而言，前者的工作是先开辟一片内存空间，然后在该内存空间上执行构造函数，构造完毕后将句柄返回给调用者，而后者的工作则是先在指定的内存空间上执行析构函数，完成一些清理善后工作，之后将这片内存空间归还给操作系统。由此可以看出，析构和内存释放其实是两个过程。回到这个问题上，**<code>Control Block</code>中的<code>reference count</code>决定了<code>Object</code>会在何时被析构，而<code>weak count</code>决定了<code>Control Block</code>所占用的内存区域何时被释放**。</p><p><strong>问题二：什么样的行为会导致 Control Block 的创建？</strong></p><p>对于指向同一个<code>Object</code>的多个<code>shared_ptr</code>而言，维护它们的<code>Control Block</code>的唯一性至关重要。以下的三种构造方式会触发<code>Control Block</code>的创建：</p><blockquote><ul><li>使用裸指针来构造<code>shared_ptr</code></li><li>使用<code>unique_ptr</code>来构造<code>shared_ptr</code></li><li>使用<code>make_shared</code>来构造<code>shared_ptr</code></li></ul></blockquote><p>因此，使用同一个裸指针反复创建多个<code>shared_ptr</code>不仅不能使<code>shared_ptr</code>发挥它们应有的功能，还可能会带来安全问题：只要其中一个<code>shared_ptr</code>被析构，那么对剩下的任何一个<code>shared_ptr</code>执行解引用操作都会导致未定义行为</p><p>除了上述的三种行为外，使用<code>weak_ptr</code>来构造<code>shared_ptr</code>、或者通过拷贝构造、拷贝赋值、移动构造、移动赋值的方式构造<code>shared_ptr</code>也不会导致<code>Control Block</code>的创建。其中，<strong>出于效率的考虑，使用移动赋值和移动构造也不会更新<code>reference count</code>信息</strong>。</p><h4 id="make-shared-的优劣"><a href="#make-shared-的优劣" class="headerlink" title="make_shared 的优劣"></a>make_shared 的优劣</h4><p>针对前面结论三中所提到的效率及安全问题，我们先上一小段代码来进一步说明问题：</p><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><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="function"><span class="type">void</span> <span class="title">Process</span><span class="params">(std::shared_ptr&lt;Object&gt; spw, <span class="type">int</span> priority)</span></span>&#123;</span><br><span class="line">    <span class="comment">//... do something;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">computePriority</span><span class="params">(<span class="type">const</span> string&amp; key)</span></span>&#123;</span><br><span class="line">    <span class="type">int</span> priority = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// ... calculate priority by key</span></span><br><span class="line">    <span class="keyword">return</span> priority;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line"> <span class="comment">//...   </span></span><br><span class="line">    <span class="built_in">Process</span>(std::<span class="built_in">shared_ptr</span>&lt;Object&gt;(<span class="keyword">new</span> Object), <span class="built_in">computerPriority</span>(<span class="string">&quot;Window&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于上述代码，当执行语句<code>Process(std::shared_ptr&lt;Object&gt;(new Object), computerPriority(&quot;Window&quot;));</code>,需要先进行参数的计算，但参数计算顺序是不确定的。换句话讲，语句<code>Process(std::shared_ptr&lt;Object&gt;(new Object), computePriority(&quot;Window&quot;));</code>的执行顺序可能是：</p><blockquote><ol><li>执行 <code>new Object</code>，在堆内存上构造出 Object 对象</li><li>执行 computePriority(“Window”) 函数</li><li>将 <code>new Object</code>所得到的指针包装成 <code>shared_ptr</code>对象</li><li>将参数传递给 <code>Process</code>执行</li></ol></blockquote><p>在上述过程中，如果<code>computePriority</code>抛出了异常，那么就会导致步骤 1 中 <code>new</code> 出来的指针无法及时存放到<code>shared_ptr</code>中，从而无法自动执行析构，造成了内存的泄漏。</p><p>对于上述问题，一个最简单的方式便是将<code>new Object</code>从<code>Process</code>当中分离出来，如：</p><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="comment">// 此处省略了 Process 与 computePriority 的代码</span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">    <span class="type">int</span> priority = <span class="built_in">computerPriority</span>(<span class="string">&quot;Window&quot;</span>);</span><br><span class="line">    <span class="keyword">auto</span> ptr = <span class="built_in">shared_ptr</span>&lt;Object&gt;(<span class="keyword">new</span> Object);</span><br><span class="line">    <span class="built_in">Process</span>(std::<span class="built_in">move</span>(ptr), priority);<span class="comment">//使用移动构造以避免更新 ptr 的 reference count</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式解决了前面所提到的内存泄漏的问题，不过依然为<code>Object</code> 和 <code>Control Block</code> 分别分配了内存空间。一种更好的方式是使用 C++ 标准库提供的 <code>make_shared</code>来实现，如下：</p><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line"><span class="built_in">Process</span>(std::<span class="built_in">make_shared</span>&lt;Object&gt;(), <span class="built_in">computePriority</span>(<span class="string">&quot;Window&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>相较于<code>shared_ptr&lt;Object&gt;(new Object)</code>的方式，<code>make_shared&lt;Object&gt;()</code>会一次性分配好<code>Object</code>和<code>Control Block</code>的内存空间，并在该内存空间上构造<code>Object</code>的对象。这样既解决了前面可能因函数求参顺序不确定而导致的内存泄漏的问题，同时也提高了运行的效率，减少了一次内存的分配。除了安全和效率两个优点外，<code>make_shared</code>还有助于提高程序的可维护性，一方面使用<code>make_shared</code>来构造<code>shared_ptr</code>可以避免产生任何形式的裸指针，也就降低了用户因误用裸指针而导致的<code>shared_ptr</code>出现<code>Control Block</code>信息不一致的可能性，另一方面结合 <code>auto</code>关键字也可以使得<code>shared_ptr</code>与特定类型名称的耦合程度尽可能低，代码改起来工作量也会少一些。</p><p>当然，<code>make_shared</code>也不是银弹，也有其自身的局限性。首先，<strong>使用<code>make_shared</code>构造<code>shared_ptr</code>无法指定 deleter 和 allocator</strong>。其次，将<code>Object</code>和<code>Control Block</code>的内存空间合并虽然提高了效率，但可能会导致这片内存区域迟迟无法被释放。正如前面<code>weak count</code>与<code>reference count</code>的作用时所提到的，<strong>当<code>Object</code> 和<code>Control Block</code>不属于同一片内存空间时，一旦<code>reference count</code>降为零，则会析构<code>Object</code>并回收其所占用的内存；当<code>weak count</code>降为零时，则会析构并回收<code>Control Block</code>所占用的内存。若<code>Object</code>和<code>Control Block</code>属于同一片内存空间时，则当<code>reference count</code>降为零时，会析构<code>Object</code>对象，但只有当<code>reference count</code>和<code>weak count</code>都为零时，才会回收这片内存空间</strong>。因此，如果当对象占用内存较大，且指向该对象<code>weak_ptr</code>的生命周期要远长于指向该对象的<code>shared_ptr</code>时，使用<code>make_shared</code>就不那么合理了。</p><h4 id="enable-shared-from-this-和-shared-from-this-的实现"><a href="#enable-shared-from-this-和-shared-from-this-的实现" class="headerlink" title="enable_shared_from_this 和 shared_from_this 的实现"></a>enable_shared_from_this 和 shared_from_this 的实现</h4><p>使用<code>make_shared</code>可以替代<code>new</code>表达式来构造<code>shared_ptr</code>对象，进而降低指向同一对象的<code>shared_ptr</code>出现<code>Control Block</code>不一致的问题。但这种作法并非万无一失，因为最常被用来构造<code>shared_ptr</code>对象的指针除了<code>new</code>句柄，也包括了<code>this</code>指针，例如：</p><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">std::vector&lt;shared_ptr&lt;Object&gt;&gt; vec;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Object</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Process</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        vec.<span class="built_in">emplace_back</span>(<span class="keyword">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>而解决方法是先继承标准库中的模板基类<code>enable_shared_from_this&lt;T&gt;</code>，并使用<code>shared_from_this</code>代替<code>this</code>指针，具体如下：</p><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">class</span> <span class="title class_">Object</span> : <span class="keyword">public</span> enable_shared_from_this&lt;Object&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">Process</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">        vec.<span class="built_in">emplace_back</span>(<span class="built_in">shared_from_this</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在上述代码中，<code>Object</code>继承了一个由<code>Object</code>所实例化的模板基类<code>enable_shared_from_this&lt;Object&gt;</code>。这种做法在 CRTP Idiom 中会经常使用到。CRTP 可以通过静态绑定的方法提升继承多态的性能。<code>enable_shared_from_this</code>的一个可能实现如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">class</span> <span class="title class_">D</span>&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">enable_shared_from_this</span> &#123;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">    <span class="function"><span class="keyword">constexpr</span> <span class="title">enable_shared_from_this</span><span class="params">()</span> </span>&#123; &#125;</span><br><span class="line">    <span class="built_in">enable_shared_from_this</span>(enable_shared_from_this <span class="type">const</span>&amp;) &#123; &#125;</span><br><span class="line">    enable_shared_from_this&amp; <span class="keyword">operator</span>=(enable_shared_from_this <span class="type">const</span>&amp;) &#123;</span><br><span class="line">        <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function">shared_ptr&lt;T&gt; <span class="title">shared_from_this</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">shared_ptr</span>&lt;T&gt;(self_); &#125;</span><br><span class="line">    <span class="function">shared_ptr&lt;T <span class="type">const</span>&gt; <span class="title">shared_from_this</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">shared_ptr</span>&lt;T <span class="type">const</span>&gt;(self_); &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    weak_ptr&lt;D&gt; self_;</span><br><span class="line">    <span class="keyword">friend</span> shared_ptr&lt;D&gt;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">shared_ptr&lt;T&gt;::<span class="built_in">shared_ptr</span>(T* ptr) &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="comment">// Code that creates control block goes here.</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// <span class="doctag">NOTE:</span> This if check is pseudo-code. Won&#x27;t compile. There&#x27;s a few</span></span><br><span class="line">    <span class="comment">// issues not being taken in to account that would make this example</span></span><br><span class="line">    <span class="comment">// rather noisy.</span></span><br><span class="line">    <span class="keyword">if</span> (is_base_of&lt;enable_shared_from_this&lt;T&gt;, T&gt;::value) &#123;</span><br><span class="line">        enable_shared_from_this&lt;T&gt;&amp; base = *ptr;</span><br><span class="line">        base.self_ = *<span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 <code>enable_shared_from_this</code>中，包含了一个类型为 <code>weak_ptr&lt;D&gt;</code>的指针<code>self_</code>。当调用<code>shared_from_this</code>函数时，会利用<code>self_</code>来构造<code>shared_ptr</code>，这种做法并不会触发<code>shared_ptr</code>创建<code>Control Block</code>的行为。不过，这种做法要求当前对象在调用<code>shared_from_this()</code>之前必须具备<code>Control Block</code>，否则将触发未定义行为，通常会抛出<code>bad_weak_ptr</code>异常。换言之，使用<code>shared_from_this</code>的对象最好也由<code>shared_ptr</code>管理，否则可能会引发异常。例子可见下面代码：</p><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><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="keyword">class</span> <span class="title class_">Object</span>: <span class="keyword">public</span> enable_shared_from_this&lt;Object&gt;&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Object</span>(<span class="type">const</span> string&amp; str):<span class="built_in">name</span>(str)&#123;&#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">auto</span> ptr = <span class="built_in">shared_from_this</span>();</span><br><span class="line">        cout &lt;&lt; ptr-&gt;name &lt;&lt; endl;</span><br><span class="line">    &#125;    </span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">const</span> string name;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="comment">//Object* ptr = new Object(&quot;Hello&quot;);//使用 new 句柄直接调用 test 函数将会引发 bad_weak_ptr 异常</span></span><br><span class="line">    <span class="keyword">auto</span> ptr = <span class="built_in">make_shared</span>&lt;Object&gt;(<span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">    ptr-&gt;<span class="built_in">test</span>();</span><br><span class="line">    <span class="comment">//delete ptr;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="weak-ptr-的内存布局及相关应用"><a href="#weak-ptr-的内存布局及相关应用" class="headerlink" title="weak_ptr 的内存布局及相关应用"></a>weak_ptr 的内存布局及相关应用</h4><p><code>weak_ptr</code>通常作为<code>shared_ptr</code>的补充，它并不会改变<code>shared_ptr</code>的引用计数。它的大小和<code>shared_ptr</code>相同，且和对应的<code>shared_ptr</code>指向同一个控制块，不过与 shared_ptr 不同的是，<code>weak_ptr</code> 不能直接执行解引用操作。<code>weak_ptr</code>的常用操作主要有以下三个：</p><p><strong>作用一: 缓存优化</strong></p><p>通常对于访问数据库或是读取文件等耗时的操作，一般都会使用缓存的方式来提高效率。<strong>如果使用<code>shared_ptr</code>来管理数据库连接或是文件句柄，则在缓存时要额外注意资源对象的生命周期问题</strong>，而<code>weak_ptr</code>为我们提供了一套解决方案，例如：</p><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><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="function">std::unique_ptr&lt;<span class="type">const</span> Object&gt; <span class="title">loadObject</span><span class="params">(ObjectID id)</span></span>;</span><br><span class="line"><span class="function">std::shared_ptr&lt;<span class="type">const</span> Object&gt; <span class="title">fastLoadObject</span><span class="params">(ObjectID id)</span></span>&#123;</span><br><span class="line">    <span class="type">static</span> std::unordered_map&lt;ObjectID, std::weak_ptr&lt;<span class="type">const</span> Object&gt;&gt; cache;</span><br><span class="line">    <span class="keyword">auto</span> objPtr = cache[id].<span class="built_in">lock</span>(); </span><br><span class="line">    <span class="comment">// to cached object (or null if object&#x27;s not in cache)     </span></span><br><span class="line">    <span class="keyword">if</span> (!objPtr) &#123; <span class="comment">// if not in cache,         </span></span><br><span class="line">        objPtr = <span class="built_in">loadObject</span>(id); <span class="comment">// load it</span></span><br><span class="line">        cache[id] = objPtr; <span class="comment">// cache it     </span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> objPtr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用<code>weak_ptr</code>来做缓存可以避免额外延长资源的生命周期，使得资源在使用结束后能够及时地被回收，同时<strong>使用<code>weak_ptr</code>的<code>lock</code>函数可以判断当前的<code>weak_ptr</code>所指向的对象是否已被删除，如果被删除，则返回空的<code>shared_ptr</code>对象。如果没有被删除，则<code>weak_ptr</code>会提升为<code>shared_ptr</code>，进而我们就可以对其进行相关的操作</strong>。这种让强弱引用相互搭配以避免影响资源回收的做法比较常见，典型有如 lua 语言中，使用对偶表示来实现私有性时，将外部表设置为弱引用键表，这样才能让 GC 在适当的时候能够顺利的回收内存。</p><p><strong>作用二：解决悬空指针问题</strong></p><p>关于如何使用<code>weak_ptr</code>解决悬空指针问题，主要有三种方法:</p><ul><li>利用<code>weak_ptr</code>的<code>lock</code>函数对<code>weak_ptr</code>进行提升，具体的做法可见前面的<strong>作用一:缓存优化</strong>的内容；</li><li>利用<code>weak_ptr</code>构造<code>shared_ptr</code>，具体的做法可以见前面<code>enable_shared_from_this</code>的实现，这里不多做赘述；</li><li>利用<code>weak_ptr</code>的<code>expired</code>函数，如下：</li></ul><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="function"><span class="type">void</span> <span class="title">process</span><span class="params">(weak_ptr&lt;Object&gt; objPtr)</span></span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">    <span class="keyword">if</span>(objPtr.<span class="built_in">expired</span>())&#123;</span><br><span class="line">        <span class="comment">//do something</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>前两种方法不仅涉及了对指针是否悬空的判断，还涉及到了对<code>Object</code>的操作。而在第三种方法中，<code>expired()</code>只能判断当前的<code>weak_ptr</code>是否悬空，但由于<code>weak_ptr</code>不能直接解引用，因此如果需要对<code>Object</code>进行相应的操作，就必须手动将<code>weak_ptr</code>提升为<code>shared_ptr</code>，这样就需要使用锁机制来保证原子性。</p><p><strong>作用三：解决环形引用问题</strong></p><div style="text-align:center">    <img src="/img/smart-pointers/circular-reference.svg" width="80%"></div><div class="image-caption" align="center">环形引用</div><p>当上图中 B 也持有了一个指向 A 的 <code>shared_ptr </code>时，就会引发”环形引用”问题：A 和 B 之间的相互引用会使得它们之间彼此的 <code>reference count</code> 至少为1，这就使得 A 和 B 所占用的资源迟迟不能被回收，造成了内存的泄漏。解决这一问题的方法便是让 B 持有一个指向 A 的 <code>weak_ptr</code> 而非 <code>shared_ptr</code>。由于 <code>weak_ptr</code> 不会增加 <code>reference count</code>，因此 A 所占有的资源可以先于 B 被释放，一旦 A 释放了，那么 B 被释放也只是时间上的问题。</p><h4 id="unique-ptr-与-PImpl-Idiom-的相关问题"><a href="#unique-ptr-与-PImpl-Idiom-的相关问题" class="headerlink" title="unique_ptr 与 PImpl Idiom 的相关问题"></a>unique_ptr 与 PImpl Idiom 的相关问题</h4><p><strong>unique_ptr 的类型</strong></p><p>默认 deleter 的<code>std::unique_ptr</code> 的大小与裸指针一致，而且大部分操作(例如解引用)采用了相同的指令，因此从效率及空间利用率上来讲，<code>std::unique_ptr</code>都可以代替裸指针使用。若用户采用了自定义的 deleter，则<code>std::unique_ptr</code> 的大小还会受到 deleter 的影响:</p><ul><li>当 deleter 是函数指针时，<code>std::unique_ptr</code> 会增加 4 或 8 个字节的大小(取决于指针大小)</li><li>当 deleter 是函数对象时，<code>std::unique_ptr</code> 所增加的大小取决于函数对象中所存储的数据成员</li><li>当 deleter 是无捕获列表的 lambda 表达式时，<code>std::unique_ptr</code> 则不会增加额外的大小。    </li></ul><p>因此在自定义 deleter 时要优先考虑无捕获列表的 lambda 表达式</p><p><code>std::unique_ptr</code>提供了指向 T 类型数组的类型 —— <code>std::unique_ptr&lt;T[]&gt;</code>。根据定义方式的不同，<code>std::unique_ptr</code>也提供了不同的接口来针对不同的实现。例如数组型的 unique_ptr 不支持解引用运算(比如 * 和 -&gt;)，但支持 [] 运算</p><p>另外，<code>std::unique_ptr</code>可以很方便的转化为 <code>std::shared_ptr</code>，且转换的过程会创建 <code>Control Block</code>, 但反之则不行</p><p><strong>unique_ptr 与 PImpl Idiom</strong></p><p>正如前面所说，deleter 作为 <code>unique_ptr</code> 类型的一部分，不仅体现在 deleter 的类型会影响 <code>unique_ptr</code> 所占用空间的大小，还体现在了 PImpl Idiom 上。 在 C++ 中，PImpl 是一种常用的技巧，合理运用可以减少编译时间，例如：</p><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Object.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Object</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">     <span class="built_in">Object</span>();</span><br><span class="line">     …</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">     std::string name;</span><br><span class="line">     std::vector&lt;<span class="type">double</span>&gt; data;</span><br><span class="line">     Gadget g1, g2, g3; <span class="comment">// Gadget is some user-defined type</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在上述代码中，每当 Gadget.h 发生变更时，所用使用到 Object的文件都需要重新编译。现在采用 <code>unique_ptr</code>和 pImpl 的方法来解除 Object 和 Gadget 依赖关系，如下：</p><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><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="comment">//Object.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Object</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Object</span>();</span><br><span class="line"> …</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Impl</span>; <span class="comment">// declare implementation struct</span></span><br><span class="line">std::unique_ptr&lt;Impl&gt; pImpl;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//Object.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Object.h&quot;</span> </span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;gadget.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Object</span>::Impl &#123; <span class="comment">// as before</span></span><br><span class="line">std::string name;</span><br><span class="line">std::vector&lt;<span class="type">double</span>&gt; data;</span><br><span class="line">    Gadget g1, g2, g3;</span><br><span class="line">&#125;;</span><br><span class="line">Object::<span class="built_in">Object</span>() : <span class="built_in">pImpl</span>(std::<span class="built_in">make_unique</span>&lt;Impl&gt;()) &#123;&#125;</span><br><span class="line"><span class="comment">//client.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Object.h&quot;</span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    Object w; <span class="comment">// error!</span></span><br><span class="line">&#125;</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">$ error: invalid application of ‘sizeof’ to incomplete <span class="built_in">type</span> ‘Object::Impl’</span><br></pre></td></tr></table></figure><p>在上述代码中，当需要析构对象 w 时，由于我们没有为 <code>Object</code> 显式定义析构函数，因此编译器会为我们生成相应的析构函数并将其内联到相应的位置，并调用 <code>unique_ptr&lt;Impl&gt;</code> 的析构函数。由于 <code>unique_ptr&lt;Impl&gt;</code> 采用的是 default deleter，它会先对中所持有的<code>Impl*</code>进行静态类型检查<code>static_assert</code>，以确保<code>Impl*</code>指向了一个完全类型。不过<strong>因为这段析构代码是由编译器生成并内联到了指定位置的，因此编译器看不到位于 <code>Object.cc</code> 文件中的 <code>Impl</code> 的详细定义，进而认为<code>Impl*</code>指向了一个不完全类型，触发了编译错误</strong></p><p>一旦明白了原因，那么解决方案就很清晰了：只需要在实现文件<code>Object.cc</code>的 Impl 定义的后面补上析构函数的定义即可，具体做法如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//Object.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Object</span> &#123; </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Object</span>();</span><br><span class="line">    ~<span class="built_in">Object</span>(); <span class="comment">// dtor is needed—see below</span></span><br><span class="line"> …</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Impl</span>; <span class="comment">// declare implementation struct</span></span><br><span class="line">std::unique_ptr&lt;Impl&gt; pImpl;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//Object.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Object.h&quot;</span> </span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;gadget.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Object</span>::Impl &#123; <span class="comment">// as before</span></span><br><span class="line">std::string name;</span><br><span class="line">std::vector&lt;<span class="type">double</span>&gt; data;</span><br><span class="line">    Gadget g1, g2, g3;</span><br><span class="line">&#125;;</span><br><span class="line">Object::<span class="built_in">Object</span>() </span><br><span class="line">: <span class="built_in">pImpl</span>(std::<span class="built_in">make_unique</span>&lt;Impl&gt;()) <span class="comment">// std::unique_ptr</span></span><br><span class="line">&#123;&#125;</span><br><span class="line">Object::~<span class="built_in">Object</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="comment">//Client.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Object.h&quot;</span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    Object w; <span class="comment">// no error</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过显式地定义了函数体，编译器在析构时就能够看到 <code>Impl</code> 的定义，自然也就不会产生 <code>delete incomplete type</code> 的错误了</p><p>而使用 shared_ptr 则没有上述的限制，例如：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//Object.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Object</span> &#123; </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">Object</span>();</span><br><span class="line"> … <span class="comment">// no declarations for dtor or move operations</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="keyword">struct</span> <span class="title class_">Impl</span>; </span><br><span class="line"> std::shared_ptr&lt;Impl&gt; pImpl; <span class="comment">// std::shared_ptr</span></span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">// Client.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Object.h&quot;</span></span></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    Object w1;</span><br><span class="line"><span class="function"><span class="keyword">auto</span> <span class="title">w2</span><span class="params">(std::move(w1))</span></span>; <span class="comment">// move-construct w2</span></span><br><span class="line">w1 = std::<span class="built_in">move</span>(w2); <span class="comment">// move-assign w1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码可以正常通过编译并且运行。</p><p>这一切都源自于 <code>unique_ptr</code> 和 <code>shared_ptr</code>之间关于 deleter 的支持方式上的差异。对于 <code>unique_ptr</code> 而言，deleter 以合成（composition）的方式包含于 <code>unique_ptr</code> 之中，需要在编译期执行类型检查，这种的 deleter 被称之为 static deleter。而对于 <code>shared_ptr</code> 而言，deleter 以聚合（aggregation）的方式存在于<code>shared_ptr</code>之中，不需要在编译期执行检查，这种 deleter 被称之为 dynamic deleter。</p><p>关于 <code>unique_ptr</code> 和 <code>shared_ptr</code> 在不同场合下对类型的要求可见下表，其中 I 表示 <code>Incomplete Type</code>，C 表示 <code>Complete Type</code>，而 N/A 表示不支持该函数</p><table><thead><tr><th></th><th>unique_ptr</th><th>shared_ptr</th></tr></thead><tbody><tr><td>default constructor : P()</td><td>I</td><td>I</td></tr><tr><td>copy constructor  : P(const P&amp;)</td><td>N/A</td><td>I</td></tr><tr><td>move constructor : P(P&amp;&amp;)</td><td>I</td><td>I</td></tr><tr><td>destructor : ~P()</td><td>C</td><td>I</td></tr><tr><td>P(A*)</td><td>I</td><td>C</td></tr><tr><td>copy assignment : operator=(const P&amp;)</td><td>N/A</td><td>I</td></tr><tr><td>move assignment : operator=(P&amp;&amp;)</td><td>C</td><td>I</td></tr><tr><td>reset()</td><td>C</td><td>I</td></tr><tr><td>reset(A*)</td><td>C</td><td>C</td></tr></tbody></table><h4 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h4><p>[0]. 《Effective Modern C++》<br>[1]. 《Linux 多线程服务端编程 —— 使用 muduo C++ 网络库》<br>[2]. <a href="https://stackoverflow.com/questions/34061515/how-stdenable-shared-from-thisshared-from-this-works">How std::enable_shared_from_this::shared_from_this works</a><br>[3]. <a href="https://stackoverflow.com/questions/6012157/is-stdunique-ptrt-required-to-know-the-full-definition-of-t">Is std::unique_ptr required to know the full definition of T?</a></p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文并不是一篇关于Smart Pointer的基础文章，它假定阅读读者至少使用过 C++11 下的 Smart Pointer，因此文章中并不涉及 Smart Pointer 的基础用法，而是希望讨论一些更深层次的东西，主要包含了以下内容&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;引用型 Smart Pointer 的内存布局以及相关推论&lt;/li&gt;
&lt;li&gt;make_shared 的优劣&lt;/li&gt;
&lt;li&gt;enable_shared_from_this 与 shared_from_this 的实现&lt;/li&gt;
&lt;li&gt;weak_ptr 的内存布局及相关应用&lt;/li&gt;
&lt;li&gt;unique_ptr 与 PImpl Idiom 的相关问题&lt;/li&gt;
&lt;li&gt;Reference&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="C++沉思录" scheme="https://phoenix500526.github.io/categories/C-%E6%B2%89%E6%80%9D%E5%BD%95/"/>
    
    
    <category term="C++" scheme="https://phoenix500526.github.io/tags/C/"/>
    
    <category term="smart pointer" scheme="https://phoenix500526.github.io/tags/smart-pointer/"/>
    
    <category term="shared_ptr" scheme="https://phoenix500526.github.io/tags/shared-ptr/"/>
    
    <category term="weak_ptr" scheme="https://phoenix500526.github.io/tags/weak-ptr/"/>
    
    <category term="unique_ptr" scheme="https://phoenix500526.github.io/tags/unique-ptr/"/>
    
    <category term="PImpl Idiom" scheme="https://phoenix500526.github.io/tags/PImpl-Idiom/"/>
    
    <category term="CRTP Idiom" scheme="https://phoenix500526.github.io/tags/CRTP-Idiom/"/>
    
  </entry>
  
  <entry>
    <title>关于 C++ 的 lvalue、xvalue 和 prvalue</title>
    <link href="https://phoenix500526.github.io/2020/12/17/%E5%85%B3%E4%BA%8E-C-%E7%9A%84-lvalue%E3%80%81xvalue-%E5%92%8C-prvalue/"/>
    <id>https://phoenix500526.github.io/2020/12/17/%E5%85%B3%E4%BA%8E-C-%E7%9A%84-lvalue%E3%80%81xvalue-%E5%92%8C-prvalue/</id>
    <published>2020-12-17T20:23:17.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文主要涉及了对 C++ 语言中 lvalue、xvalue 和 rvalue 的一些探讨，主要包括以下内容</p><blockquote><ul><li>一段有趣的代码</li><li>C++ 中的 Expression Category Taxonomy</li><li>prvalue 的作用以及判定标准</li><li>xvalue 的定义及判定标准</li><li>lvalue 的定义及判断标准</li><li>关于 glvalue 与 rvalue</li><li>Reference</li></ul></blockquote><p>本文按照先总后分，自底向上的逻辑讨论，先论述 C++ 中的 Expression Category Taxonomy，再到底层的 prvalue、xvalue 以及 lvalue，最后回到 glvalue 及 rvalue 上。</p><span id="more"></span><h4 id="起因：一段有趣的代码-0"><a href="#起因：一段有趣的代码-0" class="headerlink" title="起因：一段有趣的代码[0]"></a>起因：一段有趣的代码<sup>[0]</sup></h4><p>在前一段时间，我写出了如下的一段代码：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// value.cc</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">sendByRvalue</span><span class="params">(string&amp;&amp; str)</span></span>&#123;</span><br><span class="line">        cout &lt;&lt; str &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    A a;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">send</span><span class="params">(string&amp;&amp; str)</span></span>&#123;</span><br><span class="line">        a.<span class="built_in">sendByRvalue</span>(str);</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">run</span><span class="params">(<span class="type">const</span> string&amp; str)</span></span>&#123;</span><br><span class="line">        <span class="built_in">send</span>(<span class="string">&quot;run &quot;</span> + str + <span class="string">&quot;\n&quot;</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="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">void</span>)</span></span>&#123;</span><br><span class="line">    <span class="function">string <span class="title">str</span><span class="params">(<span class="string">&quot;hello world&quot;</span>)</span></span>;</span><br><span class="line">    B b;</span><br><span class="line">    b.<span class="built_in">run</span>(str);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</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><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ clang++ value.cc -std=c++11</span><br><span class="line">$ error: rvalue reference to <span class="built_in">type</span> <span class="string">&#x27;basic_string&lt;[...]&gt;&#x27;</span> cannot <span class="built_in">bind</span> to lvalue of <span class="built_in">type</span> <span class="string">&#x27;basic_string&lt;[...]&gt;&#x27;</span></span><br><span class="line">        a.sendByRvalue(str);</span><br></pre></td></tr></table></figure><p>从错误的信息来看，出错的原因应该是我试图将一个左值 str 传递给一个参数类型为 rvalue reference 的 <code>sendByRvalue</code> 函数。等等，好像哪里不对？str 本来就是一个 rvalue reference 啊。起初我怀疑是 str 在传递给 <code>send</code> 函数时发生了突变，导致在执行<code>send</code>函数体时变成了 lvalue，于是我对代码做了一点修改：</p><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><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">class</span> <span class="title class_">B</span>&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    A a;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">send</span><span class="params">(string&amp;&amp; str)</span></span>&#123;</span><br><span class="line">        cout &lt;&lt; boolalpha &lt;&lt; is_rvalue_reference&lt;<span class="keyword">decltype</span>(str)&gt;::value &lt;&lt; endl;</span><br><span class="line">        a.<span class="built_in">sendByRvalue</span>(std::<span class="built_in">move</span>(str));</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">run</span><span class="params">(<span class="type">const</span> string&amp; str)</span></span>&#123;</span><br><span class="line">        <span class="built_in">send</span>(<span class="string">&quot;run &quot;</span> + str + <span class="string">&quot;\n&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 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="literal">true</span></span><br><span class="line">$ run hello world</span><br></pre></td></tr></table></figure><p>显然，str 在传递给 <code>a.sendByRvalue</code> 前还是 rvalue reference。然而，结合之前编译器的错误信息，我们竟然能够得出这样一个结论：一个右值引用变量居然不能传递给一个参数类型为右值引用的函数！？ 这是咋回事？？</p><h4 id="C-中的-Expression-Category-Taxonomy"><a href="#C-中的-Expression-Category-Taxonomy" class="headerlink" title="C++ 中的 Expression Category Taxonomy"></a>C++ 中的 Expression Category Taxonomy</h4><p>在 C++ 中，任何表达式都具备两个独立的属性：类型(type)和值类别(value category)。关于表达式的 type，指的是常量表达式，整型表达式等概念，而 <strong>value category 虽然名字上带有 <code>value</code>，但实际上描述的是表达式而非值</strong>，主要包括 glvalue、rvalue、lvalue、xvalue 以及 prvalue，它们之间的关系如下图：</p><div style="text-align:center"> <img src="/img/value-categories/expression-category-taxonomy.svg"/></div><div class="image-caption" align="center">Expression category taxonomy</div><p>cpp reference 上为这种分类方法提供了解释依据：</p><blockquote><p>With the introduction of move semantics in C++11, value categories were redefined to characterize two independent properties of expressions:</p><ul><li>has identity: it’s possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify (obtained directly or indirectly);</li><li>can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression. <sup>[1]</sup></li></ul></blockquote><p>从上述解释中，我们可以知道 C++ 11 中对于表达式值分成 glvalue 和 rvalue 的主要原因。早期的 C++ 标准不支持移动语义，因此对于表达式的类别的判断依据仅为是否拥有 “identity”, 这也就将表达式的类别一分为二，分为了 lvalue 和 rvalue。<strong>在 C++ 11 标准中引入了移动语义后，标准将 <code>has identity</code> 的表达式定为 glvalue， 而将<code>can be moved from</code>的表达式定为 rvalue</strong>，并根据这两种属性两两组合，得到了以下的 4 种可能：</p><blockquote><ul><li>have identity and cannot be moved from are called <em>lvalue</em> expressions;</li><li>have identity and can be moved from are called <em>xvalue</em> expressions;</li><li>do not have identity and can be moved from are called <em>prvalue</em> (“pure rvalue”) expressions;</li><li>do not have identity and cannot be moved from are not used.</li></ul></blockquote><h4 id="prvalue-的作用以及判定标准"><a href="#prvalue-的作用以及判定标准" class="headerlink" title="prvalue 的作用以及判定标准"></a>prvalue 的作用以及判定标准</h4><p>我们先来看看 prvalue，所谓的 prvalue 是 <code>Pure rvalue</code> 的缩写。prvalue 主要包括以下两种：</p><ul><li>用于计算与对象无关的值(computes a value that is not associated with an object)</li><li>创建出来的临时对象</li></ul><p>这里简单地解释一下什么叫做<code>与对象无关的值(a value that is not associated with an object)</code>。在 C++ draft 当中是这样解释 object 的：</p><blockquote><p>The constructs in a C++ program create, destroy, refer to, access, and manipulate objects. An object is created by a definition, by a new-expression , by an operation that implicitly creates objects (see below), when implicitly changing the active member of a union, or when a temporary object is created . An object occupies a region of storage in its period of construction , throughout its lifetime, and in its period of destruction.  <sup>[2]</sup></p></blockquote><p>在 C++ 中，所谓的对象是指一个存储区域，这块存储区域需要通过对定义式或 new 表达式显式或隐式地调用来创建。而与对象无关的值，实际上指的就是那些没有相应存储区的值，例如除了字符串字面值以外的所有字面值。</p><p>在 C++ 中，属于 prvalue  的表达式有以下几种：</p><blockquote><ul><li>除了字符串字面值以外的所有字面值，如 <code>12</code>, <code>true</code> 或者 <code>nullptr</code></li><li>返回值类型为 non-reference 类型的函数调用表达式，包括了一般形式的函数调用，如 <code>f()</code>，也包括了重载过的函数运算符，如 “str1 + str2”</li><li>返回临时值的 built-in 运算符表达式，例如 <code>a++</code>, <code>a + b</code>, <code>&amp;a</code>, <code>a &lt; b</code> 等</li><li>将对象转换为 non-reference 类型的 cast 表达式，如 <code>static_cast&lt;double&gt;(x)</code></li><li><strong>任何枚举值都是 prvalue</strong>，也包括定义在类中的枚举值，如 <code>obj.m</code> 或 <code>obj_ptr-&gt;m</code>，其中 m 是枚举值</li><li>this 指针</li><li>lambda 表达式</li></ul></blockquote><p>为了避免和前面非对象类的 prvalue 判断相混淆，我特地将类对象的 prvalue 的部分单独拎出来，放在了下面：</p><blockquote><p>针对成员变量：</p><ul><li>若对象 obj 是 rvalue，则形如 <code>obj.m</code> 与 <code>obj.*mp</code> 为 prvalue，其中 m 为 non-static 的 non-reference 类型的成员变量， mp 为指向数据成员的指针</li></ul><p>针对成员函数(注意，此处函数指的是函数本身<code>obj.func</code>，而不是函数调用<code>obj.func()</code>)：</p><ul><li>当 func 为 obj 的 non-static 成员函数时， <code>obj.func</code> 和 <code>obj_ptr-&gt;func</code> 也为 prvalue。</li><li>当 func_ptr 为指向对象成员函数的函数指针时，则<code>obj.*func_ptr</code> 和 <code>obj-&gt;*func_ptr</code> 均为 prvalue</li></ul></blockquote><h4 id="xvalue-的定义及判定标准"><a href="#xvalue-的定义及判定标准" class="headerlink" title="xvalue 的定义及判定标准"></a>xvalue 的定义及判定标准</h4><p>接下来看看 xvalue，所谓 xvalue 是 <code>eXpiring value</code> 的缩写，代表了资源在回收前可以被重复利用的 glvalue。在 C++ 当中，类型为 xvalue 的表达式有以下四种：</p><blockquote><ul><li>对类型为 rvalue 类型的数组进行 built-in 的下标运算，如 arr[n]</li><li>对于形如 obj.m 或 obj.*mp 的表达式，如果 obj 是 xvalue，或者 m 为 non-static、non-reference 的数据成员，或者 mp 为指向数据成员的指针，则 obj.m 与 obj.*mp 为 xvalue</li><li>当 obj 为 rvalue 时，且 m 为 non-static non-reference 类型的数据成员，mp 为指向数据成员的指针，则 <code>obj.m</code> 或 <code>obj.*mp</code> 为 xvalue </li><li>返回类型为 rvalue reference 的函数调用表达式，包括了一般的函数调用以及重载运算符，如 std::move(x)</li><li>将一个对象强制转换为 rvalue reference 的表达式，如 static_cast&lt;int&amp;&amp;&gt;(obj)</li></ul></blockquote><h4 id="lvalue-的定义及判断标准"><a href="#lvalue-的定义及判断标准" class="headerlink" title="lvalue 的定义及判断标准"></a>lvalue 的定义及判断标准</h4><p>最后来看看 lvalue，所谓的 lvalue 最初是指那些可以被放在 built-in 赋值运算符左侧的表达式，然而随着 C++ 语言的演化，这一说法也变得不再准确。在新版本中，lvalue 被定义为那些属于 glvalue 但是不属于 xvalue 的表达式类型。lvalue expression 都符合以下条件：</p><blockquote><ul><li><strong>任何有名字的变量，函数，数据成员，无论其类型是什么，一律都是 lvalue</strong>。即便是 rvalue reference 类型的具名变量，其 value category 也是 lvalue</li><li>任何返回类型为 lvalue reference 的函数调用表达式，其中函数调用也包括了以重载运算符形式进行的调用</li><li>任何返回类型为<strong>对函数的 rvalue reference</strong> 的函数调用表达式，其中函数调用也包括了以重载运算符形式进行的调用</li><li>任何返回类型为 lvalue reference 的 built-in 运算符表达式，例如 <code>++a</code>，<code>a += b</code> 等。</li><li>对 lvalue 类型数组 arr 执行 built-in 的取下标运算符, 如 <code>arr[n]</code></li><li>对指针变量 p 的解引用操作，如 <code>*p</code> 或者 <code>p[n]</code></li><li>字符串字面值，如 <code>HelloWorld</code></li><li>将对象强制转换为 lvalue reference 的 cast 表达式，如 <code>static_cast&lt;int&amp;&gt;(x)</code></li><li>将对象强制转换为<strong>对函数的 rvalue reference</strong> 的 cast 表达式，如 <code>static_cast&lt;void (&amp;&amp;)(int)&gt;(x)</code></li></ul></blockquote><p>和前面相同，这里单独将与对象相关的 lvalue 拎出来讨论：</p><blockquote><p>针对成员变量：</p><ul><li>对于 <code>obj.m</code> 的表达式，如果 obj 是 glvalue 或者 m 是静态数据成员，则 <code>obj.m</code> 是 lvalue</li><li>对于 <code>obj.*mp</code> 的表达式，如果 obj 是 lvalue 且 mp 是指向数据成员的指针，则 obj.*mp 是 lvalue</li><li>对于 <code>obj-&gt;*mp</code> 的表达式是 lvalue，其中 mp 是指向其数据成员的指针</li></ul><p>针对成员函数(注意，此处函数指的是函数本身<code>obj.func</code>，而不是函数调用<code>obj.func()</code>)：</p><ul><li>若 func 为静态成员函数，则 <code>obj.func</code> 和 <code>obj-&gt;*func</code>是 lvalue</li></ul></blockquote><p>这里需要提一下，<strong>只有字符串字面值是 lvalue，而其他的字面值都是 rvalue</strong>。因为在 C++ 中，任何字符串字面值，例如 “Hello world”，<strong>其类型都是数组类型，而不是指针类型</strong>。这主要是因为在 C 语言中，数组类型必须作为 lvalue，否则无法兼容 C 语言中的一些用法，比如使用 <code>sizeof(arr)</code> 来求出 arr 所占内存空间的大小。C++ 为了兼容 C 语言，沿袭了这一习惯<sup>[3]</sup>。</p><h4 id="关于-glvalue-与-rvalue"><a href="#关于-glvalue-与-rvalue" class="headerlink" title="关于 glvalue 与  rvalue"></a>关于 glvalue 与  rvalue</h4><p>先来看看 glvalue。在 C++ 11 中，glvalue 代表了 <code>Generalized lvalue</code>, 一个 glvalue 要么是 lvalue，要么是 xvalue，具备以下特征：</p><blockquote><ul><li>一个 glvalue 通过隐式转换，如 lvalue-to-rvalue、array-to-pointer 或者 function-to-pointer，转换成为 prvalue</li><li>一个 glvalue 可以是多态的，其对象的动态类型并不一定需要与表达式的静态类型相一致</li><li>在表达式允许的范围内，glvalue 可以拥有不完全类型</li></ul></blockquote><p>lvalue 可以视为是对 glvalue 特征的一个补充，它除了具备 glvalue 所有的特征以外，还额外具备以下特征：</p><blockquote><ul><li>可以使用 built-in 的取地址运算符 &amp; 为 lvalue expression 进行取地址运算，例如 <code>&amp;++i</code> 是合法的</li><li>一个可变的 lvalue expression 可作为 built-in 的赋值运算符和复合赋值运算符的左操作数</li><li>一个 lvalue expression 可以用于被绑定到一个 lvalue reference 上</li></ul></blockquote><p>再来看看 rvalue。在 C++ 11 中， 一个 rvalue 要么是 prvalue，要么是 xvalue，具备了以下的特征：</p><blockquote><ul><li>无法使用 built-in 的 &amp; 运算符对其取地址，例如 <code>&amp;int()</code>, <code>&amp;i++</code> 等都是非法的</li><li>rvalue 无法被放在 built-in 的赋值运算符的左侧</li><li>rvalue 可以绑定到 const lvalue reference 或 rvalue reference 类型的变量上，一旦完成绑定，rvalue 的生命周期将会延长到引用结束</li></ul></blockquote><p>对于 prvalue 而言，除了具备 rvalue 的所有特征外，还额外具备以下特征：</p><blockquote><ul><li>prvalue 不能是除了 <code>void</code> 以外的不完全类型</li><li>non-class、non-array 的 prvalue expression 不能被 cv 限定符修饰</li><li>一个 prvalue expression 不具备多态性，其动态类型始终和它的静态类型相一致、</li></ul></blockquote><p>而 xvalue 作为 lvalue 到 prvalue 之间的过渡，它同时具备 rvalue 以及 glvalue 的特征。一个 xvalue 既可以像 rvalue 那样被绑定到 rvalue reference 上，又可以像 glvalue 那样体现出多态的特性。</p><p>最后，回到之前的那段代码, 尽管 <code>void B::send(string&amp;&amp; str)</code> 中的参数 <code>str</code> 是一个 rvalue reference，但它依然是一个 lvalue，无法用来初始化一个 rvalue reference，自然也就无法传递给函数 <code>void A::sendByRvalue(string&amp;&amp; str)</code>。</p><h4 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h4><p>[0]. <a href="https://stackoverflow.com/questions/64798137/why-the-rvalue-reference-parameter-cannot-be-passed-between-functions-directly">Why the rvalue reference parameter cannot be passed between functions directly?</a><br>[1]. <a href="https://en.cppreference.com/w/cpp/language/value_category">Cppreference: Value categories</a><br>[2]. <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0593r5.html#662-object-model-introobject">P0593R5. Implicit creation of objects for low-level object manipulation</a><br>[3]. <a href="https://stackoverflow.com/questions/63899666/is-a-literal-say-hello-world-a-rvalue-when-i-passing-it-to-a-template-which-i">Is a literal, say “Hello world”，a rvalue when I passing it to a template which its paramater is an universal reference? - Stackoverflow</a></p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文主要涉及了对 C++ 语言中 lvalue、xvalue 和 rvalue 的一些探讨，主要包括以下内容&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;一段有趣的代码&lt;/li&gt;
&lt;li&gt;C++ 中的 Expression Category Taxonomy&lt;/li&gt;
&lt;li&gt;prvalue 的作用以及判定标准&lt;/li&gt;
&lt;li&gt;xvalue 的定义及判定标准&lt;/li&gt;
&lt;li&gt;lvalue 的定义及判断标准&lt;/li&gt;
&lt;li&gt;关于 glvalue 与 rvalue&lt;/li&gt;
&lt;li&gt;Reference&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文按照先总后分，自底向上的逻辑讨论，先论述 C++ 中的 Expression Category Taxonomy，再到底层的 prvalue、xvalue 以及 lvalue，最后回到 glvalue 及 rvalue 上。&lt;/p&gt;</summary>
    
    
    
    <category term="C++沉思录" scheme="https://phoenix500526.github.io/categories/C-%E6%B2%89%E6%80%9D%E5%BD%95/"/>
    
    
    <category term="C++" scheme="https://phoenix500526.github.io/tags/C/"/>
    
    <category term="Expression Category Taxonomy" scheme="https://phoenix500526.github.io/tags/Expression-Category-Taxonomy/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库中的进程监控方法的思考与实践</title>
    <link href="https://phoenix500526.github.io/2020/11/14/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E7%9A%84%E8%BF%9B%E7%A8%8B%E7%9B%91%E6%8E%A7%E6%96%B9%E6%B3%95%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/"/>
    <id>https://phoenix500526.github.io/2020/11/14/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E7%9A%84%E8%BF%9B%E7%A8%8B%E7%9B%91%E6%8E%A7%E6%96%B9%E6%B3%95%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/</id>
    <published>2020-11-14T22:11:23.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 网络库源码剖析系列文章的第九篇文章，主要涉及了 muduo 网络库中是如何获取进程信息，又是以什么样的方式展现给客户的，其中包含了以下内容：</p><blockquote><ul><li>幕后功臣 ： FileUtil 的实现</li><li>一种基于 REST 风格的进程监控</li><li>一种基于 Observer Pattern 实现的进程监控</li><li>最后一点碎碎念</li></ul></blockquote><p>注：由于这两种进程监控的方法都涉及到了与其他组件的配合，例如 HttpServer、hub 等组件，碍于篇幅原因，本文中不会粘贴过多的代码，主要以图表的形式阐述。本文假定读者了解 Http 协议， Observer Pattern 以及 linux 下文件系统等相关知识。另外，为了避免代码与我之前文章代码不一致，此处涉及代码引用自 tmuduo。在进程相关方面， tmuduo 只做了及其微小的修改，与 muduo 源码差距很小。在设计上以 muduo 的思路为主，但代码还是来自于 tmuduo，望知悉。<br>本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#103;&#105;&#x74;&#x40;&#x67;&#105;&#x74;&#x68;&#x75;&#98;&#x2e;&#99;&#111;&#109;">&#103;&#105;&#x74;&#x40;&#x67;&#105;&#x74;&#x68;&#x75;&#98;&#x2e;&#99;&#111;&#109;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><h4 id="幕后功臣-：-FileUtil-的实现"><a href="#幕后功臣-：-FileUtil-的实现" class="headerlink" title="幕后功臣 ： FileUtil 的实现"></a>幕后功臣 ： FileUtil 的实现</h4><p>linux 的设计哲学之一便是“一切皆文件”，连进程信息也不例外。在 linux 系统下，想要获取进程信息不是一件难事，只需要读取 procfs 下相应的文件即可。在 tmuduo 中，用来读取文件信息的类有两个，一个是用来获取文件信息的 ReadSmallFile，另一个则是用来向文件中追加信息的 AppendFile，它们均被定义在 tmuduo/base/FileUtil.{h,cc} 文件中，而本文要关注的是 ReadSmallFile</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// read small file &lt; 64KB</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ReadSmallFile</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">ReadSmallFile</span>(StringArg filename): <span class="built_in">fd_</span>(::<span class="built_in">open</span>(filename.<span class="built_in">c_str</span>(), O_RDONLY | O_CLOEXEC)), <span class="built_in">err_</span>(<span class="number">0</span>) &#123;</span><br><span class="line">    buf_[<span class="number">0</span>] = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    <span class="keyword">if</span> (fd_ &lt; <span class="number">0</span>) &#123;</span><br><span class="line">      err_ = errno;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  ~<span class="built_in">ReadSmallFile</span>() &#123;</span><br><span class="line">    <span class="keyword">if</span> (fd_ &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">      ::<span class="built_in">close</span>(fd_);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// return errno</span></span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> String&gt;</span><br><span class="line">  <span class="function"><span class="type">int</span> <span class="title">readToString</span><span class="params">(<span class="type">int</span> maxSize, String* content, <span class="type">int64_t</span>* fileSize,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="type">int64_t</span>* modifyTime, <span class="type">int64_t</span>* createTime)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Read at maxium kBufferSize into buf_</span></span><br><span class="line">  <span class="comment">// return errno</span></span><br><span class="line">  <span class="function"><span class="type">int</span> <span class="title">readToBuffer</span><span class="params">(<span class="type">int</span>* size)</span></span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">const</span> <span class="type">char</span>* <span class="title">buffer</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> buf_; &#125;</span><br><span class="line">  <span class="type">static</span> <span class="type">const</span> <span class="type">int</span> kBufferSize = <span class="number">64</span> * <span class="number">1024</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="type">int</span> fd_;</span><br><span class="line">  <span class="type">int</span> err_;</span><br><span class="line">  <span class="type">char</span> buf_[kBufferSize];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// read the file content, returns errno if error happens.</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> String&gt;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">readFile</span><span class="params">(StringArg filename, <span class="type">int</span> maxSize, String* content,</span></span></span><br><span class="line"><span class="params"><span class="function">             <span class="type">int64_t</span>* fileSize = <span class="literal">nullptr</span>, <span class="type">int64_t</span>* modifyTime = <span class="literal">nullptr</span>,</span></span></span><br><span class="line"><span class="params"><span class="function">             <span class="type">int64_t</span>* createTime = <span class="literal">nullptr</span>)</span> </span>&#123;</span><br><span class="line">  <span class="function">ReadSmallFile <span class="title">file</span><span class="params">(filename)</span></span>;</span><br><span class="line">  <span class="keyword">return</span> file.<span class="built_in">readToString</span>(maxSize, content, fileSize, modifyTime, createTime);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>muduo 中在 ProcessInfo.h 中提供了一系列的 API，提供给用户调用以获得进程信息，我们来看一个例子：</p><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></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">ProcessInfo::procStatus</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  std::string result;</span><br><span class="line">  FileUtil::<span class="built_in">readFile</span>(<span class="string">&quot;/proc/self/status&quot;</span>, <span class="number">65536</span>, &amp;result);</span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上例可以看到，由于 linux 系统本身的支持，使得获取进程信息这件事情变得非常简单。然而，procfs 虽然便利，但是也有两个不足：</p><ul><li>只能暴露 system-wide 的数据，不能查看每个进程内部的数据</li><li>procfs 是本地文件系统，必须登录到这台机器上才能查看，如果要管理多台机器，那么工作量就会大大增加。<br>对于第一个问题，我们可以通过让程序主动暴露出内部状态，而针对第二个问题，muduo 中提供了两种不同的做法，以下分别讨论。</li></ul><h4 id="基于-REST-风格的进程监控"><a href="#基于-REST-风格的进程监控" class="headerlink" title="基于 REST 风格的进程监控"></a>基于 REST 风格的进程监控</h4><p>针对 procfs 的第二个问题，muduo 中所提供的第一种是一种基于 REST 风格的进程监控。所谓 REST 风格，简而言之便是以 “<strong>URL 定位资源，用 HTTP 动词描述操作</strong>”。在分布式系统当中，使用 HTTP 获取进程信息有以下好处：</p><blockquote><ul><li>使用 HTTP 协议，无需使用特定的客户端程序，只需要使用普通的 Web 浏览器就可以实现远程访问，紧急情况下，也可以使用 curl/wget 甚至是 telnet 来访问。</li><li>实现方便，而且也很容易编写一些脚本来进行自动化的状态收集与分析</li><li>可以通过 URL 区分资源，很容易实现有选择地查看信息，而不是把进程信息一股脑全 dump 出来</li><li>HTTP 天生支持聚合，一个浏览器页面内可以内置多个 iframe，能一眼看清多个进程的状态。</li><li>除了 GET 方法外，如果有必要，还可以实现 PUT/POST/DELETE 等方法，通过 HTTP 协议来控制并修改进程状态，让程序做到“能观能控”</li></ul></blockquote><p>其中，Inspector 的实现框图大致如下：</p><div style="text-align:center"> <img src="/img/muduo-inspect/http-inspector.svg"/></div><div class="image-caption" align="center">HttpInspector框图</div><p>muduo 中相关代码位于 net/inspect/ 下</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Inspector</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="keyword">using</span> ArgList = std::vector&lt;std::string&gt;;</span><br><span class="line">  <span class="keyword">using</span> Callback =</span><br><span class="line">      std::function&lt;std::<span class="built_in">string</span>(HttpRequest::Method, <span class="type">const</span> ArgList&amp; arg)&gt;;</span><br><span class="line">  <span class="built_in">Inspector</span>(EventLoop* loop, <span class="type">const</span> InetAddress&amp; httpAddr, <span class="type">const</span> std::string&amp; name)</span><br><span class="line">    : <span class="built_in">server_</span>(loop, httpAddr, <span class="string">&quot;Inspector:&quot;</span> + name),</span><br><span class="line">      <span class="built_in">processInspector_</span>(<span class="keyword">new</span> ProcessInspector),</span><br><span class="line">      <span class="built_in">systemInspector_</span>(<span class="keyword">new</span> SystemInspector) &#123;</span><br><span class="line">    <span class="built_in">assert</span>(CurrentThread::<span class="built_in">isMainThread</span>());</span><br><span class="line">    <span class="built_in">assert</span>(g_globalInspector == <span class="number">0</span>);</span><br><span class="line">    <span class="comment">/* omitted some code */</span></span><br><span class="line">    server_.<span class="built_in">setHttpCallback</span>(std::<span class="built_in">bind</span>(&amp;Inspector::onRequest, <span class="keyword">this</span>, _1, _2));</span><br><span class="line">    processInspector_-&gt;<span class="built_in">registerCommands</span>(<span class="keyword">this</span>);</span><br><span class="line">    systemInspector_-&gt;<span class="built_in">registerCommands</span>(<span class="keyword">this</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> HAVE_TCMALLOC</span></span><br><span class="line">    performanceInspector_.<span class="built_in">reset</span>(<span class="keyword">new</span> PerformanceInspector);</span><br><span class="line">    performanceInspector_-&gt;<span class="built_in">registerCommands</span>(<span class="keyword">this</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">    loop-&gt;<span class="built_in">runAfter</span>(<span class="number">0</span>, std::<span class="built_in">bind</span>(&amp;Inspector::start, <span class="keyword">this</span>));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// Add a Callback for handling the special uri : /module/command</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(<span class="type">const</span> std::string&amp; <span class="keyword">module</span>, <span class="type">const</span> std::string&amp; command,</span></span></span><br><span class="line"><span class="params"><span class="function">           <span class="type">const</span> Callback&amp; cb, <span class="type">const</span> std::string&amp; help)</span></span>&#123;</span><br><span class="line">    <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">    modules_[<span class="keyword">module</span>][command] = cb;</span><br><span class="line">    helps_[<span class="keyword">module</span>][command] = help;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">using</span> CommandList = std::map&lt;std::string, Callback&gt;;</span><br><span class="line">  <span class="keyword">using</span> HelpList = std::map&lt;std::string, std::string&gt;;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">onRequest</span><span class="params">(<span class="type">const</span> HttpRequest&amp; req, HttpResponse* resp)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (req.<span class="built_in">path</span>() == <span class="string">&quot;/&quot;</span>) &#123;</span><br><span class="line">      std::string result;</span><br><span class="line">      <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">      <span class="keyword">for</span> (std::map&lt;std::string, HelpList&gt;::const_iterator helpListI = helps_.<span class="built_in">begin</span>();</span><br><span class="line">          helpListI != helps_.<span class="built_in">end</span>(); ++helpListI) &#123;</span><br><span class="line">        <span class="type">const</span> HelpList&amp; list = helpListI-&gt;second;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">const</span> <span class="keyword">auto</span>&amp; it : list) &#123;</span><br><span class="line">          result += <span class="string">&quot;/&quot;</span>;</span><br><span class="line">          result += helpListI-&gt;first;</span><br><span class="line">          result += <span class="string">&quot;/&quot;</span>;</span><br><span class="line">          result += it.first;</span><br><span class="line">          <span class="type">size_t</span> len = helpListI-&gt;first.<span class="built_in">size</span>() + it.first.<span class="built_in">size</span>();</span><br><span class="line">          result += std::<span class="built_in">string</span>(len &gt;= <span class="number">25</span> ? <span class="number">1</span> : <span class="number">25</span> - len, <span class="string">&#x27; &#x27;</span>);</span><br><span class="line">          result += it.second;</span><br><span class="line">          result += <span class="string">&quot;\n&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      resp-&gt;<span class="built_in">setStatusCode</span>(HttpResponse::HttpStatusCode::k200Ok);</span><br><span class="line">      resp-&gt;<span class="built_in">setStatusMessage</span>(<span class="string">&quot;OK&quot;</span>);</span><br><span class="line">      resp-&gt;<span class="built_in">setContentType</span>(<span class="string">&quot;text/plain&quot;</span>);</span><br><span class="line">      resp-&gt;<span class="built_in">setBody</span>(result);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      std::vector&lt;std::string&gt; result = <span class="built_in">split</span>(req.<span class="built_in">path</span>());</span><br><span class="line">      <span class="type">bool</span> ok = <span class="literal">false</span>;</span><br><span class="line">      <span class="comment">/* omitted some code */</span></span><br><span class="line">      std::string <span class="keyword">module</span> = result[<span class="number">0</span>];</span><br><span class="line">      <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">      std::map&lt;std::string, CommandList&gt;::const_iterator commListI =</span><br><span class="line">          modules_.<span class="built_in">begin</span>();</span><br><span class="line">      <span class="keyword">if</span> (commListI != modules_.<span class="built_in">end</span>()) &#123;</span><br><span class="line">        std::string command = result[<span class="number">1</span>];</span><br><span class="line">        <span class="type">const</span> CommandList&amp; commList = commListI-&gt;second;</span><br><span class="line">        CommandList::const_iterator it = commList.<span class="built_in">find</span>(command);</span><br><span class="line">        <span class="keyword">if</span> (it != commList.<span class="built_in">end</span>()) &#123;</span><br><span class="line">          <span class="function">ArgList <span class="title">args</span><span class="params">(result.begin() + <span class="number">2</span>, result.end())</span></span>;</span><br><span class="line">          <span class="keyword">if</span> (it-&gt;second) &#123;</span><br><span class="line">            resp-&gt;<span class="built_in">setStatusCode</span>(HttpResponse::HttpStatusCode::k200Ok);</span><br><span class="line">            resp-&gt;<span class="built_in">setStatusMessage</span>(<span class="string">&quot;OK&quot;</span>);</span><br><span class="line">            resp-&gt;<span class="built_in">setContentType</span>(<span class="string">&quot;text/plain&quot;</span>);</span><br><span class="line">            <span class="type">const</span> Callback&amp; cb = it-&gt;second;</span><br><span class="line">            resp-&gt;<span class="built_in">setBody</span>(<span class="built_in">cb</span>(req.<span class="built_in">method</span>(), args));</span><br><span class="line">            ok = <span class="literal">true</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 class="keyword">if</span> (!ok) &#123;</span><br><span class="line">        resp-&gt;<span class="built_in">setStatusCode</span>(HttpResponse::HttpStatusCode::k404NotFound);</span><br><span class="line">        resp-&gt;<span class="built_in">setStatusMessage</span>(<span class="string">&quot;Not Found&quot;</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">  HttpServer server_;</span><br><span class="line">  std::unique_ptr&lt;ProcessInspector&gt; processInspector_;</span><br><span class="line">  std::unique_ptr&lt;PerformanceInspector&gt; performanceInspector_;</span><br><span class="line">  std::unique_ptr&lt;SystemInspector&gt; systemInspector_;</span><br><span class="line">  Mutex mutex_;</span><br><span class="line">  <span class="function">std::map&lt;std::string, CommandList&gt; modules_ <span class="title">GUARDED_BY</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">  <span class="function">std::map&lt;std::string, HelpList&gt; helps_ <span class="title">GUARDED_BY</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>其中，结合大致的框图和相应的代码，可以看出 Inspector 的大致工作流程如下：</p><blockquote><ol><li>将 onRequest 注册为 server_ 的 HttpCallback 函数</li><li>在 Inspector 构造初期调用 processInspector_ 等对象的 registerCommands 函数，将其中的命令都注册到 Inspector 的 modules_ 和 helps_ 当中。</li><li>启动事件循环，等待客户端发送相应的 http 请求，然后根据 url 指向相应 module 的命令，并将执行结果打包成为 http 响应报文，发送给用户。</li></ol></blockquote><p>其中 <code>ProcessInspector</code>, <code>SystemInspector</code> 以及 <code>PerformanceInspector</code> 的实现是大体一致的，我们以 <code>ProcessInspector</code> 为例，看看其对应的实现如何：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ProcessInspector</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">registerCommands</span><span class="params">(Inspector* ins)</span> </span>&#123;</span><br><span class="line">    ins-&gt;<span class="built_in">add</span>(<span class="string">&quot;proc&quot;</span>, <span class="string">&quot;overview&quot;</span>, ProcessInspector::overview,</span><br><span class="line">           <span class="string">&quot;print basic overview&quot;</span>);</span><br><span class="line">    ins-&gt;<span class="built_in">add</span>(<span class="string">&quot;proc&quot;</span>, <span class="string">&quot;pid&quot;</span>, ProcessInspector::pid, <span class="string">&quot;print pid&quot;</span>);</span><br><span class="line">    ins-&gt;<span class="built_in">add</span>(<span class="string">&quot;proc&quot;</span>, <span class="string">&quot;status&quot;</span>, ProcessInspector::procStatus,</span><br><span class="line">           <span class="string">&quot;print /proc/self/status&quot;</span>);</span><br><span class="line">    ins-&gt;<span class="built_in">add</span>(<span class="string">&quot;proc&quot;</span>, <span class="string">&quot;opened_files&quot;</span>, ProcessInspector::openedFiles,</span><br><span class="line">           <span class="string">&quot;count /proc/self/fd&quot;</span>);</span><br><span class="line">    ins-&gt;<span class="built_in">add</span>(<span class="string">&quot;proc&quot;</span>, <span class="string">&quot;threads&quot;</span>, ProcessInspector::threads,</span><br><span class="line">           <span class="string">&quot;list /proc/self/task&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> std::string <span class="title">ProcessInspector::pid</span><span class="params">(HttpRequest::Method,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  <span class="type">const</span> Inspector::ArgList&amp;)</span> </span>&#123;</span><br><span class="line">    <span class="type">char</span> buf[<span class="number">32</span>];</span><br><span class="line">    <span class="built_in">snprintf</span>(buf, <span class="keyword">sizeof</span> buf, <span class="string">&quot;%d&quot;</span>, ProcessInfo::<span class="built_in">pid</span>());</span><br><span class="line">    <span class="keyword">return</span> buf;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/* omitted */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中 <code>registerCommands</code> 会调用 <code>Inspector</code> 的 <code>add</code> 函数将对应的命令名称、描述信息以及对应对调函数注册到 <code>Inspector</code> 中。</p><h4 id="基于-Observer-Pattern-实现的进程监控"><a href="#基于-Observer-Pattern-实现的进程监控" class="headerlink" title="基于 Observer Pattern 实现的进程监控"></a>基于 Observer Pattern 实现的进程监控</h4><p>muduo 还提供了另外一种基于 Observer Pattern 的实现。这种方式有点像 redis 的集群机制，让不同的节点将自身的相关信息发布到对应的 topic 上，有需要的节点只需要订阅相关的 topic 就可以获得相应节点的进程信息。</p><p>在基于 Observer Pattern 模式实现的进程监控方式中，主要涉及了四个不同的概念，分别是话题<code>Topic</code>, 集线器<code>Hub</code>，发布者<code>Pub</code>以及订阅者<code>Sub</code>,它们之间的关系如下：</p><div style="text-align:center"> <img src="/img/muduo-inspect/observer-pubsub.svg"/></div><div class="image-caption" align="center">基于 Observer Pattern 模式实现的进程监控</div><p>muduo 当中的 Hub 示例采用了 “\r\n” 分界的文本协议，这样做可以简单地使用 telnet 测试 Hub，使用时采用命令的方式，如：</p><blockquote><ul><li>订阅 topic: sub &lt;topic&gt;\r\n</li><li>退订 topic: unsub &lt;topic&gt;\r\n</li><li>发布 content: pub &lt;topic&gt;\r\n&lt;content&gt;\r\n</li></ul></blockquote><p>由于代码篇幅过长且 sub 和 pub 都可以用 telnet 来代替，因此这里只放了 hub 以及 Topic 的相关实现。，有需要的可以自行将代码下下来研究。先来看看 Topic 的实现：</p><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><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">class</span> <span class="title class_">Topic</span> : <span class="keyword">public</span> tmuduo::copyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Topic</span>(<span class="type">const</span> string&amp; topic) : <span class="built_in">topic_</span>(topic) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(<span class="type">const</span> TcpConnectionPtr&amp; conn)</span> </span>&#123;</span><br><span class="line">    audiences_.<span class="built_in">insert</span>(conn);</span><br><span class="line">    <span class="keyword">if</span> (lastPubTime_.<span class="built_in">valid</span>()) &#123;</span><br><span class="line">      conn-&gt;<span class="built_in">send</span>(<span class="built_in">makeMessage</span>());</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">/* omitted */</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">publish</span><span class="params">(<span class="type">const</span> string&amp; content, Timestamp time)</span> </span>&#123;</span><br><span class="line">    content_ = content;</span><br><span class="line">    lastPubTime_ = time;</span><br><span class="line">    string message = <span class="built_in">makeMessage</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> it = audiences_.<span class="built_in">begin</span>(); it != audiences_.<span class="built_in">end</span>(); ++it) &#123;</span><br><span class="line">      (*it)-&gt;<span class="built_in">send</span>(message);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="function">string <span class="title">makeMessage</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="string">&quot;pub &quot;</span> + topic_ + <span class="string">&quot;\r\n&quot;</span> + content_ + <span class="string">&quot;\r\n&quot;</span>; &#125;</span><br><span class="line">  string topic_;</span><br><span class="line">  string content_;</span><br><span class="line">  Timestamp lastPubTime_;</span><br><span class="line">  std::set&lt;TcpConnectionPtr&gt; audiences_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>对于 Topic 而言，关键的接口为 <code>add</code> 和 <code>publish</code>，其中 <code>add</code> 会将订阅了该 topic 的连接保存到 <code>audiences_</code> 当中，而 <code>publish</code> 则负责向 <code>audiences_</code> 中的连接发送发布消息。从框图中也可以看到在 hub 当中保存了一个 <code>topics_</code> 的 map, 用于保存所有的 <code>topic</code>,其实现相关部分如下：</p><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><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="keyword">class</span> <span class="title class_">PubSubServer</span> : tmuduo::noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">PubSubServer</span>(EventLoop* loop, <span class="type">const</span> InetAddress&amp; serverAddr)</span><br><span class="line">      : <span class="built_in">loop_</span>(loop), <span class="built_in">server_</span>(loop, serverAddr, <span class="string">&quot;PubSubServer&quot;</span>) &#123;</span><br><span class="line">    server_.<span class="built_in">setConnectionCallback</span>(</span><br><span class="line">        std::<span class="built_in">bind</span>(&amp;PubSubServer::onConnection, <span class="keyword">this</span>, _1));</span><br><span class="line">    server_.<span class="built_in">setMessageCallback</span>(</span><br><span class="line">        std::<span class="built_in">bind</span>(&amp;PubSubServer::onMessage, <span class="keyword">this</span>, _1, _2, _3));</span><br><span class="line">    loop_-&gt;<span class="built_in">runEvery</span>(<span class="number">1.0</span>, std::<span class="built_in">bind</span>(&amp;PubSubServer::timePublish, <span class="keyword">this</span>));</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="comment">/* omitted */</span></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">onMessage</span><span class="params">(<span class="type">const</span> TcpConnectionPtr&amp; conn, Buffer* buf,</span></span></span><br><span class="line"><span class="params"><span class="function">                 Timestamp receiveTime)</span> </span>&#123;</span><br><span class="line">    ParseResult result = ParseResult::kSuccess;</span><br><span class="line">    <span class="keyword">while</span> (result == ParseResult::kSuccess) &#123;</span><br><span class="line">      string cmd;</span><br><span class="line">      string topic;</span><br><span class="line">      string content;</span><br><span class="line">      result = <span class="built_in">parseMessage</span>(buf, &amp;cmd, &amp;topic, &amp;content);</span><br><span class="line">      <span class="keyword">if</span> (result == ParseResult::kSuccess) &#123;</span><br><span class="line">        <span class="keyword">if</span> (cmd == <span class="string">&quot;pub&quot;</span>) &#123;</span><br><span class="line">          <span class="built_in">doPublish</span>(conn-&gt;<span class="built_in">name</span>(), topic, content, receiveTime);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cmd == <span class="string">&quot;sub&quot;</span>) &#123;</span><br><span class="line">          LOG_INFO &lt;&lt; conn-&gt;<span class="built_in">name</span>() &lt;&lt; <span class="string">&quot; subscribes &quot;</span> &lt;&lt; topic;</span><br><span class="line">          <span class="built_in">doSubscribe</span>(conn, topic);</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cmd == <span class="string">&quot;unsub&quot;</span>) &#123;</span><br><span class="line">          <span class="built_in">doUnsubscribe</span>(conn, topic);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          conn-&gt;<span class="built_in">shutdown</span>();</span><br><span class="line">          result = ParseResult::kError;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (result == ParseResult::kError) &#123;</span><br><span class="line">        conn-&gt;<span class="built_in">shutdown</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="function"><span class="type">void</span> <span class="title">timePublish</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    Timestamp now = Timestamp::<span class="built_in">now</span>();</span><br><span class="line">    <span class="built_in">doPublish</span>(<span class="string">&quot;internal&quot;</span>, <span class="string">&quot;utc_time&quot;</span>, now.<span class="built_in">toFormattedString</span>(), now);</span><br><span class="line">  &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  EventLoop* loop_;</span><br><span class="line">  TcpServer server_;</span><br><span class="line">  std::map&lt;string, Topic&gt; topics_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在 hub 的实现上，比较关键的两个函数是 <code>onMessage</code> 和 <code>timePublish</code>，当其他节点向 hub 发送消息时，会触发 <code>onMessage</code> 函数的执行，该函数会根据消息的内容执行相应的函数，而 <code>timePublish</code> 则由定时事件进行触发。这里 <code>timePublish</code> 保留了原有的实现，如果需要发布进程信息可以直接在此进行修改。</p><h4 id="最后一点想法"><a href="#最后一点想法" class="headerlink" title="最后一点想法"></a>最后一点想法</h4><p>到了这里，基于 Observer Pattern 模式实现的进程监控也大体说完。这两种方式实现不难，只需要了解其中大体流程及 muduo 的相关用法，也可以很快地写出来。在基于 Observer Pattern 的进程监控方式中，一个比较大的问题是 hub 很容易成为 single point of failure. 关于这点，也许在 Redis 的源码中会有解决的方案。而这也是我写这篇文章的目的之一：如果未来有时间，我希望在解决这个问题后，可以直接从这篇文章中接续下去，而不必重新开始。<br>这篇文章其实并不好写，原因是由于 linux 的 procfs 的实现使得获取进程信息非常简单，如果单独写篇文章比较划不来。可如果将这两种模式总结进来，又很容易使得文章变得非常冗长。</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 网络库源码剖析系列文章的第九篇文章，主要涉及了 muduo 网络库中是如何获取进程信息，又是以什么样的方式展现给客户的，其中包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;幕后功臣 ： FileUtil 的实现&lt;/li&gt;
&lt;li&gt;一种基于 REST 风格的进程监控&lt;/li&gt;
&lt;li&gt;一种基于 Observer Pattern 实现的进程监控&lt;/li&gt;
&lt;li&gt;最后一点碎碎念&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;注：由于这两种进程监控的方法都涉及到了与其他组件的配合，例如 HttpServer、hub 等组件，碍于篇幅原因，本文中不会粘贴过多的代码，主要以图表的形式阐述。本文假定读者了解 Http 协议， Observer Pattern 以及 linux 下文件系统等相关知识。另外，为了避免代码与我之前文章代码不一致，此处涉及代码引用自 tmuduo。在进程相关方面， tmuduo 只做了及其微小的修改，与 muduo 源码差距很小。在设计上以 muduo 的思路为主，但代码还是来自于 tmuduo，望知悉。&lt;br&gt;本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#103;&amp;#105;&amp;#x74;&amp;#x40;&amp;#x67;&amp;#105;&amp;#x74;&amp;#x68;&amp;#x75;&amp;#98;&amp;#x2e;&amp;#99;&amp;#111;&amp;#109;&quot;&gt;&amp;#103;&amp;#105;&amp;#x74;&amp;#x40;&amp;#x67;&amp;#105;&amp;#x74;&amp;#x68;&amp;#x75;&amp;#98;&amp;#x2e;&amp;#99;&amp;#111;&amp;#109;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
    <category term="进程监控" scheme="https://phoenix500526.github.io/tags/%E8%BF%9B%E7%A8%8B%E7%9B%91%E6%8E%A7/"/>
    
    <category term="Observer Pattern" scheme="https://phoenix500526.github.io/tags/Observer-Pattern/"/>
    
    <category term="REST" scheme="https://phoenix500526.github.io/tags/REST/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库单例模式的思考与实践(下)</title>
    <link href="https://phoenix500526.github.io/2020/11/09/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5-%E4%B8%8B/"/>
    <id>https://phoenix500526.github.io/2020/11/09/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5-%E4%B8%8B/</id>
    <published>2020-11-09T14:17:27.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 网络库源码剖析系列文章的第八篇文章，是对前一篇文章的一个补充。另外需要注意，本文假定读者已经了解了 SFINAE 的基本含义，因此没有花费笔墨具体讨论什么是 SFINAE，如果读者不知道什么是 SFINAE ，建议先阅读知乎大佬：空明流转的文章 <a href="https://zhuanlan.zhihu.com/p/21314708">《C++模板进阶指南：SFINAE》</a>，以便对 SFINAE 有基本的了解。<br>本文主要包含以下内容</p><blockquote><ul><li>SFINAE —— muduo Singleton 中 has_no_destroy 的启示</li><li><code>has_no_destroy</code> 的进阶玩法 —— 如何判断一个类的基类子类型中是否声明了某个函数<ul><li><code>has_no_destroy</code> 为何无法判断子类中基类方法</li><li>从一个简单的例子讲起 —— 从编译错误中得到的启示</li><li><code>has_no_destroy</code> 的进阶实现与测试</li></ul></li></ul></blockquote><p>本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#x67;&#105;&#x74;&#x40;&#103;&#105;&#116;&#104;&#x75;&#x62;&#46;&#x63;&#111;&#x6d;">&#x67;&#105;&#x74;&#x40;&#103;&#105;&#116;&#104;&#x75;&#x62;&#46;&#x63;&#111;&#x6d;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><h4 id="SFINAE-——-muduo-Singleton-中-has-no-destroy-的启示"><a href="#SFINAE-——-muduo-Singleton-中-has-no-destroy-的启示" class="headerlink" title="SFINAE —— muduo Singleton 中 has_no_destroy 的启示"></a>SFINAE —— muduo Singleton 中 has_no_destroy 的启示</h4><p>再讲完了 tmuduo 中单例模式的实现后，我们来看看 muduo 中 Singleton 使用到的另一个技术 —— SFINAE(Substitution failure is not an error)。我们先来看看 Singleton 中 <code>has_no_destroy</code> 的实现：</p><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">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">has_no_destroy</span>&#123;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> C&gt; <span class="function"><span class="type">static</span> <span class="type">char</span> <span class="title">test</span><span class="params">(<span class="keyword">decltype</span>(&amp;C::no_destroy))</span></span>;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> C&gt; <span class="function"><span class="type">static</span> <span class="type">int32_t</span> <span class="title">test</span><span class="params">(...)</span></span>;</span><br><span class="line">  <span class="type">const</span> <span class="type">static</span> <span class="type">bool</span> value = <span class="built_in">sizeof</span>(<span class="built_in">test</span>&lt;T&gt;(<span class="number">0</span>)) == <span class="number">1</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如前言所述，<code>has_no_destroy</code> 的作用是用来判断一个类中是否声明了 <code>no_destroy</code> 函数。我们先来看看它的具体用法：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">no_destroy</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">no_destroy</span><span class="params">(<span class="type">int</span>)</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C</span> : A&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">D</span>&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether A has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;A&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;  </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether B has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;B&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;   </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether C has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;C&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;  </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether D has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;D&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行结果：</p><blockquote><p>Whether A has no_destroy or not? yes<br>Whether B has no_destroy or not? yes<br>Whether C has no_destroy or not? no<br>Whether D has no_destroy or not? no</p></blockquote><p>在讲述 <code>has_no_destroy</code> 的原理前，我们先看下面的代码：</p><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int32_t</span> <span class="title">foo1</span><span class="params">(<span class="type">int</span>)</span></span>;</span><br><span class="line"><span class="function"><span class="type">char</span> <span class="title">foo2</span><span class="params">(<span class="type">void</span>*)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;sizeof foo1(0) is &quot;</span> &lt;&lt; <span class="built_in">sizeof</span>(<span class="built_in">foo1</span>(<span class="number">0</span>)) &lt;&lt; endl;   </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;sizeof foo2(0) is &quot;</span> &lt;&lt; <span class="built_in">sizeof</span>(<span class="built_in">foo2</span>(<span class="number">0</span>)) &lt;&lt; endl;   </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行结果:</p><blockquote><p>sizeof foo1(0) is 4<br>sizeof foo2(0) is 1</p></blockquote><p>在上述代码中，我们声明了两个函数: <code>foo1</code> 和 <code>foo2</code>。注意这两个函数都是只声明未定义。当我们执行 <code>sizeof(foo1(0))</code> 时，实际上是在检测 <code>foo1</code> 返回值类型的大小。这里简单提一下，编译器会在编译时使用 <code>sizeof(int32_t)</code> 代替 <code>sizeof(foo1(0))</code> 进行求值。虽然 <code>sizeof(foo1(0))</code> 看似产生了函数调用，实则不然。由于编译器没有编译出函数调用的命令，所以链接器并不会去查找 <code>foo1</code> 的定义，也就不会产生任何错误。</p><p>让我们重新看看 <code>has_no_destroy</code> 的实现：</p><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">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">has_no_destroy</span>&#123;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> C&gt; <span class="function"><span class="type">static</span> <span class="type">char</span> <span class="title">test</span><span class="params">(<span class="keyword">decltype</span>(&amp;C::no_destroy))</span></span>;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> C&gt; <span class="function"><span class="type">static</span> <span class="type">int32_t</span> <span class="title">test</span><span class="params">(...)</span></span>;</span><br><span class="line">  <span class="type">const</span> <span class="type">static</span> <span class="type">bool</span> value = <span class="built_in">sizeof</span>(<span class="built_in">test</span>&lt;T&gt;(<span class="literal">nullptr</span>)) == <span class="number">1</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在上述代码中，两个 <code>test</code> 函数都只是只声明未定义。当我们查看 <code>has_no_destroy&lt;A&gt;::value</code> 的值时，会先执行 <code>has_no_destroy&lt;A&gt;</code> 的实例化。由于 A 中声明了 <code>no_destroy</code> 函数，根据模板实例化的原则，函数 <code>static char test(decltype(&amp;C::no_destroy))</code>是最佳匹配，因此会实例化该函数，此时 <code>test&lt;T&gt;(nullptr)</code> 的返回值便是 char 类型，大小为 1 ，故 value 的值为 true。当执行 <code>has_no_destroy&lt;D&gt;::value</code>，同样会执行 <code>has_no_destroy&lt;D&gt;</code> 的实例化。由于 D 中没有声明 <code>no_destroy</code> 函数，因此只能实例化 <code>static int32_t test(...)</code> 函数，其返回值为 <code>int32_t</code>, 故 value 的值为 false(…代表接受任意数量任意类型的参数)。这样我们就实现了一种能够判断类中是否具有成员函数 <code>no_destroy</code> 的方法。</p><h4 id="has-no-destroy-的进阶玩法-——-如何判断一个类的基类子类型中是否声明了某个函数"><a href="#has-no-destroy-的进阶玩法-——-如何判断一个类的基类子类型中是否声明了某个函数" class="headerlink" title="has_no_destroy 的进阶玩法 —— 如何判断一个类的基类子类型中是否声明了某个函数"></a><code>has_no_destroy</code> 的进阶玩法 —— 如何判断一个类的基类子类型中是否声明了某个函数</h4><h6 id="has-no-destroy-为何无法判断子类中基类方法"><a href="#has-no-destroy-为何无法判断子类中基类方法" class="headerlink" title="has_no_destroy 为何无法判断子类中基类方法"></a><code>has_no_destroy</code> 为何无法判断子类中基类方法</h6><p>从前面的实例当中我们可以看出，<code>has_no_destroy</code> 不能用于判断具有子类中是否具有基类的 <code>no_destroy</code> 方法，例如前面例子中的 C 类型。有没有什么办法能够增强 <code>has_no_destroy</code> 的功能，使其既能保持当前的用法不变，同时又能自动判断子类中的基类子类型是否包含 <code>no_destroy</code> 函数。</p><p>我们先看看为什么 <code>has_no_destroy</code> 无法干这个活。<code>has_no_destroy</code> 的基本工作原理，是利用了模板的匹配原则，如果 C 中恰好声明了函数 <code>no_destroy</code>,那么函数 <code>static char test(decltype(&amp;C::no_destroy))</code> 将会得到实例化。但是当子类型中包含了基类函数时，以上述的 C 为例子，C 中虽然包含了从基类 A 中继承来的成员函数 <code>no_destroy</code>，但其函数签名应当是 <code>A::no_destroy</code> 而非　<code>no_destroy</code>。当我们对 C 实例化对象调用 <code>no_destroy</code> 函数时，编译器在 C 自身类型中找不到对应 <code>no_destroy</code> 函数，便会去基类子类型中查找 <code>no_destroy</code> 函数。<strong>为了便于理解，你可以将这个过程看成是一次隐式类型转换：编译器将 <code>no_destroy</code> 转换成了 <code>A::no_destroy</code>。不过由于模板自身有类型推导规则，因此编译器不会为 <code>has_no_destroy</code> 执行这个转换工作。</strong>我们需要自己来。<br>显然现在摆在我们面前的只有两条路，要么多增加一个模板类型参数，让用户把基类类型传进来，然后在 <code>has_no_destroy</code> 中做手动转换；要么反过来，让所有没有定义 <code>no_destroy</code> 的函数匹配成功，然后对 value 取反就可以。第一条路非常不好走，一方面是因为我之前说明了，我希望做一个 <code>has_no_destroy</code> 的增强版，因此我不希望增加多余的类型参数，另一方面，这种做法对于没有继承体系的类非常不友好，甚至来说是错误的存在。因此我选择了第二种做法，不仅能保持 <code>has_no_destroy</code> 的基本用法不变，而且对带继承或不带继承的类型等同视之，能够简化判断的策略。</p><h6 id="从一个简单例子讲起-——-从编译错误中得到的启示"><a href="#从一个简单例子讲起-——-从编译错误中得到的启示" class="headerlink" title="从一个简单例子讲起 —— 从编译错误中得到的启示"></a>从一个简单例子讲起 —— 从编译错误中得到的启示</h6><p>既然明确了要采用反证的思想来解决这个问题，我们希望所有定义了 <code>no_destroy</code> 函数的类型均无法正确匹配。在这之前先用一个例子来说明一下我的思路：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">no_destroy</span><span class="params">()</span></span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;A::no_destroy()&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">no_destroy</span><span class="params">()</span></span>&#123;</span><br><span class="line">        cout &lt;&lt; <span class="string">&quot;B::no_destroy()&quot;</span> &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C</span>: <span class="keyword">public</span> A&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">D</span>: <span class="keyword">public</span> A, <span class="keyword">public</span> B&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">F</span>: <span class="keyword">public</span> C, <span class="keyword">public</span> B&#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    C c;</span><br><span class="line">    c.<span class="built_in">no_destroy</span>();</span><br><span class="line">    D d;</span><br><span class="line">    <span class="comment">//d.no_destroy(): // error: member &#x27;no_destroy&#x27; found in multiple base classes of different types</span></span><br><span class="line">    F f;</span><br><span class="line">    <span class="comment">//f.no_destroy(): // error: member &#x27;no_destroy&#x27; found in multiple base classes of different types</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，D 由于同时继承 A 和 B，因此当我们执行 <code>d.no_destroy()</code>函数时，会产生二义性错误(f 亦同理)。反观 C，由于 C 只继承了 A，因此当执行 <code>c.no_destroy()</code>， 编译器在 C 的类型中找不到 <code>no_destroy</code> 的定义时，会自动到 C 的基类子类型中寻找。我们可以模仿这种做法，<strong>先在 <code>has_no_destroy</code> 中定义一个声明了 <code>no_destroy</code> 函数 <code>Base</code> 作为基类，然后让类型 T 也作为基类，并让 Derive 作为二者的联合派生类。接着在对 Derive 中的 <code>no_destroy</code> 进行模板类型推导。如果 T 中或 T 的基类子类型中包含了 <code>no_destroy</code> 类型，那么都会产生二义性错误从而导致相应的函数模板实例化失效。</strong></p><h6 id="进阶-has-no-destroy-的实现与测试"><a href="#进阶-has-no-destroy-的实现与测试" class="headerlink" title="进阶 has_no_destroy 的实现与测试"></a>进阶 <code>has_no_destroy</code> 的实现与测试</h6><p>讲完了上述想法后，我们可以开始着手 <code>has_no_destroy</code> 进阶版的实现：</p><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><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"><span class="comment">//SFINAE.h</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt; </span><br><span class="line"><span class="keyword">class</span> <span class="title class_">has_no_destroy</span></span><br><span class="line">&#123; </span><br><span class="line">   <span class="keyword">using</span> yes = <span class="type">char</span>;</span><br><span class="line">   <span class="keyword">using</span> no = <span class="type">int32_t</span>;</span><br><span class="line">   <span class="keyword">struct</span> <span class="title class_">Base</span> </span><br><span class="line">   &#123; </span><br><span class="line">     <span class="function"><span class="type">void</span> <span class="title">no_destroy</span><span class="params">()</span></span>;</span><br><span class="line">   &#125;; </span><br><span class="line">   <span class="keyword">struct</span> <span class="title class_">Derive</span> : <span class="keyword">public</span> T, <span class="keyword">public</span> Base &#123;&#125;; </span><br><span class="line">   <span class="keyword">template</span> &lt;<span class="keyword">typename</span> C, C&gt;  <span class="keyword">class</span> <span class="title class_">Helper</span>&#123;&#125;; </span><br><span class="line">   <span class="keyword">template</span> &lt;<span class="keyword">typename</span> U&gt; </span><br><span class="line">   <span class="function"><span class="type">static</span> no <span class="title">test</span><span class="params">(U*, Helper&lt;<span class="keyword">decltype</span>(&amp;Base::no_destroy), &amp;U::no_destroy&gt;* = <span class="literal">nullptr</span>)</span></span>; </span><br><span class="line">   <span class="function"><span class="type">static</span> yes <span class="title">test</span><span class="params">(...)</span></span>; </span><br><span class="line"><span class="keyword">public</span>: </span><br><span class="line">   <span class="type">static</span> <span class="type">const</span> <span class="type">bool</span> value = <span class="built_in">sizeof</span>(yes) == <span class="built_in">sizeof</span>(<span class="built_in">test</span>(<span class="built_in">static_cast</span>&lt;Derive*&gt;(<span class="literal">nullptr</span>))); </span><br><span class="line">&#125;; </span><br></pre></td></tr></table></figure><p>在上述代码中，由于采用了反证的思想，逻辑比较绕，我采用了更有意义的类型别名 <code>yes</code> 和 <code>no</code> 来描述结果。其中 <code>Helper</code> 是用来辅助模板类型推断的辅助类模板。 不管是 U 还是 U 的基类子类型中，只要声明了 <code>no_destroy</code>，都会因产生二义性错误而导致 <code>Helper</code> 实例化失败，进而导致 <code>static no test</code> 实例化失败。 此时 <code>value</code> 中对 <code>test</code> 的调用将会匹配到 <code>static yes test</code>。如果 U 或者 U 的基类子类型中没有声明 <code>no_destroy</code> 函数，则 <code>static no test</code> 将会实例化成功，根据最佳匹配原则，<code>value</code> 中对 <code>test</code> 的调用将会匹配到 <code>static no test</code>,这样我们便实现了区分<br>以下是我对进阶版 <code>has_no_destroy</code> 的测试。<br>测试代码：</p><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><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="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;SFINAE.h&quot;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">no_destroy</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> : A &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">C</span> &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether A has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;A&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;  </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether B has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;B&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;   </span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Whether C has no_destroy or not? &quot;</span> &lt;&lt; (has_no_destroy&lt;C&gt;::value ? <span class="string">&quot;yes&quot;</span> : <span class="string">&quot;no&quot;</span>) &lt;&lt; endl;  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>测试结果：</p><blockquote><p>Whether A has no_destroy or not? yes<br>Whether B has no_destroy or not? yes<br>Whether C has no_destroy or not? no</p></blockquote><p>关于进阶版的 <code>has_no_destroy</code> 的实现代码我放在了 tmuduo/base/Singleton.h 当中，不过由于这个功能仅是兴趣，没有涉及到 tmuduo 本身的使用，我没有将测试代码添加到 tmuduo/test 当中，测试本身不复杂，有需要的可以自己实现。</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 网络库源码剖析系列文章的第八篇文章，是对前一篇文章的一个补充。另外需要注意，本文假定读者已经了解了 SFINAE 的基本含义，因此没有花费笔墨具体讨论什么是 SFINAE，如果读者不知道什么是 SFINAE ，建议先阅读知乎大佬：空明流转的文章 &lt;a href=&quot;https://zhuanlan.zhihu.com/p/21314708&quot;&gt;《C++模板进阶指南：SFINAE》&lt;/a&gt;，以便对 SFINAE 有基本的了解。&lt;br&gt;本文主要包含以下内容&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;SFINAE —— muduo Singleton 中 has_no_destroy 的启示&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_no_destroy&lt;/code&gt; 的进阶玩法 —— 如何判断一个类的基类子类型中是否声明了某个函数&lt;ul&gt;
&lt;li&gt;&lt;code&gt;has_no_destroy&lt;/code&gt; 为何无法判断子类中基类方法&lt;/li&gt;
&lt;li&gt;从一个简单的例子讲起 —— 从编译错误中得到的启示&lt;/li&gt;
&lt;li&gt;&lt;code&gt;has_no_destroy&lt;/code&gt; 的进阶实现与测试&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#x67;&amp;#105;&amp;#x74;&amp;#x40;&amp;#103;&amp;#105;&amp;#116;&amp;#104;&amp;#x75;&amp;#x62;&amp;#46;&amp;#x63;&amp;#111;&amp;#x6d;&quot;&gt;&amp;#x67;&amp;#105;&amp;#x74;&amp;#x40;&amp;#103;&amp;#105;&amp;#116;&amp;#104;&amp;#x75;&amp;#x62;&amp;#46;&amp;#x63;&amp;#111;&amp;#x6d;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
    <category term="SFINAE" scheme="https://phoenix500526.github.io/tags/SFINAE/"/>
    
    <category term="泛型编程" scheme="https://phoenix500526.github.io/tags/%E6%B3%9B%E5%9E%8B%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库单例模式的思考与实践(上)</title>
    <link href="https://phoenix500526.github.io/2020/11/09/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5-%E4%B8%8A/"/>
    <id>https://phoenix500526.github.io/2020/11/09/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5-%E4%B8%8A/</id>
    <published>2020-11-09T14:16:56.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 网络库源码剖析系列文章的第七篇文章，主要涉及了对 muduo 网络库中单例模式的分析，以及 C++11 标准下一种更加简洁的单例模式实现，其中包含了以下内容：</p><blockquote><ul><li>对 muduo 网络库下单例模式的思考<ul><li>线程全局单例模式 Singleton 的实现</li><li>线程局部单例模式 ThreadLocalSingleton 的实现</li></ul></li><li>tmuduo 中单例模式的实现</li></ul></blockquote><p>本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#103;&#105;&#116;&#x40;&#x67;&#x69;&#x74;&#104;&#x75;&#x62;&#x2e;&#x63;&#x6f;&#x6d;">&#103;&#105;&#116;&#x40;&#x67;&#x69;&#x74;&#104;&#x75;&#x62;&#x2e;&#x63;&#x6f;&#x6d;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><h4 id="对-muduo-网络库下单例模式的思考"><a href="#对-muduo-网络库下单例模式的思考" class="headerlink" title="对 muduo 网络库下单例模式的思考"></a>对 muduo 网络库下单例模式的思考</h4><h6 id="Singleton-的实现"><a href="#Singleton-的实现" class="headerlink" title="Singleton 的实现"></a>Singleton 的实现</h6><p>在 muduo 网络库的源代码中，单例模式分为两种，一种是线程全局单例模式，另一种是线程局部单例模式，相关的代码分别存放在 base/Singleton.h 和 base/ThreadLocalSingleton.h 文件中。我们先看下 Singleton 的实现</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//Singleton.h</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Singleton</span> : noncopyable&#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Singleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  ~<span class="built_in">Singleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="function"><span class="type">static</span> T&amp; <span class="title">instance</span><span class="params">()</span></span></span><br><span class="line"><span class="function">  </span>&#123;</span><br><span class="line">    <span class="built_in">pthread_once</span>(&amp;ponce_, &amp;Singleton::init);</span><br><span class="line">    <span class="built_in">assert</span>(value_ != <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">return</span> *value_;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">init</span><span class="params">()</span></span></span><br><span class="line"><span class="function">  </span>&#123;</span><br><span class="line">    value_ = <span class="keyword">new</span> <span class="built_in">T</span>();</span><br><span class="line">    <span class="keyword">if</span> (!detail::has_no_destroy&lt;T&gt;::value)</span><br><span class="line">    &#123;</span><br><span class="line">      ::<span class="built_in">atexit</span>(destroy);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">destroy</span><span class="params">()</span></span></span><br><span class="line"><span class="function">  </span>&#123;</span><br><span class="line">    <span class="comment">// T_must_be_complete_type 的作用是检测 T 是不是不完全类型</span></span><br><span class="line">    <span class="comment">// 若 T 是不完全类型(仅有声明没有定义)，则 sizeof(T) = 0</span></span><br><span class="line">    <span class="comment">// 则此时 T_must_be_complete_type 相当于 char[-1]</span></span><br><span class="line">    <span class="comment">// 利用此类型定义了一个 dummy 对象会引发变异错误。</span></span><br><span class="line">    <span class="keyword">typedef</span> <span class="type">char</span> T_must_be_complete_type[<span class="built_in">sizeof</span>(T) == <span class="number">0</span> ? <span class="number">-1</span> : <span class="number">1</span>];</span><br><span class="line">    T_must_be_complete_type dummy; (<span class="type">void</span>) dummy;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">delete</span> value_;</span><br><span class="line">    value_ = <span class="literal">NULL</span>;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="type">static</span> <span class="type">pthread_once_t</span> ponce_;</span><br><span class="line">  <span class="type">static</span> T*             value_;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="type">pthread_once_t</span> Singleton&lt;T&gt;::ponce_ = PTHREAD_ONCE_INIT;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">T* Singleton&lt;T&gt;::value_ = <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure><p>从上述代码可以看出，Singleton 是一个模板类，且用户只能调用该类的静态成员函数<code>instance</code>。当用户调用 <code>Singleton&lt;T&gt;::instance()</code> 时，muduo 调用 <code>Singleton&lt;T&gt;::init</code> 进行 T 的初始化，而 <code>pthread_once</code> 保证了这一过程仅会在首次调用 <code>instance</code> 方法时发生。对于其中 <code>has_no_destroy</code> 的具体实现，我们先按下不表，留到下一篇文章再回来讨论。现在只需要知道， <code>has_no_destroy&lt;T&gt;::value</code> 是用来判断类型 T 当中是否声明了函数 <code>no_destroy</code> 即可。因此，如果 T 中没有声明 <code>no_destroy</code> 函数，那么 <code>init</code> 会调用 <code>atexit</code> 将 <code>destroy</code> 注册到进程当中，这样当进程退出时就会自动执行后 <code>destroy</code> 进行对象的析构工作。心细的同学应该很容易发现，<strong>如果单例对象中声明了 <code>no_destroy</code> 函数，那毫无疑问地会导致内存的泄露</strong>。</p><h6 id="ThreadLocalSingleton-的实现"><a href="#ThreadLocalSingleton-的实现" class="headerlink" title="ThreadLocalSingleton 的实现"></a>ThreadLocalSingleton 的实现</h6><p>在大体知道了 Singleton 的实现后，我们再来看看 ThreadLocalSingleton 的实现：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ThreadLocalSingleton</span> : noncopyable&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">ThreadLocalSingleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  ~<span class="built_in">ThreadLocalSingleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="function"><span class="type">static</span> T&amp; <span class="title">instance</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!t_value_)</span><br><span class="line">    &#123;</span><br><span class="line">      t_value_ = <span class="keyword">new</span> <span class="built_in">T</span>();</span><br><span class="line">      deleter_.<span class="built_in">set</span>(t_value_);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> *t_value_;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">static</span> T* <span class="title">pointer</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> t_value_;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">destructor</span><span class="params">(<span class="type">void</span>* obj)</span></span>&#123;</span><br><span class="line">    <span class="built_in">assert</span>(obj == t_value_);</span><br><span class="line">    <span class="keyword">typedef</span> <span class="type">char</span> T_must_be_complete_type[<span class="built_in">sizeof</span>(T) == <span class="number">0</span> ? <span class="number">-1</span> : <span class="number">1</span>];</span><br><span class="line">    T_must_be_complete_type dummy; (<span class="type">void</span>) dummy;</span><br><span class="line">    <span class="keyword">delete</span> t_value_;</span><br><span class="line">    t_value_ = <span class="number">0</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">class</span> <span class="title class_">Deleter</span>&#123;</span><br><span class="line">   <span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Deleter</span>()&#123;</span><br><span class="line">      <span class="built_in">pthread_key_create</span>(&amp;pkey_, &amp;ThreadLocalSingleton::destructor);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">Deleter</span>()&#123;</span><br><span class="line">      <span class="built_in">pthread_key_delete</span>(pkey_);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">set</span><span class="params">(T* newObj)</span></span>&#123;</span><br><span class="line">      <span class="built_in">assert</span>(<span class="built_in">pthread_getspecific</span>(pkey_) == <span class="literal">NULL</span>);</span><br><span class="line">      <span class="built_in">pthread_setspecific</span>(pkey_, newObj);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">pthread_key_t</span> pkey_;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="type">static</span> __thread T* t_value_;</span><br><span class="line">  <span class="type">static</span> Deleter deleter_;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">__thread T* ThreadLocalSingleton&lt;T&gt;::t_value_ = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">typename</span> ThreadLocalSingleton&lt;T&gt;::Deleter ThreadLocalSingleton&lt;T&gt;::deleter_;</span><br></pre></td></tr></table></figure><p>在上述代码中，我们可以看到，ThreadLocalSingleton 也是一个模板类，用户只能调用类中的静态方法 <code>instance</code> 以及 <code>pointer</code>。由于和 Singleton 的应用场景不同， ThreadLocalSingleton 只需要通过判断 <code>t_value_</code> 是否为空，就可以实现仅在首次调用 <code>instance</code> 方法进行初始化。基本的流程和前面 Singleton 是差不多的，其中最大的不同在于 ThreadLocalSingleton 多了一个 Deleter class，这是因为 <code>t_value_</code> 由 GCC 关键字 <code>__thread</code> 所修饰，因此不能自动调用析构函数，需要额外定义一个 Deleter 来完成这份工作。<code>deleter_</code> 实际上相当于一个 RAII 对象，在调用 <code>instance</code> 时将 <code>t_value_</code> 交由 <code>deleter_</code> 保管，当线程退出时析构 <code>deleter_</code> 时，会执行 <code>ThreadLocalSingleton::destructor</code> 来析构 <code>t_value_</code></p><h4 id="tmuduo-中单例模式的实现"><a href="#tmuduo-中单例模式的实现" class="headerlink" title="tmuduo 中单例模式的实现"></a>tmuduo 中单例模式的实现</h4><p>在讨论 tmuduo 如何实现单例模式之前，我们需要先思考一个问题，一个单例对象需要符合哪些特质才能算得上是一个合格的单例？我想至少有以下几点：</p><blockquote><ul><li>该对象必定是不可拷贝的</li><li>用户只能够通过 instance 方法获得唯一的实例，且在若干次调用 instance() 方法中，仅会在首次调用时进行初始化</li><li>必须是资源安全的，在程序退出时必须能够正确地析构，不会造成任何的资源泄露</li><li>若是线程全局单例模式，则还需要考虑线程安全的问题。若是线程局部单例模式，则需要考虑线程局部存储的问题</li></ul></blockquote><p>第一个问题非常好解决，我们只需要继承 noncopyable 标签类即可。真正需要注意的其实是第二和第三个问题。<br>如果看过我之前的文章 《对 muduo 网络库中的线程模型的思考与实践》，应该还记得其中提到了三个变量存储期概念，分别是 static stroage duration，thread storage duration 以及 dynamic storage duration。任何被声明为 static 或 thread_local 的局部变量具有 static storage duration 或 thread storage duration(这里的或指的是互斥或而非逻辑或，以下或如未特殊声明，均代表互斥或)，且他们都不被分配在堆内存上。<br>另外，在 C++11 的标准中，关于被声明为 <code>static</code> 或 <code>thread_local</code> 的局部变量，有如下的规定：</p><blockquote><p>Variables declared at block scope with the specifier static or thread_local (since C++11) have static or thread (since C++11) storage duration but are <strong>initialized the first time control passes through their declaration (unless their initialization is zero- or constant-initialization, which can be performed before the block is first entered). On all further calls, the declaration is skipped.</strong></p><p>If the initialization throws an exception, the variable is not considered to be initialized, and initialization will be attempted again the next time control passes through the declaration.</p><p>If the initialization recursively enters the block in which the variable is being initialized, the behavior is undefined.</p><p><strong>If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once</strong> (similar behavior can be obtained for arbitrary functions with std::call_once).</p></blockquote><p>上述 C++11 标准保证了 <code>static</code> 或 <code>thread_local</code> 修饰局部变量只会在程序的控制流首次进入相应的块作用域才进行实例化工作，而且在实例化成功后，在程序或线程退出时能够自动调用相应的析构函数。另外，由于 <code>static</code> 或 <code>thread_local</code> 修饰的局部变量并不存储在堆内存当中，因此自然也不会造成任何的内存泄露。最后，对于线程全局单例模式，标准保证了 static local variable 的初始化是线程安全的，而 thread_local 本身就代表了线程局部存储。有了 C++11 标准的保驾护航，第二、第三和第四个问题将不再是问题，我们可以以一种非常简洁的方式来实现 tmuduo 中的单例模式</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Singleton.h 的实现</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Singleton</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Singleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  ~<span class="built_in">Singleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="function"><span class="type">static</span> T&amp; <span class="title">instance</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="type">static</span> T instance;</span><br><span class="line">    <span class="keyword">return</span> instance;</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">// ThreadLocalSingleton.h 的实现</span></span><br><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ThreadLocalSingleton</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">ThreadLocalSingleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  ~<span class="built_in">ThreadLocalSingleton</span>() = <span class="keyword">delete</span>;</span><br><span class="line">  <span class="function"><span class="type">static</span> T&amp; <span class="title">instance</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">thread_local</span> T instance;</span><br><span class="line">    <span class="keyword">return</span> instance;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>为了验证 Singleton 以及 ThreadLocalSingleton 的正确性，我在自身代码的基础上运行 muduo 中的测试。与 muduo 本身的测试结果相当，具体测试代码可见:tmuduo/test/Singleton_test.cc 以及 tmuduo/test/ThreadLocalSingleton_test.cc</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 网络库源码剖析系列文章的第七篇文章，主要涉及了对 muduo 网络库中单例模式的分析，以及 C++11 标准下一种更加简洁的单例模式实现，其中包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;对 muduo 网络库下单例模式的思考&lt;ul&gt;
&lt;li&gt;线程全局单例模式 Singleton 的实现&lt;/li&gt;
&lt;li&gt;线程局部单例模式 ThreadLocalSingleton 的实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;tmuduo 中单例模式的实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#103;&amp;#105;&amp;#116;&amp;#x40;&amp;#x67;&amp;#x69;&amp;#x74;&amp;#104;&amp;#x75;&amp;#x62;&amp;#x2e;&amp;#x63;&amp;#x6f;&amp;#x6d;&quot;&gt;&amp;#103;&amp;#105;&amp;#116;&amp;#x40;&amp;#x67;&amp;#x69;&amp;#x74;&amp;#104;&amp;#x75;&amp;#x62;&amp;#x2e;&amp;#x63;&amp;#x6f;&amp;#x6d;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="C++" scheme="https://phoenix500526.github.io/tags/C/"/>
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
    <category term="Singleton" scheme="https://phoenix500526.github.io/tags/Singleton/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库线程池的思考与实践</title>
    <link href="https://phoenix500526.github.io/2020/11/06/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/"/>
    <id>https://phoenix500526.github.io/2020/11/06/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/</id>
    <published>2020-11-06T14:51:42.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 源码剖析系列的第六篇文章，主要探讨了 tmuduo 网络库的线程池模型，其中包含了以下内容：</p><blockquote><ul><li>为什么要重新设计 ThreadPool</li><li>SyncQueue 的实现</li><li>ThreadPool 的实现</li></ul></blockquote><p>本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#x67;&#x69;&#116;&#x40;&#x67;&#x69;&#116;&#104;&#117;&#98;&#46;&#99;&#x6f;&#109;">&#x67;&#x69;&#116;&#x40;&#x67;&#x69;&#116;&#104;&#117;&#98;&#46;&#99;&#x6f;&#109;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><h4 id="tmuduo-线程池的设计"><a href="#tmuduo-线程池的设计" class="headerlink" title="tmuduo 线程池的设计"></a>tmuduo 线程池的设计</h4><p>如果你看过我之前的文章，那么很容易发现这篇文章和之前的文章不太一样。之前的文章大部分都是谈论 muduo 相应部分的设计，然后提出我自己的想法并实践，最终将实践结果,也就是 tmuduo 的代码，展示出来。而这篇文章跳过 muduo 网络库设计的部分，直接谈论了 tmuduo 网络库中的 ThreadPool 设计，主要原因有二：</p><blockquote><ul><li>muduo 网络库中的线程池依赖于 Thread 的实现，而 tmuduo 中的 Thread 采用和 muduo 不一样的实现方式，在这样子的前提下去修改 ThreadPool 的代码，改出来的东西完全是四不像。</li><li>muduo 网络库中的 ThreadPool 有点不太符合我的编程美学。muduo 网络库中 ThreadPool 采用 deque 作为任务队列，把任务队列的所有同步工作一起放到了 ThreadPool 中去完成。这种做法让我感到不太舒服，于是我在 tmuduo 网络库中实现了一个 SyncQueue，将同步工作移交给 SyncQueue，而 ThreadPool 只需要使用即可。</li></ul></blockquote><p>基于以上两个理由，我决定脱离 muduo 的 ThreadPool 限制，实现一个自己版本的 ThreadPool，但为了能融入 tmuduo 的整体框架之中，我还是保留了 muduo 的 ThreadPool 测试代码，并让 tmuduo 的 ThreadPool 通过了该测试。我自己对 tmuduo 的线程池部分有一个未实现的想法：那就是看看是否能引入类似于 skynet 网络框架的设计，为线程池中的不同线程分配不同的优先级，并让他们根据优先级来取出不同数量的任务(有点类似于操作系统的优先级队列轮转调度算法)，这样可以更好地利用到 CPU 的资源。不过由于时间和精力的原因，这个想法还没有明确的时间计划，以后有机会再补充吧。</p><p>线程池在实现上大概可分为两种，一种是半同步半异步线程池，而另一种则是领导者追随者线程池。tmuduo 的 ThreadPool 属于前者，本质上也就是个生产者消费者模型。大体可分两层：同步服务层和异步服务层。上层会调用线程池的相应接口，将任务添加到同步服务层中，而线程池中的线程则属于异步服务层，它们会在空闲的时候异步地从同步队列中取出任务来执行。</p><h4 id="同步队列-SyncQueue-的实现"><a href="#同步队列-SyncQueue-的实现" class="headerlink" title="同步队列 SyncQueue 的实现"></a>同步队列 SyncQueue 的实现</h4><p>同步队列的代码主要放在 base/SyncQueue.h 当中</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SyncQueue</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">SyncQueue</span>(<span class="type">int</span> maxsize)</span><br><span class="line">      : <span class="built_in">mutex_</span>(), <span class="built_in">full_</span>(), <span class="built_in">empty_</span>(), <span class="built_in">maxSize_</span>(maxsize), <span class="built_in">running_</span>(<span class="literal">true</span>) &#123;&#125;</span><br><span class="line">  ~<span class="built_in">SyncQueue</span>() = <span class="keyword">default</span>;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">put</span><span class="params">(<span class="type">const</span> T&amp; x)</span> </span>&#123; <span class="built_in">add</span>(x); &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">put</span><span class="params">(T&amp;&amp; x)</span> </span>&#123; <span class="built_in">add</span>(std::forward&lt;T&gt;(x)); &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">take</span><span class="params">(T&amp; t)</span> </span>&#123;</span><br><span class="line">    <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">    <span class="keyword">while</span> (queue_.<span class="built_in">empty</span>() &amp;&amp; running_) &#123;</span><br><span class="line">      empty_.<span class="built_in">wait</span>(lock);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!running_) <span class="keyword">return</span>;</span><br><span class="line">    t = queue_.<span class="built_in">front</span>();</span><br><span class="line">    queue_.<span class="built_in">pop_front</span>();</span><br><span class="line">    full_.<span class="built_in">notify_one</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="type">size_t</span> <span class="title">size</span><span class="params">()</span> <span class="type">const</span> </span>&#123;</span><br><span class="line">    <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">    <span class="keyword">return</span> queue_.<span class="built_in">size</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">stop</span><span class="params">()</span> NO_THREAD_SAFETY_ANALYSIS </span>&#123;</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">      running_ = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    full_.<span class="built_in">notify_all</span>();</span><br><span class="line">    empty_.<span class="built_in">notify_all</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">typename</span> F&gt;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">add</span><span class="params">(F&amp;&amp; x)</span> </span>&#123;</span><br><span class="line">    <span class="function">UniqueLock <span class="title">lock</span><span class="params">(mutex_)</span></span>;</span><br><span class="line">    <span class="keyword">while</span> (queue_.<span class="built_in">size</span>() &gt;= maxSize_ &amp;&amp; running_) &#123;</span><br><span class="line">      full_.<span class="built_in">wait</span>(lock);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!running_) <span class="keyword">return</span>;</span><br><span class="line">    queue_.<span class="built_in">push_back</span>(std::forward&lt;F&gt;(x));</span><br><span class="line">    empty_.<span class="built_in">notify_one</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  std::list&lt;T&gt; queue_;</span><br><span class="line">  <span class="keyword">mutable</span> Mutex mutex_;</span><br><span class="line">  <span class="function">Condition full_ <span class="title">GUARDED_BY</span><span class="params">(mutex_)</span></span>;   <span class="comment">//同步队列已满</span></span><br><span class="line">  <span class="function">Condition empty_ <span class="title">GUARDED_BY</span><span class="params">(mutex_)</span></span>;  <span class="comment">//同步队列已空</span></span><br><span class="line">  <span class="type">int</span> maxSize_;</span><br><span class="line">  <span class="type">bool</span> running_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>SyncQueue 整体的实现也不复杂，如果看了之前的文章：<a href="/2020/11/05/clang-%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E6%B3%A8%E8%A7%A3TSA/">《clang 的线程安全注解TSA》</a>以及 <a href="/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E4%BA%92%E6%96%A5%E9%87%8F%E4%B8%8E%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/">《对 muduo 网络库中互斥量与条件变量的思考与实践》</a>，理解上应该不会有什么太大的问题。<br>这里有个地方稍微提一下：出于效率的考虑，running_ 不需要是 atomic 类型。这主要是因在同步队列当中，只有在初始化队列和停止队列时才会修改到 running_ 而使用频繁的 <code>take</code> 和 <code>put</code> 函数会不断访问 running_ 的值，因此我将 running_ 设置为普通的 bool 类型，而在 <code>stop</code> 函数中采用上锁访问的方式来避免 data race.</p><h4 id="ThreadPool-的实现"><a href="#ThreadPool-的实现" class="headerlink" title="ThreadPool 的实现"></a>ThreadPool 的实现</h4><p>ThreadPool 的具体代码放在 tmuduo/base/ThreadPool.{h, cc} 当中，有需要的可以自行下下来研究。这里出于篇幅问题，只展示了一些关键代码。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//ThreadPool.h:</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ThreadPool</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="keyword">using</span> Task = std::function&lt;<span class="built_in">void</span>()&gt;;</span><br><span class="line">  <span class="built_in">ThreadPool</span>(<span class="type">int</span> maxSize, <span class="type">const</span> std::string&amp; name = std::<span class="built_in">string</span>(<span class="string">&quot;ThreadPool&quot;</span>));</span><br><span class="line">  ~<span class="built_in">ThreadPool</span>();</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">setThreadInitCallback</span><span class="params">(<span class="type">const</span> Task&amp; cb)</span> </span>&#123; threadInitCallback_ = cb; &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">start</span><span class="params">(<span class="type">int</span> numThreads)</span></span>;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">stop</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">run</span><span class="params">(Task&amp;&amp; f)</span></span>;</span><br><span class="line">  <span class="comment">//一些其他的函数</span></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">runInThread</span><span class="params">()</span></span>;</span><br><span class="line">  std::string name;</span><br><span class="line">  Task threadInitCallback_;</span><br><span class="line">  std::vector&lt;std::unique_ptr&lt;Thread&gt;&gt; threads_;</span><br><span class="line">  SyncQueue&lt;Task&gt; queue_;</span><br><span class="line">  std::atomic&lt;<span class="type">bool</span>&gt; running_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//ThreadPool.cc</span></span><br><span class="line">ThreadPool::<span class="built_in">ThreadPool</span>(<span class="type">int</span> maxSize, <span class="type">const</span> std::string&amp; name)</span><br><span class="line">    : <span class="built_in">name_</span>(name), <span class="built_in">queue_</span>(maxSize), <span class="built_in">running_</span>(<span class="literal">false</span>) &#123;</span><br><span class="line">  <span class="built_in">assert</span>(maxSize &gt; <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ThreadPool::~<span class="built_in">ThreadPool</span>() &#123;</span><br><span class="line">  <span class="keyword">if</span> (running_) &#123;</span><br><span class="line">    <span class="built_in">stop</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="function"><span class="type">void</span> <span class="title">ThreadPool::start</span><span class="params">(<span class="type">int</span> numThreads)</span> </span>&#123;</span><br><span class="line">  <span class="built_in">assert</span>(threads_.<span class="built_in">empty</span>() &amp;&amp; numThreads &gt;= <span class="number">0</span>);</span><br><span class="line">  running_ = <span class="literal">true</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; numThreads; ++i) &#123;</span><br><span class="line">    threads_.<span class="built_in">emplace_back</span>(</span><br><span class="line">        <span class="keyword">new</span> tmuduo::<span class="built_in">Thread</span>(std::<span class="built_in">bind</span>(&amp;ThreadPool::runInThread, <span class="keyword">this</span>),</span><br><span class="line">                           name_ + std::<span class="built_in">to_string</span>(i + <span class="number">1</span>)));</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="number">0</span> == numThreads &amp;&amp; threadInitCallback_) &#123;</span><br><span class="line">    <span class="built_in">threadInitCallback_</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="function"><span class="type">void</span> <span class="title">ThreadPool::stop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  queue_.<span class="built_in">stop</span>();</span><br><span class="line">  running_ = <span class="literal">false</span>;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">auto</span>&amp; thr : threads_) &#123;</span><br><span class="line">    thr-&gt;<span class="built_in">join</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="function"><span class="type">void</span> <span class="title">ThreadPool::run</span><span class="params">(Task&amp;&amp; task)</span> </span>&#123; queue_.<span class="built_in">put</span>(std::forward&lt;Task&gt;(task)); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">ThreadPool::runInThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (threadInitCallback_) &#123;</span><br><span class="line">      <span class="built_in">threadInitCallback_</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> (running_) &#123;</span><br><span class="line">      Task task;</span><br><span class="line">      queue_.<span class="built_in">take</span>(task);</span><br><span class="line">      <span class="keyword">if</span> (task) &#123;</span><br><span class="line">        <span class="built_in">task</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="built_in">catch</span> (<span class="type">const</span> Exception&amp; ex) &#123;</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;exception caught in ThreadPool %s\n&quot;</span>, name_.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;reasion: %s\n&quot;</span>, ex.<span class="built_in">what</span>());</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;stack trace: %s\n&quot;</span>, ex.<span class="built_in">stackTrace</span>());</span><br><span class="line">    <span class="built_in">abort</span>();</span><br><span class="line">  &#125; <span class="built_in">catch</span> (<span class="type">const</span> std::exception&amp; ex) &#123;</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;exception caught in ThreadPool %s\n&quot;</span>, name_.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;reasion: %s\n&quot;</span>, ex.<span class="built_in">what</span>());</span><br><span class="line">    <span class="built_in">abort</span>();</span><br><span class="line">  &#125; <span class="built_in">catch</span> (...) &#123;</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;unknown exception caught in ThreadPool %s\n&quot;</span>,</span><br><span class="line">            name_.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">throw</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上述代码的实现也比较简单，如果看过之前的文章<a href="/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/">《对-muduo-网络库中的线程模型的思考与实践》</a> 了解 tmuduo 的 Thread 实现，那么只要捋清线程的 <code>start</code> 以及 <code>stop</code> 基本就能够将这个 ThreadPool 实现出来。</p><p>回顾到之前提到的 ThreadPool 的双层结构，对应到代码上则是：</p><blockquote><p>上层  ：创建 ThreadPool 的线程<br>同步层：ThreadPool 中的 SyncQueue<br>异步层：ThreadPool 的 threads_ 数组</p></blockquote><p>上层通过调用 <code>run</code> 函数将任务添加到同步层中，异步层中的线程的 <code>runInThread</code> 则会在空闲的时候将任务取出并执行。</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 源码剖析系列的第六篇文章，主要探讨了 tmuduo 网络库的线程池模型，其中包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;为什么要重新设计 ThreadPool&lt;/li&gt;
&lt;li&gt;SyncQueue 的实现&lt;/li&gt;
&lt;li&gt;ThreadPool 的实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#x67;&amp;#x69;&amp;#116;&amp;#x40;&amp;#x67;&amp;#x69;&amp;#116;&amp;#104;&amp;#117;&amp;#98;&amp;#46;&amp;#99;&amp;#x6f;&amp;#109;&quot;&gt;&amp;#x67;&amp;#x69;&amp;#116;&amp;#x40;&amp;#x67;&amp;#x69;&amp;#116;&amp;#104;&amp;#117;&amp;#98;&amp;#46;&amp;#99;&amp;#x6f;&amp;#109;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
    <category term="线程池" scheme="https://phoenix500526.github.io/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库异常对象的思考与实践</title>
    <link href="https://phoenix500526.github.io/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E5%BC%82%E5%B8%B8%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/"/>
    <id>https://phoenix500526.github.io/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E5%BC%82%E5%B8%B8%E5%AF%B9%E8%B1%A1%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/</id>
    <published>2020-11-05T23:14:42.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 源码剖析系列文章的第五篇文章，主要探讨了如何设计异常类，使其携带尽可能多的有效信息。</p><blockquote><ul><li>异常到底要不要用 —— Google 到底怎么看异常</li><li>C++ 的 mangle 与 demangle 过程</li><li>如何让异常携带友好的线程函数栈调用信息</li></ul></blockquote><p>本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#x67;&#105;&#x74;&#64;&#103;&#105;&#x74;&#104;&#x75;&#98;&#46;&#x63;&#111;&#x6d;">&#x67;&#105;&#x74;&#64;&#103;&#105;&#x74;&#104;&#x75;&#98;&#46;&#x63;&#111;&#x6d;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><p>在前一篇文章《对 muduo 网络库中的线程模型的思考与实践》中，在 Thread 的 <code>runInThread</code> 函数中涉及到了对异常的一些处理，而这篇文章的目的便是探讨自己在muduo网络库中所学到的异常实现的一点经验</p><h4 id="异常到底要不要用-——-Google-到底怎么看异常"><a href="#异常到底要不要用-——-Google-到底怎么看异常" class="headerlink" title="异常到底要不要用 —— Google 到底怎么看异常"></a>异常到底要不要用 —— Google 到底怎么看异常</h4><p>在我最开始重新用自己的方式实现 tmuduo 的线程模型时，我并没有引入异常处理机制。在此之前，我对 Google 禁用异常的说法略有耳闻，而陈硕大佬作为 Google 的工程师，却在自己的开源项目中用到了异常！？带着好奇心，我去查看了《Google C++ Style Guide》<sub>[1]</sub>,在 Exception 的那章找到了如下内容：</p><blockquote><p>Exception:<br>  We do not use C++ exceptions.<br>…<br>Decision:<br>  On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.<br>  Given that Google’s existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in a new project. The conversion process would be slow and error-prone. We don’t believe that the available alternatives to exceptions, such as error codes and assertions, introduce a significant burden.</p></blockquote><p>这下就清楚了，Google 不用异常的原因是 Google 有很多旧代码并不是异常安全的，将这些代码中引入异常处理，其工作量之巨大远超过异常所能够带来的好处。**如果你所做的项目没有历史包袱，那么适当的使用异常是利大于弊的(关于异常的优缺点，可以直接查看Google C++ Style Guide中的 Pros and Cons，这里就不做搬运工了)**。</p><h4 id="tmuduo-的异常实现"><a href="#tmuduo-的异常实现" class="headerlink" title="tmuduo 的异常实现"></a>tmuduo 的异常实现</h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Exception</span> : <span class="keyword">public</span> std::exception &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Exception</span>(std::string what): <span class="built_in">message_</span>(std::<span class="built_in">move</span>(msg)),</span><br><span class="line">      <span class="built_in">stack_</span>(CurrentThread::<span class="built_in">stackTrace</span>(<span class="comment">/*demangle = */</span> <span class="literal">false</span>)) &#123;&#125;</span><br><span class="line">  ~<span class="built_in">Exception</span>() <span class="keyword">noexcept</span> <span class="keyword">override</span> = <span class="keyword">default</span>;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">const</span> <span class="type">char</span>* <span class="title">what</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">noexcept</span> <span class="keyword">override</span> </span>&#123; <span class="keyword">return</span> message_.<span class="built_in">c_str</span>(); &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">const</span> <span class="type">char</span>* <span class="title">stackTrace</span><span class="params">()</span> <span class="type">const</span> <span class="keyword">noexcept</span> </span>&#123; <span class="keyword">return</span> stack_.<span class="built_in">c_str</span>(); &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  std::string message_;</span><br><span class="line">  std::string stack_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>注意：本节内容中所用的异常代码和 muduo 网络库中是高度类似的，我仅是对其做了一点点微小的调整以使其适应我自己编写的 Thread 类。为了能和之前的文章保持一致，此处还是用 tmuduo 的代码。<br>从上述代码来看，整个异常类的代码非常简单，重点还是在于 <code>CurrentThread::stackTrace</code> 函数的实现上。不过在展示 <code>CurrentThread::stackTrace</code> 的代码实现之前，需要先了解两个基本概念 mangle 与 demangle</p><h4 id="C-的-mangle-与-demangle"><a href="#C-的-mangle-与-demangle" class="headerlink" title="C++ 的 mangle 与 demangle"></a>C++ 的 mangle 与 demangle</h4><p>先简单地解释一下一些相关的概念：</p><blockquote><p>mangle   : 将 C++ 的源程序标识符(original C++ source identifier)转换成C++ ABI 标识符(C++ ABI identifier)的过程<br>demangle : 将 C++ 的 ABI 标识符转换为源程序标识符的过程<br>ABI(Application Binary Interface): 从命名上来看，指的是程序二进制层面上的接口，包括如符号修饰标准、变量内存布局、函数调用方式等等这些所有跟可执行代码二进制兼容性相关的内容。<br>API(Application Programming Interface): 指的往往是源码级别的接口，例如 POSIX 是一个 API 标准，POSIX 线程库下的 <code>pthread_create</code>函数是一个 API。</p></blockquote><p>由于历史原因，不同的编译器之间往往有各自的 C++ ABI 标准，所以同一份 C++ 代码使用不同的编译器，甚至是同一个编译器的不同版本所编译出来的程序都可能存在 ABI 不兼容问题。而 mangle 规则也算是 ABI 的一部分。我们可以先来看看在 Ubuntu-16.04 下的 clang 3.8.0-2 的 mangle 规则：</p><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="comment">//mangle_test.cc</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">(<span class="type">int</span> var)</span></span>&#123;&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">(<span class="type">float</span> var)</span></span>&#123;&#125;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们执行 <code>clang++ -c mangle_test.cc &amp;&amp; nm mangle_test.o</code> 后可以看到如下结果：</p><div style="text-align:center"> <img src="/img/muduo-exception/nm-mangle-test.svg"/></div><div class="image-caption" align="center">mangle_test</div> <p>可以看到我们所写的重载函数 foo 经过了 mangle 后转变成了 _Z3foof 和 _Z3fooi。</p><h4 id="CurrentThread-stackTrace-的实现"><a href="#CurrentThread-stackTrace-的实现" class="headerlink" title="CurrentThread::stackTrace 的实现"></a>CurrentThread::stackTrace 的实现</h4><p>CurrentThread::stackTrace 具体代码保存在 tmuduo/base/CurrentThread.cc 文件中</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="function">std::string <span class="title">stackTrace</span><span class="params">(<span class="type">bool</span> demangle)</span> </span>&#123;</span><br><span class="line">  std::string stack;</span><br><span class="line">  <span class="type">const</span> <span class="type">int</span> max_frames = <span class="number">200</span>;</span><br><span class="line">  <span class="type">void</span>* frame[max_frames];</span><br><span class="line"> </span><br><span class="line">  <span class="type">int</span> nptrs = ::<span class="built_in">backtrace</span>(frame, max_frames);</span><br><span class="line">  <span class="type">char</span>** strings = ::<span class="built_in">backtrace_symbols</span>(frame, nptrs);</span><br><span class="line">  <span class="keyword">if</span> (strings) &#123;</span><br><span class="line">    <span class="type">size_t</span> len = <span class="number">256</span>;</span><br><span class="line">    <span class="type">char</span>* demangled = demangle ? <span class="built_in">static_cast</span>&lt;<span class="type">char</span>*&gt;(::<span class="built_in">malloc</span>(len)) : <span class="literal">nullptr</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i &lt; nptrs; ++i) &#123;</span><br><span class="line">      <span class="keyword">if</span> (demangle) &#123;</span><br><span class="line">        <span class="type">char</span>* left_par = <span class="literal">nullptr</span>;</span><br><span class="line">        <span class="type">char</span>* plus = <span class="literal">nullptr</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>* p = strings[i]; *p; ++p) &#123;</span><br><span class="line">          <span class="keyword">if</span> (*p == <span class="string">&#x27;(&#x27;</span>)</span><br><span class="line">            left_par = p;</span><br><span class="line">          <span class="keyword">else</span> <span class="keyword">if</span> (*p == <span class="string">&#x27;+&#x27;</span>)</span><br><span class="line">            plus = p;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (left_par &amp;&amp; plus) &#123;</span><br><span class="line">          *plus = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">          <span class="type">int</span> status = <span class="number">0</span>;</span><br><span class="line">          <span class="type">char</span>* ret =</span><br><span class="line">              abi::__cxa_demangle(left_par + <span class="number">1</span>, demangled, &amp;len, &amp;status);</span><br><span class="line">          *plus = <span class="string">&#x27;+&#x27;</span>;</span><br><span class="line">          <span class="keyword">if</span> (status == <span class="number">0</span>) &#123;</span><br><span class="line">            demangled = ret;</span><br><span class="line">            stack.<span class="built_in">append</span>(strings[i], left_par + <span class="number">1</span>);</span><br><span class="line">            stack.<span class="built_in">append</span>(demangled);</span><br><span class="line">            stack.<span class="built_in">append</span>(plus);</span><br><span class="line">            stack.<span class="built_in">push_back</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      stack.<span class="built_in">append</span>(strings[i]);</span><br><span class="line">      stack.<span class="built_in">push_back</span>(<span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">free</span>(demangled);</span><br><span class="line">    <span class="built_in">free</span>(strings);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> stack;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中涉及到了三个新的函数：<code>backtrace</code>， <code>backtrace_symbols</code> 以及 <code>abi::__cxa_demangle</code>。其中 <code>backtrace</code> 和 <code>backtrace_symbols</code> 用于获得线程的函数调用栈信息，其具体用法可以在 linux 下的 man 手册中查看，这里不多展开。而 <code>abi::__cxa_demangle</code> 是 libstdc++ 库中的函数，具体的用法可以在 GNU 网站的《abi Namespace Reference》<sub>[2]</sub>中找到</p><p>这里再额外补充点：</p><blockquote><ul><li>某些编译器的优化选项对获取正确的函数调用栈有干扰</li><li>内联函数的调用信息也不会出现在函数调用栈当中</li><li>如果使用 <code>abi::__cxa_demangle</code> 函数，则在连接时需要加上选项 <code>-rdynamic</code>，　注意这是一个　linker option 而非 compiler option，如果你使用 cmake-3.13 或以上的版本，可以使用 <code>add_link_options(-rdynamic)</code></li></ul></blockquote><h4 id="Exception-的测试结果"><a href="#Exception-的测试结果" class="headerlink" title="Exception 的测试结果"></a>Exception 的测试结果</h4><p>tmuduo 当中有关于 Exception 的测试，代码路径为 test/Exception_test.cc。由于调用栈信息比较长，以下只贴一部分的执行结果，有需要的可以自己把代码下下来跑一下<br>经 demangle 处理的异常信息</p><div style="text-align:center"> <img src="/img/muduo-exception/exception-with-demangle.svg" width=735/></div><div class="image-caption" align="center">Exception with demangle</div> <p>未经 demangle 处理的异常信息</p><div style="text-align:center"> <img src="/img/muduo-exception/exception-without-demangle.svg"/></div><div class="image-caption" align="center">Exception without demangle</div> <p>从上述结果可以看出，同一个函数 Bar::test 在经过 demangle 处理的异常信息中显示为 <code>Bar::test(...)</code>， 在没有经过 demangle 处理的异常信息中显示为 <code>_ZN3Bar4testEbSt6vectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS6_EE+0x1c</code></p><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><p>[1]. <a href="https://google.github.io/styleguide/cppguide.html">Google C++ Style Guide</a><br>[2]. <a href="https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html">abi Namespace Reference</a><br>[3].<a href="https://panthema.net/2008/0901-stacktrace-demangled/">C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names</a></p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 源码剖析系列文章的第五篇文章，主要探讨了如何设计异常类，使其携带尽可能多的有效信息。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;异常到底要不要用 —— Google 到底怎么看异常&lt;/li&gt;
&lt;li&gt;C++ 的 mangle 与 demangle 过程&lt;/li&gt;
&lt;li&gt;如何让异常携带友好的线程函数栈调用信息&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#x67;&amp;#105;&amp;#x74;&amp;#64;&amp;#103;&amp;#105;&amp;#x74;&amp;#104;&amp;#x75;&amp;#98;&amp;#46;&amp;#x63;&amp;#111;&amp;#x6d;&quot;&gt;&amp;#x67;&amp;#105;&amp;#x74;&amp;#64;&amp;#103;&amp;#105;&amp;#x74;&amp;#104;&amp;#x75;&amp;#98;&amp;#46;&amp;#x63;&amp;#111;&amp;#x6d;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="C++" scheme="https://phoenix500526.github.io/tags/C/"/>
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
    <category term="Exception" scheme="https://phoenix500526.github.io/tags/Exception/"/>
    
    <category term="ABI" scheme="https://phoenix500526.github.io/tags/ABI/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库中的线程模型的思考与实践</title>
    <link href="https://phoenix500526.github.io/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/"/>
    <id>https://phoenix500526.github.io/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/</id>
    <published>2020-11-05T20:22:56.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 网络库源码剖析系列文章的第四篇文章，主要涉及了 muduo 网络库中对线程模型的一些设计亮点，以及我如何使用标准线程库来对其进行重新实现，其中包含了以下内容：</p><blockquote><ul><li>变量存储期的概念</li><li>pthread_t、pid_t 以及 thread::id 的比较及它们各自的问题</li><li>如何更加简单方便地封装线程上下文</li><li>用标准线程库实现 tmuduo 的线程模型</li></ul></blockquote><p>本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#103;&#105;&#116;&#x40;&#x67;&#105;&#116;&#104;&#117;&#x62;&#46;&#x63;&#x6f;&#109;">&#103;&#105;&#116;&#x40;&#x67;&#105;&#116;&#104;&#117;&#x62;&#46;&#x63;&#x6f;&#109;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><h4 id="一、变量存储期的概念"><a href="#一、变量存储期的概念" class="headerlink" title="一、变量存储期的概念"></a>一、变量存储期的概念</h4><p>C++ 标准中，变量一共有四种不同的存储周期(storage duration)，分别是：</p><ul><li>automatic storage duration: 拥有此生命周期的对象会在<strong>代码块的开始处</strong>分配内存，并在<strong>代码块的结尾处</strong>回收内存。<ul><li>任何没有被声明为 <code>static</code>, <code>extern</code> 或者 <code>thread_local</code> 的<strong>局部变量</strong>都具备此生命周期</li></ul></li><li>static storage duration: 拥有此生命周期的对象会在<strong>程序开始时</strong>分配内存，并在<strong>程序结束时</strong>回收内存。<ul><li>任何声明在命名空间中的对象以及被修饰为 static 和 extern 的对象均具有此生命周期</li><li>在整个程序运行过程中始终只有一个实例</li></ul></li><li>thread storage duration: 拥有此生命周期的对象会在<strong>线程开始时</strong>分配内存，并在<strong>线程结束时</strong>回收内存。<ul><li>thread_local 可以和 static 以及 extern 连用，此时 static 和 extern 都只表示链接性。</li><li>每个线程都有自己的一份 thread_local 对象实例。</li></ul></li><li>dynamic storage duration：拥有此生命周期的对象会在<strong>动态申请内存</strong>处分配内存，并在<strong>析构</strong>时回收内存。</li></ul><h6 id="thread-local-与-thread-的比较"><a href="#thread-local-与-thread-的比较" class="headerlink" title="thread_local 与 __thread 的比较"></a><code>thread_local</code> 与 <code>__thread</code> 的比较</h6><p><code>thread_local</code> 关键字总是很容易让人联想到另一个和它非常类似的关键字<code>__thread</code>，二者之间究竟有何区别？<br>先来看看<code>__thread</code>关键字，__thread 是 GCC 内置的线程局部存储设施，它的实现非常高效，其存储效率可以媲美全局变量。然而使用起来却有以下限制：</p><ul><li>只能修饰 POD 类型，不能修饰 class 类型(因为 __thread 不会自动调用构造和析构函数)</li><li>只能使用编译期常量进行初始化</li><li>__thread 只能修饰全局变量和静态变量，但是不能修饰局部变量或者 class 的普通成员变量<br>__thread 变量在每个线程中都有一份独立实体，各个线程中的变量值互不干扰。利用这个特点，__thread关键字常常被用来修饰那些“值可能会变，带有全局性，但是又不值得用全局锁保护的变量”。<br>而 C++11 的 <code>thread_local</code> 在使用限制上则要宽松不少：</li><li><code>thread_local</code> 可以修饰非 POD 类型的变量，它会自动调用构造函数和析构函数</li><li><code>thread_local</code> 除了全局变量以外，还可以修饰局部变量，并且只会在线程生命周期中只会有一个实例。</li></ul><p>综上所述，如果你使用 C++11 标准进行开发，使用 <code>thread_local</code> 会比使用 <code>__thread</code> 要好用不少。实际上在 muduo 网络库的源代码中，陈硕使用了 <code>ThreadLocal</code> 类来绕开了 <code>__thread</code> 无法自动调用构造函数和析构函数的限制，而在我的 tmuduo 中，由于使用 <code>thread_local</code>, 因此不在需要实现 <code>ThreadLocal</code> 类。在 tmuduo 的 test/ThreadLocal_test.cc 文件中，我对 <code>thread_local</code> 代替 <code>ThreadLocal</code> 进行了测试，效果还是不错的，有兴趣的可以自行查看代码。另外，由于 C++11 标准的保证，使用 <code>thread_local</code> 实现线程局部单例模式很非常的方便，不过由于脱离本篇内容范畴，因此我将其放在后面和单例模式一起探讨。</p><h4 id="二、一种更好的标识线程的方法"><a href="#二、一种更好的标识线程的方法" class="headerlink" title="二、一种更好的标识线程的方法"></a>二、一种更好的标识线程的方法</h4><p>如果阅读过 Linux 内核源码实现的朋友应该知道，其实在 Linux 系统下，进程和线程之间并无本质区别：二者实际上都是用同一个结构体 <code>task_struct</code> 来表示一个执行任务的实体。虽然二者的创建方式各不相同，进程创建调用 <code>fork</code> 系统调用，而线程创建主要通过 <code>pthread_create</code> 函数，但这两个方法最终都会调用到 <code>do_fork</code> 来完成具体的创建操作，最大的区别仅在于传进的参数不同。换句话讲，<strong>Linux 系统中所谓的线程，只不过是看起来像线程，其本质是和其他进程共享部分资源(虚拟内存、文件描述符以及页表等)的进程</strong>。</p><p>既然在 Linux 系统下，进程和线程在本质上没有区别，那么是否可以使用用来描述进程的 <code>pid_t</code> 类型来实现 Linux 系统下的线程标识呢？答案是肯定的。但在这之前，我们先来看看为何要如此大费周章地完成这一事情。在 Linux 系统下, 我们想要去标识一个线程,可供选择的有 <code>pthread_t</code>, <code>pid_t</code> 以及 <code>thread::id</code>. 我们先来看看 <code>pthread_t</code> 类型.POSIX 线程库中提供了 <code>pthread_self</code> 函数来获得一个线程的线程 id. 不过由于某些原因, <code>pthread_t</code> 的类型是不确定的,可能是整数、指针或是结构体, 这取决于你使用的具体系统。由于类型的不确定，POSIX 提供了 <code>pthread_equal</code> 函数来比较两个线程标识是否相同，这带来很多问题：</p><ul><li>如果不知道具体类型，自然也就难以打印输出。对于 <code>printf</code> 函数，不确定类型自然无法打印。对于 C++ 的流，虽然使用时可以不考虑类型差异，但你也很难对输出的结果有合理的预期。</li><li>无法进行大小和相等性的比较，因此不能过作为 hash 的 key 进行存储</li><li>POSIX 没有提供一个专门用来表示非法线程的专属ID.</li><li><code>pthread_t</code> 只能保证在当前进程内唯一，并不能保证全局唯一，甚至连在同一个进程内先后创建的不同线程都可能拥有相同的线程ID</li></ul><p>接下来再来看看 <code>pid_t</code>, 和 <code>pthread_t</code> 相比，它具有以下特点：</p><ul><li>类型明确，pid_t 就是一个小整数类型，这也就意味着你可以很方便地将其打印到日志中，同时可以对其值有合理的预期(不必去猜测这个打印出来的 id 到底是数字还是指针)</li><li>在现代 linux 系统中，<code>pid_t</code> 代表了任务调度 id，而线程是任务的基本调度单位，因此可以很方便地将它们对应起来，这样就可以利用 linux 的 /proc 系统来获取相应的信息</li><li>linux 对 <code>pid_t</code> 的分配采用了轮回递增的方法，因此任何时刻都是全局唯一的，短时间内也不会有重复</li><li>0 代表非法值，因为 linux 中的第一个进程的 pid 是 1；<br>虽然使用 <code>pid_t</code> 来标识线程有上述的好处，不过 linux 并没有提供相应的系统调用来完成这一工作，不过我们依然可以利用间接系统调用 <code>syscall</code> 来绕开这一限制，这在接下来的 一种封装线程上下文的简单尝试** 中会展示。</li></ul><p>最后来看看<code>thread::id</code>。在 thread 标准库中也提供了 <code>thread::id</code> 来为进程提供唯一标识。<code>thread::id</code> 相对于 <code>pthread_t</code> 有以下特点:</p><ul><li><code>thread::id</code> 类型明确，一方面重载了流操作运算符，可以使用流进行输出，也可以利用流将其转换为字符串类型，另一方面标准库也提供了 std::hash 来对 <code>thread::id</code> 进行散列，这样也可以得到一个类型为 <code>std::size_t</code> 的值，两种转换方法如下：  <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="comment">// thread::id 转换为字符串类型</span></span><br><span class="line"><span class="keyword">auto</span> myid = this_thread.<span class="built_in">get_id</span>(); </span><br><span class="line">stringstream ss; </span><br><span class="line">ss &lt;&lt; myid; </span><br><span class="line">string mystring = ss.<span class="built_in">str</span>();</span><br><span class="line"><span class="comment">// thread::id 转换为 std::size_t 类型</span></span><br><span class="line">std::<span class="built_in">hash</span>&lt;std::thread::id&gt;()(std::this_thread::<span class="built_in">get_id</span>())</span><br></pre></td></tr></table></figure></li><li>标准库提供了一个 <code>id()</code> 方法，能够获得一个可以用来标记 non-joinable 的线程，也就相当于有了判定非法线程的方法。</li><li>标准库提供了比较运算符，可以对 <code>thread::id</code> 进行比较</li></ul><p>在了解了上述三种标识线程的方法后，我们也很容易根据自己的需求挑选出合适的线程标识类型。如果只考虑 linux 系统开发而不考虑可移植性，那么首选 <code>pid_t</code> 作为线程<br>ID 的类型。如果有移植性的需要，那么可以考虑使用 <code>thread::id</code>。由于 muduo 网络库本身只考虑 linux 系统，因此采用了 pthread + pid_t 的方式来实现其线程模型。接下来，我会使用 thread + pid_t 的方式重新实现对应的线程模型。</p><h4 id="三、一种封装线程上下文的简单尝试"><a href="#三、一种封装线程上下文的简单尝试" class="headerlink" title="三、一种封装线程上下文的简单尝试"></a>三、一种封装线程上下文的简单尝试</h4><p>我曾经分析过 skynet 框架中的多线程模型(见《skynet 源码阅读笔记 —— 消息调度机制》一文）。skynet 与 muduo 类似，都是采用了基于回调的 Reactor 模型。由于 skynet 采用 C 语言实现。C 语言中没有闭包的概念，无法方便地实现可调用对象，因此只能定义回调接口，然后定义业务逻辑处理函数来实现接口，并通过回调函数的参数传递上下文(如 skynet 中的 skynet_context)。本来我是想要按照这种方式，利用类来封装线程上下文，并通过传参的方式来传递上下文。但是我在看到了 muduo 网络库的 CurrentThread.{cc,h} 的实现后，感觉又学到了一种新的封装线程上下文的方法：利用 <code>thread_local</code> 来存储相关的上下文(在这里主要是线程id和线程名称)，而利用命名空间来”封装”操作上下文的方法。这种做法非常巧妙，相当于构造了一个生命周期贯穿于线程始末的闭包，它有以下的好处：</p><ul><li>函数无需传参亦可访问到自身线程上下文信息。如果采用类的方式进行封装，那么在线程中的低阶函数就必须要以传参的方式去获得上下文。</li><li>使用 <code>thread_local</code> + 命名空间的方法，可以将变量声明为全局变量而无需担心命名污染。全局变量往往存取效率更高</li><li>比起类的封装，命名空间封装函数的方式在结构上更加松散， 松散的结构虽然降低一定的安全性，但是也带来了灵活性的大大提升。</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//CurrentThread.h</span></span><br><span class="line"><span class="keyword">namespace</span> tmuduo &#123;</span><br><span class="line"><span class="keyword">namespace</span> detail &#123;</span><br><span class="line"><span class="comment">//利用间接系统调用获得线程 id</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">pid_t</span> <span class="title">gettid</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">static_cast</span>&lt;<span class="type">pid_t</span>&gt;(::<span class="built_in">syscall</span>(SYS_gettid)); &#125;</span><br><span class="line"></span><br><span class="line">&#125;  <span class="comment">// namespace detail</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> CurrentThread &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">thread_local</span> <span class="type">pid_t</span> t_cachedTid;</span><br><span class="line"><span class="keyword">extern</span> <span class="keyword">thread_local</span> <span class="type">const</span> <span class="type">char</span>* t_threadName;</span><br><span class="line"></span><br><span class="line"><span class="comment">// cacheTid、isMainTHread、sleepUsec均定义在 Thread.cc 文件中，</span></span><br><span class="line"><span class="comment">// 因为需要拿到对应线程的 ID</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">cacheTid</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">bool</span> <span class="title">isMainThread</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">sleepUsec</span><span class="params">(<span class="type">int64_t</span> usec)</span></span>;  <span class="comment">// for testing</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">pid_t</span> <span class="title">tid</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (__builtin_expect(t_cachedTid == <span class="number">0</span>, <span class="number">0</span>)) &#123;</span><br><span class="line">    <span class="built_in">cacheTid</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> t_cachedTid;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">const</span> <span class="type">char</span>* <span class="title">name</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> t_threadName; &#125;</span><br><span class="line">&#125;  <span class="comment">// namespace CurrentThread</span></span><br><span class="line">&#125;  <span class="comment">// namespace tmuduo</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//CurrentThread.cc</span></span><br><span class="line"><span class="keyword">namespace</span> tmuduo &#123;</span><br><span class="line"><span class="keyword">namespace</span> CurrentThread &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">thread_local</span> <span class="type">pid_t</span> t_cachedTid = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">thread_local</span> <span class="type">const</span> <span class="type">char</span>* t_threadName = <span class="string">&quot;unknown&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">cacheTid</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (t_cachedTid == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">//获取线程的 pid_t</span></span><br><span class="line">    t_cachedTid = detail::<span class="built_in">gettid</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="function"><span class="type">bool</span> <span class="title">isMainThread</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> <span class="built_in">tid</span>() == ::<span class="built_in">getpid</span>(); &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">sleepUsec</span><span class="params">(<span class="type">int64_t</span> usec)</span> </span>&#123;</span><br><span class="line">  <span class="type">static</span> <span class="type">const</span> <span class="type">int</span> kMicroSecondsPerSecond = <span class="number">1000</span> * <span class="number">1000</span>;</span><br><span class="line">  <span class="keyword">struct</span> <span class="title class_">timespec</span> ts = &#123;<span class="number">0</span>, <span class="number">0</span>&#125;;</span><br><span class="line">  ts.tv_sec = <span class="built_in">static_cast</span>&lt;<span class="type">time_t</span>&gt;(usec / kMicroSecondsPerSecond);</span><br><span class="line">  ts.tv_nsec = <span class="built_in">static_cast</span>&lt;<span class="type">long</span>&gt;(usec % kMicroSecondsPerSecond * <span class="number">1000</span>);</span><br><span class="line">  ::<span class="built_in">nanosleep</span>(&amp;ts, <span class="literal">NULL</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;  <span class="comment">// namespace CurrentThread</span></span><br><span class="line">&#125;  <span class="comment">// namespace tmuduo</span></span><br></pre></td></tr></table></figure><p>在上述代码中，<code>gettid</code>展示了使用 <code>syscall</code> 来获取线程的一般方法(这也是 linux 中 man 关于 syscall 的一个示例用法)。另外，为了避免反复调用系统调用带来的开销，<code>CurrentThread</code> 采用 <code>t_cachedTid</code> 缓存 <code>gettid</code> 的执行结果，因此性能无忧。另外，<code>__builtin_expect</code> 是 GCC 的一个内置函数，其作用是供程序员将分支信息提供给编译器，以方便编译器调整取指令的顺序进行优化，这样可以减少 cache 产生控制冒险。</p><h4 id="四、tmuduo-线程类-Thread-的实现"><a href="#四、tmuduo-线程类-Thread-的实现" class="headerlink" title="四、tmuduo 线程类 Thread 的实现"></a>四、tmuduo 线程类 Thread 的实现</h4><p>在讨论 tmuduo 如何使用标准线程库来封装线程之前，我们需要先明确几个问题：</p><ul><li>为什么要封装线程对象？thread 不好用吗？</li><li>标准线程库比起 POSIX 线程库好在哪？<br>对于第一个问题，之所以要封装线程，是因为我们希望让线程在初始化的过程当中去执行一些簿记工作，这样有利于我们进行后期调试。由于标准库不会假定你要做何种工作，因此我们需要自己为 thread 对象做一层薄薄的封装<br>对于第二个问题：标准线程库参照了 java 中线程库的设计，历史包袱远远少于 POSIX 线程库，淘汰掉了相当一部分不合理的接口。其次标准库本身与系统无关，具有良好的移植性。最后，标准线程库中的许多函数实现是有语言标准保证的，我们无需做过多额外工作，因此能够减少不少编码工作。</li></ul><p>举个例子，在 muduo 网络库中，Thread 采用了 pthread 进行设计，将 Thread 的构造函数和启动函数分离开。这主要是因为当执行 <code>pthread_create</code> 函数创建线程后，线程并不一定是马上开始运行的，尽管当 CPU 低负载的大部分情况下，线程是马上开始运行的。但是当 CPU 负载提高，并发度增加时，有可能会出现线程创建完成后返回，但是线程函数并为开始执行，而此时主线程去访问新线程时就会产生错误。因此，muduo 网络库中的 Thread 对象为了杜绝这种情况，将构造和启动函数分离，并在启动函数中使用了 CountDownLatch 进行同步，只有当创建好的线程开始运行时，start 函数才会返回。而标准库则保证在 thread 的构造函数返回时，线程函数已经开始运行。(具体可见我在 stackoverflow 上的一个讨论：<a href="https://stackoverflow.com/questions/64461444/is-that-the-thread-object-can-be-moved-in-c-11-reasonable">Is that the thread object can be moved in C++ 11 reasonable?</a>)<br>在明确了构造即启动的语义后，Thread 中可以减少很多用于同步变量以及函数，例如不再需要 <code>start</code> 函数，只需要实现构造函数即可，又例如不再需要用于标识线程运行状态的 <code>started_</code>，因为我们可通过 <code>joinable</code> 来判断一个线程的状态。下面我们来看代码实现：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//Thread.h</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Thread</span> : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="keyword">using</span> ThreadFunc = std::function&lt;<span class="built_in">void</span>()&gt;;</span><br><span class="line">  <span class="built_in">Thread</span>(ThreadFunc func, <span class="type">const</span> std::string&amp; name = std::<span class="built_in">string</span>());</span><br><span class="line">  ~<span class="built_in">Thread</span>();</span><br><span class="line">  <span class="built_in">Thread</span>(Thread&amp;&amp; rhs) <span class="keyword">noexcept</span>;</span><br><span class="line">  Thread&amp; <span class="keyword">operator</span>=(Thread&amp;&amp; rhs) <span class="keyword">noexcept</span>;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">join</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">int</span> <span class="title">numCreated</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> numCreated_.<span class="built_in">load</span>(); &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="comment">//线程实体</span></span><br><span class="line">  std::thread thread_;</span><br><span class="line">  <span class="type">static</span> std::atomic&lt;<span class="type">int</span>&gt; numCreated_;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">runInThread</span><span class="params">(ThreadFunc func, <span class="type">const</span> std::string&amp; name)</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//Thread.cc</span></span><br><span class="line"><span class="function">std::atomic&lt;<span class="type">int</span>&gt; <span class="title">Thread::numCreated_</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Thread::runInThread</span><span class="params">(ThreadFunc func, <span class="type">const</span> std::string&amp; name)</span> </span>&#123;</span><br><span class="line">  CurrentThread::t_cachedTid = CurrentThread::<span class="built_in">tid</span>();</span><br><span class="line">  <span class="keyword">if</span> (name.<span class="built_in">empty</span>()) &#123;</span><br><span class="line">    <span class="type">char</span> buf[<span class="number">32</span>];</span><br><span class="line">    <span class="built_in">snprintf</span>(buf, <span class="keyword">sizeof</span> buf, <span class="string">&quot;Thread%d&quot;</span>, Thread::<span class="built_in">numCreated</span>());</span><br><span class="line">    CurrentThread::t_threadName = buf;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    CurrentThread::t_threadName = name.<span class="built_in">c_str</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//::prctl(PR_SET_NAME, threadName)：表示用 threadName</span></span><br><span class="line">  <span class="comment">//为当前线程命名，threadName 的长度</span></span><br><span class="line">  <span class="comment">//不得超过 16 bytes。当名字长度超过 16 个字节时会默认截断</span></span><br><span class="line">  ::<span class="built_in">prctl</span>(PR_SET_NAME, CurrentThread::t_threadName);</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="built_in">func</span>();</span><br><span class="line">    CurrentThread::t_threadName = <span class="string">&quot;finished&quot;</span>;</span><br><span class="line">  &#125; <span class="built_in">catch</span> (<span class="type">const</span> Exception&amp; ex) &#123;</span><br><span class="line">    CurrentThread::t_threadName = <span class="string">&quot;crashed&quot;</span>;</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;exception caught in Thread %s\n&quot;</span>, name.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;reason: %s\n&quot;</span>, ex.<span class="built_in">what</span>());</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;stack trace: %s\n&quot;</span>, ex.<span class="built_in">stackTrace</span>());</span><br><span class="line">    <span class="built_in">abort</span>();</span><br><span class="line">  &#125; <span class="built_in">catch</span> (<span class="type">const</span> std::exception&amp; ex) &#123;</span><br><span class="line">    CurrentThread::t_threadName = <span class="string">&quot;crashed&quot;</span>;</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;exception caught in Thread %s\n&quot;</span>, name.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;reason: %s\n&quot;</span>, ex.<span class="built_in">what</span>());</span><br><span class="line">    <span class="built_in">abort</span>();</span><br><span class="line">  &#125; <span class="built_in">catch</span> (...) &#123;</span><br><span class="line">    CurrentThread::t_threadName = <span class="string">&quot;crashed&quot;</span>;</span><br><span class="line">    <span class="built_in">fprintf</span>(stderr, <span class="string">&quot;unknown exception caught in Thread %s\n&quot;</span>, name.<span class="built_in">c_str</span>());</span><br><span class="line">    <span class="keyword">throw</span>;  <span class="comment">// rethrow</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Thread::<span class="built_in">Thread</span>(ThreadFunc func, <span class="type">const</span> std::string&amp; name)</span><br><span class="line">    : <span class="built_in">thread_</span>(&amp;Thread::runInThread, <span class="keyword">this</span>, std::<span class="built_in">move</span>(func), name) &#123;</span><br><span class="line">  ++numCreated_;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Thread::<span class="built_in">Thread</span>(Thread&amp;&amp; rhs) <span class="keyword">noexcept</span> : <span class="built_in">thread_</span>(std::<span class="built_in">move</span>(rhs.thread_)) &#123;&#125;</span><br><span class="line"></span><br><span class="line">Thread&amp; Thread::<span class="keyword">operator</span>=(Thread&amp;&amp; rhs) <span class="keyword">noexcept</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">this</span> != &amp;rhs) &#123;</span><br><span class="line">    thread_ = std::<span class="built_in">move</span>(rhs.thread_);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> *<span class="keyword">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Thread::join</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (thread_.<span class="built_in">joinable</span>()) &#123;</span><br><span class="line">    --numCreated_;</span><br><span class="line">    thread_.<span class="built_in">join</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Thread::~<span class="built_in">Thread</span>() &#123;</span><br><span class="line">  <span class="keyword">if</span> (thread_.<span class="built_in">joinable</span>()) &#123;</span><br><span class="line">    --numCreated_;</span><br><span class="line">    thread_.<span class="built_in">detach</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="测试结果"><a href="#测试结果" class="headerlink" title="测试结果"></a>测试结果</h4><p>上述代码的实现经过测试，运行良好。由于测试代码比较冗长，且案例覆盖上存在重复的部分，这里就不贴出代码。具体的测试代码位于 tmuduo/test/Thread_test.cc 中，编译后的可执行文件为 tmuduo/build/bin/Thread_test，其执行结果可以通过 <code>top -H -p pid</code> 来观测，具体结果如下：</p><div style="text-align:center"> <img src="/img/muduo-thread/thread-test-top.svg"/></div><div class="image-caption" align="center">Thread_test 运行结果</div>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 网络库源码剖析系列文章的第四篇文章，主要涉及了 muduo 网络库中对线程模型的一些设计亮点，以及我如何使用标准线程库来对其进行重新实现，其中包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;变量存储期的概念&lt;/li&gt;
&lt;li&gt;pthread_t、pid_t 以及 thread::id 的比较及它们各自的问题&lt;/li&gt;
&lt;li&gt;如何更加简单方便地封装线程上下文&lt;/li&gt;
&lt;li&gt;用标准线程库实现 tmuduo 的线程模型&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#103;&amp;#105;&amp;#116;&amp;#x40;&amp;#x67;&amp;#105;&amp;#116;&amp;#104;&amp;#117;&amp;#x62;&amp;#46;&amp;#x63;&amp;#x6f;&amp;#109;&quot;&gt;&amp;#103;&amp;#105;&amp;#116;&amp;#x40;&amp;#x67;&amp;#105;&amp;#116;&amp;#104;&amp;#117;&amp;#x62;&amp;#46;&amp;#x63;&amp;#x6f;&amp;#109;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="多线程" scheme="https://phoenix500526.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
    <category term="C++" scheme="https://phoenix500526.github.io/tags/C/"/>
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>对 muduo 网络库中互斥量与条件变量的思考与实践</title>
    <link href="https://phoenix500526.github.io/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E4%BA%92%E6%96%A5%E9%87%8F%E4%B8%8E%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/"/>
    <id>https://phoenix500526.github.io/2020/11/05/%E5%AF%B9-muduo-%E7%BD%91%E7%BB%9C%E5%BA%93%E4%B8%AD%E4%BA%92%E6%96%A5%E9%87%8F%E4%B8%8E%E6%9D%A1%E4%BB%B6%E5%8F%98%E9%87%8F%E7%9A%84%E6%80%9D%E8%80%83%E4%B8%8E%E5%AE%9E%E8%B7%B5/</id>
    <published>2020-11-05T20:18:54.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文是 muduo 网络库源码剖析系列文章的第三篇文章，主要探讨了 muduo 网络库对互斥量和条件变量的封装，以及我如何使用 C++11 的 mutex 以及 condition_variable 来对其进行重新实现，其中包含了以下内容：</p><blockquote><ul><li>基于 clang 的线程安全注解实现的 Mutex 类</li><li>基于 condition_variable 实现的 Condition 类</li><li>RAII 对象测试的一些 tips</li></ul></blockquote><p>本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 <a href="mailto:&#103;&#105;&#116;&#64;&#x67;&#105;&#116;&#104;&#x75;&#x62;&#46;&#99;&#x6f;&#109;">&#103;&#105;&#116;&#64;&#x67;&#105;&#116;&#104;&#x75;&#x62;&#46;&#99;&#x6f;&#109;</a>:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习</p><span id="more"></span><h4 id="基于-clang-的线程安全注解实现的-Mutex-类"><a href="#基于-clang-的线程安全注解实现的-Mutex-类" class="headerlink" title="基于 clang 的线程安全注解实现的 Mutex 类"></a>基于 clang 的线程安全注解实现的 Mutex 类</h4><p>在并发编程当中，data race 往往是个非常令人头疼的问题。通常为了避免 data race，我们通常会使用静态的检查工具(如 clang 的 TSA)或者动态的检查工具(如 Valgrind) 等来检查代码。为了能够在 tmuduo 的实现中引入 TSA， 我对标准库的 mutex 进行了一层封装。具体的代码如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">class</span> <span class="title">CAPABILITY</span><span class="params">(<span class="string">&quot;mutex&quot;</span>)</span> Mutex : noncopyable &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Mutex</span>() : <span class="built_in">mutex_</span>(), <span class="built_in">holder_</span>(<span class="number">0</span>) &#123;&#125;</span><br><span class="line">  ~<span class="built_in">Mutex</span>() &#123; <span class="built_in">assert</span>(holder_ == <span class="number">0</span>); &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">lock</span><span class="params">()</span> <span class="title">ACQUIRE</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    mutex_.<span class="built_in">lock</span>();</span><br><span class="line">    <span class="built_in">assignHolder</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">unlock</span><span class="params">()</span> <span class="title">RELEASE</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">unassignHolder</span>();</span><br><span class="line">    mutex_.<span class="built_in">unlock</span>();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">bool</span> <span class="title">isLockedByThisThread</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> holder_ == CurrentThread::<span class="built_in">tid</span>(); &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">assertLocked</span><span class="params">()</span> <span class="title">ASSERT_CAPABILITY</span><span class="params">(<span class="keyword">this</span>)</span> </span>&#123;</span><br><span class="line">    <span class="built_in">assert</span>(<span class="built_in">isLockedByThisThread</span>());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="keyword">friend</span> <span class="keyword">class</span> <span class="title class_">UniqueLock</span>;</span><br><span class="line">  <span class="comment">//仅供 UniqueLock 使用，严禁用户调用</span></span><br><span class="line">  <span class="function">std::mutex&amp; <span class="title">getMutex</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> mutex_; &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">assignHolder</span><span class="params">()</span> </span>&#123; holder_ = CurrentThread::<span class="built_in">tid</span>(); &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">unassignHolder</span><span class="params">()</span> </span>&#123; holder_ = <span class="number">0</span>; &#125;</span><br><span class="line">  std::mutex mutex_;</span><br><span class="line">  <span class="type">pid_t</span> holder_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>对于上述代码，只要了解 TSA 的相关宏定义以及 <code>mutex</code> 的使用，应该不难理解。这里不过多地解释我自己写的代码。在我最初实现 Mutex 时，我并没有保留 <code>isLockedByThisThread()</code> 和 <code>assertLocked()</code>，而且我使用的是 <code>thread::id</code> 作为 <code>holder_</code> 的类型。后来，在实现 tmuduo 的过程当中，随着对项目理解的加深，我对 Mutex 做出了两点改变：</p><ul><li>使用 pid_t 而非 thread::id 来保存 holder_</li><li>保留了<code>isLockedByThisThread()</code>和<code>assertLocked</code>这两个函数<br>其中关于第一个改变的原因，由于涉及到线程方面的一些内容，我会放到下一篇文章中来讨论，而这里主要谈谈<code>isLockedByThisThread()</code>和<code>assertLocked</code>这两个函数的意义。</li></ul><p>通常情况下，如果一个函数既有可能在已加锁的情况下访问，也可能在未加锁的情况下访问，那么出于执行效率的考虑，你应当将其拆分成为两个函数，例如：</p><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><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">MutexLock mutex;</span><br><span class="line">std::vector&lt;Foo&gt; foos;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">post</span><span class="params">(<span class="type">const</span> Foo&amp; f)</span></span>&#123;</span><br><span class="line">    <span class="function">MutexLockGuard <span class="title">lock</span><span class="params">(mutex)</span></span>;</span><br><span class="line">    <span class="built_in">postWithLockHold</span>(f);    <span class="comment">//编译器会自动内联优化，因此不必担心开销</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//引入这个函数是为了体现代码作者的意图，尽管 push_back 通常可以手动内联</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">postWithLockHold</span><span class="params">(<span class="type">const</span> Foo&amp; f)</span></span>&#123;</span><br><span class="line">    foos.<span class="built_in">push_back</span>(f);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于上述代码，可能会导致出现两个问题（此处的无锁和加锁是指函数调用外部，而非函数内部）：</p><ul><li>本该使用无锁版本 post， 结果误用了加锁版本 postWithLockHold，导致了死锁【之所以带上了 WithLockHold 这个显眼的后缀就是为了避免误用。但是如果误用了，可以用 gdb 利用函数调用栈来进行调试，如果两个函数先后占有了同一个 mutex 就会引发死锁】</li><li>本该使用加锁版本 postWithLockHold，结果误用了无锁版本 post，导致了数据的损坏。【因此，MutexLock 提供了 <code>assertLocked( )</code>来进行检查】，例如：<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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">postWithLockHold</span><span class="params">(<span class="type">const</span> Foo&amp; f)</span></span>&#123;</span><br><span class="line">    <span class="built_in">assertLocked</span>();</span><br><span class="line">    foos.<span class="built_in">push_back</span>(f);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>回到问题本身，在 Mutex 的封装下，<code>assertLocked</code> 的实现可以看做是对 TSA 的一点点补充，TSA 是编译期的静态检查，对于运行时产生的错误无能为力。在实现 Mutex 时候，我们有一个基本原则：那就是<strong>尽可能让错误尽早出现</strong>。不管是利用 TSA 让编译器能够在编译期检测出错误，还是实现 <code>assertLocked</code> 让代码在运行时一旦产生错误就立即终止程序，都是为了尽量避免将错误留给用户去处理。</li></ul><p>当然，由于目前 Mutex 资源需要手动分配与释放， 用起来不太方便。我们可以采用 RAII 的方式对其进行包装，如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">SCOPED_CAPABILITY</span> UniqueLock : noncopyable &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">explicit</span> <span class="title">UniqueLock</span><span class="params">(Mutex&amp; mutex)</span> <span class="title">ACQUIRE</span><span class="params">(mutex)</span></span></span><br><span class="line"><span class="function">      : mutex_(mutex), lck_(mutex.getMutex()) &#123;</span></span><br><span class="line">    mutex_.<span class="built_in">assignHolder</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  ~<span class="built_in">UniqueLock</span>() <span class="built_in">RELEASE</span>() &#123; mutex_.<span class="built_in">unassignHolder</span>(); &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function">std::unique_lock&lt;std::mutex&gt;&amp; <span class="title">getUniqueLock</span><span class="params">()</span> </span>&#123; <span class="keyword">return</span> lck_; &#125;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  Mutex&amp; mutex_;</span><br><span class="line">  std::unique_lock&lt;std::mutex&gt; lck_;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> UniqueLock(x) <span class="keyword">error</span> <span class="string">&quot;Missing guard object name&quot;</span></span></span><br></pre></td></tr></table></figure><p>上述代码也比较好理解，这里主要讲一下宏<code>UniqueLock(x)</code>的意义。对于 RAII 对象而言，通常有两种情况会导致诡异的bug：</p><ul><li>由于编译器优化，而使得 RAII 对象的生命周期大大延长</li><li>不小心将 RAII 对象定义为匿名对象，从而导致提前析构<br>对于第一个问题，我们留到后面将 Mutex_test 的时候来讨论，而宏<code>UniqueLock(x)</code>正是为了避免解决第二个问题而存在的。我们来看一个例子：<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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">doit</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">MutexLockGuard</span>(mutex);    <span class="comment">//因为遗漏了变量名，结果产生了一个临时对象然后又立马销毁了，结果没能锁住临界区</span></span><br><span class="line">    <span class="comment">//正确写法为 MutexLockGuard lock(mutex);</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>如果定义了宏<code>UniqueLock(x)</code>，则上述代码是无法通过编译的。</li></ul><h4 id="基于-condition-variable-实现的-Condition-类"><a href="#基于-condition-variable-实现的-Condition-类" class="headerlink" title="基于 condition_variable 实现的 Condition 类"></a>基于 condition_variable 实现的 Condition 类</h4><p>在实现了 Mutex 后，我也对 condition_variable 做了一层薄薄的封装。虽然 Condition 本身没有引入 TSA，但标准库中的 condition_variable 需要依赖于 mutex，因此我们也需要对 condition_variable 做了一层封装才能够兼容我们自己实现的 Mutex 类。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Condition</span> : noncopyable &#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">  std::condition_variable m_cond;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="built_in">Condition</span>() : <span class="built_in">m_cond</span>() &#123;&#125;</span><br><span class="line">  ~<span class="built_in">Condition</span>() = <span class="keyword">default</span>;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">notify_all</span><span class="params">()</span> </span>&#123; m_cond.<span class="built_in">notify_all</span>(); &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">notify_one</span><span class="params">()</span> </span>&#123; m_cond.<span class="built_in">notify_one</span>(); &#125;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">wait</span><span class="params">(UniqueLock&amp; lck)</span> </span>&#123; m_cond.<span class="built_in">wait</span>(lck.<span class="built_in">getUniqueLock</span>()); &#125;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">Predicate</span>&gt;</span><br><span class="line">  <span class="function"><span class="type">void</span> <span class="title">wait</span><span class="params">(UniqueLock&amp; lck, Predicate pred)</span> </span>&#123;</span><br><span class="line">    m_cond.<span class="built_in">wait</span>(lck.<span class="built_in">getUniqueLock</span>(), pred);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">Rep</span>, <span class="keyword">class</span> <span class="title class_">Period</span>&gt;</span><br><span class="line">  <span class="function">std::cv_status <span class="title">wait_for</span><span class="params">(UniqueLock&amp; lck,</span></span></span><br><span class="line"><span class="params"><span class="function">                          <span class="type">const</span> std::chrono::duration&lt;Rep, Period&gt;&amp; rel_time)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> m_cond.<span class="built_in">wait_for</span>(lck.<span class="built_in">getUniqueLock</span>(), rel_time);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">template</span> &lt;<span class="keyword">class</span> <span class="title class_">Rep</span>, <span class="keyword">class</span> <span class="title class_">Period</span>, <span class="keyword">class</span> <span class="title class_">Predicate</span>&gt;</span><br><span class="line">  <span class="function"><span class="type">bool</span> <span class="title">wait_for</span><span class="params">(UniqueLock&amp; lck,</span></span></span><br><span class="line"><span class="params"><span class="function">                <span class="type">const</span> std::chrono::duration&lt;Rep, Period&gt;&amp; rel_time,</span></span></span><br><span class="line"><span class="params"><span class="function">                Predicate pred)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> m_cond.<span class="built_in">wait_for</span>(lck.<span class="built_in">getUniqueLock</span>(), rel_time, pred);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>代码整体来说比较简单，这里不过多赘述。</p><h4 id="Mutex-test-的测试"><a href="#Mutex-test-的测试" class="headerlink" title="Mutex_test 的测试"></a>Mutex_test 的测试</h4><p>回到我们之前提到的关于 RAII 对象生命周期的一个问题，那就是要特别注意因为编译器的内联优化而导致 RAII 对象作用域溢出的问题。由于测试代码比较冗长，因此只贴了一小部分代码，如果有需要，可以在 tmuduo/test/Mutex_test.cc 文件中查看完整代码。下面主要说一下测试当中怎么样去注意这个问题：</p><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><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="function"><span class="type">int</span> <span class="title">foo</span><span class="params">()</span> __<span class="title">attribute__</span><span class="params">((noinline))</span></span>;</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">foo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="function">UniqueLock <span class="title">lock</span><span class="params">(g_mutex)</span></span>;</span><br><span class="line">  <span class="keyword">if</span> (!g_mutex.<span class="built_in">isLockedByThisThread</span>()) &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;FAIL\n&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  ++g_count;</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="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">void</span>)</span></span>&#123;</span><br><span class="line">  ... <span class="comment">//do something</span></span><br><span class="line">  <span class="built_in">foo</span>();</span><br><span class="line">  <span class="keyword">if</span> (g_count != <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;foo calls twice\n&quot;</span>);</span><br><span class="line">    <span class="built_in">abort</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  ... <span class="comment">//do something</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，利用到了 <code>__attribute__((noinline))</code> 来显式指明了 <code>foo</code> 是非内联函数，因此编译器不会对其进行内联优化。如果编译器对 <code>foo</code> 执行了内联优化，则 lock 的作用域将会从 <code>foo</code> 的局部作用域扩张到 <code>mian</code> 的作用域当中，这就很容易导致一些诡异的 bug 的产生</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文是 muduo 网络库源码剖析系列文章的第三篇文章，主要探讨了 muduo 网络库对互斥量和条件变量的封装，以及我如何使用 C++11 的 mutex 以及 condition_variable 来对其进行重新实现，其中包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;基于 clang 的线程安全注解实现的 Mutex 类&lt;/li&gt;
&lt;li&gt;基于 condition_variable 实现的 Condition 类&lt;/li&gt;
&lt;li&gt;RAII 对象测试的一些 tips&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文中所涉及的代码来源于我的个人项目：tmuduo。本着“纸上学来终觉浅，绝知此事要躬行”的想法，我将 muduo 网络库重新实现了一遍，并在上面验证了自己的不少猜想。项目的仓库地址为 &lt;a href=&quot;mailto:&amp;#103;&amp;#105;&amp;#116;&amp;#64;&amp;#x67;&amp;#105;&amp;#116;&amp;#104;&amp;#x75;&amp;#x62;&amp;#46;&amp;#99;&amp;#x6f;&amp;#109;&quot;&gt;&amp;#103;&amp;#105;&amp;#116;&amp;#64;&amp;#x67;&amp;#105;&amp;#116;&amp;#104;&amp;#x75;&amp;#x62;&amp;#46;&amp;#99;&amp;#x6f;&amp;#109;&lt;/a&gt;:Phoenix500526/Tmuduo.git, 欢迎各位fork + star，一起加入学习&lt;/p&gt;</summary>
    
    
    
    <category term="muduo源码剖析" scheme="https://phoenix500526.github.io/categories/muduo%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="多线程" scheme="https://phoenix500526.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
    <category term="muduo网络库" scheme="https://phoenix500526.github.io/tags/muduo%E7%BD%91%E7%BB%9C%E5%BA%93/"/>
    
    <category term="mutex" scheme="https://phoenix500526.github.io/tags/mutex/"/>
    
    <category term="condition_variable" scheme="https://phoenix500526.github.io/tags/condition-variable/"/>
    
  </entry>
  
  <entry>
    <title>clang 的线程安全注解TSA</title>
    <link href="https://phoenix500526.github.io/2020/11/05/clang-%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E6%B3%A8%E8%A7%A3TSA/"/>
    <id>https://phoenix500526.github.io/2020/11/05/clang-%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E6%B3%A8%E8%A7%A3TSA/</id>
    <published>2020-11-05T17:42:56.000Z</published>
    <updated>2026-05-27T05:02:59.527Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>本文主要涉及到 clang 编译器的线程安全注解功能(Thread Safety Annotation, 以下简称TSA)，主要包含以下内容:</p><blockquote><ul><li>什么是 TSA？</li><li>TSA 常用的宏定义(按照修饰对象来分类)</li><li>使用 TSA 的注意事项</li></ul></blockquote><p>严格来讲，这一篇文章并不涉及 TSA 的所有宏定义，只是解释了一些基本的概念和常用的几个宏定义。我个人认为对于研发工具的学习应当从实际应用出发，先了解常用的功能如何使用，并在后续的开发中陆续补充新的用法。想学语言一样学习开发工具，一上来就抱着文档统统啃下的做法并不务实。</p><span id="more"></span><h4 id="什么是-TSA"><a href="#什么是-TSA" class="headerlink" title="什么是 TSA ?"></a>什么是 TSA ?</h4><p>在进行 C++ 并发编程中，最原始的模型应该就是基于锁来对共享数据进行保护的并发模型了。由于这种模型很容易出问题，因此需要采用各种动态的或静态的检查工具来避免程序出现 data race。而 clang 的 TSA 就是一个简单易用的静态检查工具。我们通过代码注解(annotation)的方式来告知编译器，哪些成员变量或成员函数受到了哪个 mutex 的保护。这样当开发者忘记加锁或尝试重复加锁时，编译器能够及时发出警告。这在现代程序开发时非常有用，因为一个程序往往开发和维护的人未必是同一个人。注解不仅能帮助编译器理解开发者的意图，还可以帮助维护者理解这一意图，这样也就避免了犯非常低级的错误。</p><h4 id="TSA-常用的宏定义有哪些？"><a href="#TSA-常用的宏定义有哪些？" class="headerlink" title="TSA 常用的宏定义有哪些？"></a>TSA 常用的宏定义有哪些？</h4><p>clang 提供的 TSA 有两种用法，一种属于 GNU 风格，例如__attribute__((…))), 另一种则是属于 C++11 风格，例如[[…]],但不管是哪一种风格，一种推荐的做法是利用宏来包装这些相应的关键字，这样如果编译器不是 clang 的话，这些宏会被自动置空。接下来对 TSA 部分的介绍也会按照这种宏定义模式来讨论，并通过代码注释来进行相应的说明。更多的宏定义可以参考 clang 的官方文档[1]</p><h6 id="修饰类、结构体以及typedef别名的宏"><a href="#修饰类、结构体以及typedef别名的宏" class="headerlink" title="修饰类、结构体以及typedef别名的宏"></a>修饰类、结构体以及<code>typedef</code>别名的宏</h6><p>宏<code>CAPABILITY</code>表明某个类对象可以当作 capability 使用，其中 x 的类型是 string，能够在错误信息当中指出对应的 capability 的名称, 而宏<code>SCOPED_CAPABILITY</code>用于修饰基于 RAII 实现的 capability。</p><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><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="meta">#<span class="keyword">define</span> CAPABILITY(x) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(capability(x))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SCOPED_CAPABILITY \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">class</span> <span class="title">CAPABILITY</span> <span class="params">( <span class="string">&quot;mutex&quot;</span> )</span> Mutex </span>&#123;</span><br><span class="line"><span class="keyword">public</span> :</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">lock</span> <span class="params">( )</span> <span class="title">ACQUIRE</span> <span class="params">( t hi s )</span> </span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">readerLock</span> <span class="params">( )</span> ACQUIRE <span class="title">SHARED</span><span class="params">( t hi s )</span> </span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">unlock</span> <span class="params">( )</span> <span class="title">RELEASE</span><span class="params">( t hi s )</span> </span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">readerUnlock</span> <span class="params">( )</span> RELEASE <span class="title">SHARED</span><span class="params">( t hi s )</span> </span>;</span><br><span class="line">&#125; ;</span><br></pre></td></tr></table></figure><p> 这里简单地说明一下 capability： capability 是 TSA 中的一个概念，用来为临界资源的访问提供相应的保护。这样讲可能有点抽象，你可以简单地将其理解成为一个标签，这个标签可以被贴到任何锁上面，不论它是标准库的 mutex 还是你自己实现的 Mutex。一旦一个锁被贴上了这个标签，TSA 就会对这个锁进行重点关注。因此语句 <code>class CAPABILITY ( &quot;mutex&quot; ) Mutex</code> 表明 Mutex 类型的对象可以作为一个 capability，而且它的名称就是 “mutex”。</p><h6 id="修饰数据成员的宏-GUARDED-BY"><a href="#修饰数据成员的宏-GUARDED-BY" class="headerlink" title="修饰数据成员的宏 GUARDED_BY"></a>修饰数据成员的宏 GUARDED_BY</h6><p>宏<code>GUARD_BY</code>用于修饰对象，表明该对象需要受到 capability 的保护, 而宏<code>PT_GUARDED_BY(mutex)</code> 则用于修饰指针类型变量，在更改指针变量<strong>所指向的内容</strong>前需要加锁，否则发出警告。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> GUARDED_BY(x) \</span></span><br><span class="line"><span class="meta">THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PT_GUARDED_BY(x) \</span></span><br><span class="line"><span class="meta">THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))</span></span><br><span class="line"><span class="comment">//示例用法</span></span><br><span class="line"><span class="function"><span class="type">int</span> *p1             <span class="title">GUARDED_BY</span><span class="params">(mu)</span></span>;</span><br><span class="line"><span class="function"><span class="type">int</span> *p2             <span class="title">PT_GUARDED_BY</span><span class="params">(mu)</span></span>;</span><br><span class="line"><span class="function">unique_ptr&lt;<span class="type">int</span>&gt; p3  <span class="title">PT_GUARDED_BY</span><span class="params">(mu)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  p1 = <span class="number">0</span>;             <span class="comment">// Warning!</span></span><br><span class="line">  *p2 = <span class="number">42</span>;           <span class="comment">// Warning!</span></span><br><span class="line">  p2 = <span class="keyword">new</span> <span class="type">int</span>;       <span class="comment">// OK.</span></span><br><span class="line">  *p3 = <span class="number">42</span>;           <span class="comment">// Warning!</span></span><br><span class="line">  p<span class="number">3.</span><span class="built_in">reset</span>(<span class="keyword">new</span> <span class="type">int</span>);  <span class="comment">// OK.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h6 id="修饰函数-方法-成员函数-的宏"><a href="#修饰函数-方法-成员函数-的宏" class="headerlink" title="修饰函数/方法(成员函数)的宏"></a>修饰函数/方法(成员函数)的宏</h6><p>宏<code>REQUIRES</code>声明调用线程必须拥有对指定的 capability 具有独占访问权。可以指定多个 capabilities。函数/方法在访问资源时，必须先上锁，再调用函数，然后再解锁(注意，不是在函数内解锁)</p><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><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="meta">#<span class="keyword">define</span> REQUIRES(...) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))</span></span><br><span class="line"><span class="comment">//示范用法</span></span><br><span class="line">Mutex mu1, mu2;</span><br><span class="line"><span class="function"><span class="type">int</span> a <span class="title">GUARDED_BY</span><span class="params">(mu1)</span></span>;</span><br><span class="line"><span class="function"><span class="type">int</span> b <span class="title">GUARDED_BY</span><span class="params">(mu2)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">()</span> <span class="title">REQUIRES</span><span class="params">(mu1, mu2)</span> </span>&#123;</span><br><span class="line">  a = <span class="number">0</span>;</span><br><span class="line">  b = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  mu<span class="number">1.L</span>ock();</span><br><span class="line">  <span class="built_in">foo</span>();         <span class="comment">// Warning!  Requires mu2.</span></span><br><span class="line">  mu<span class="number">1.</span><span class="built_in">Unlock</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>宏<code>ACQUIRE</code>表示一个函数/方法需要持有一个 capability，但并不释放这个 capability。调用者在调用被 ACQUIRE 修饰的函数/方法时，要确保没有持有任何 capability，同时在函数/方法结束时会持有一个 capability(加锁的过程发生在函数体内),而宏<code>RELEASE</code> 则和宏<code>ACQUIRE</code> 作用相反，它们表示调用方在调用该函数/方法时需要先持有锁，而当函数执行结束后会释放锁(释放锁的行为发生在函数体内)，具体例子如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> ACQUIRE(...) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> RELEASE(...) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))</span></span><br><span class="line"><span class="comment">//示范用法</span></span><br><span class="line">MutexLock mu;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyClass</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">doSomething</span><span class="params">()</span></span>&#123;</span><br><span class="line">cout &lt;&lt; <span class="string">&quot;x = &quot;</span> &lt;&lt; x &lt;&lt; endl;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">MyClass</span>() = <span class="keyword">default</span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">init</span><span class="params">(<span class="type">const</span> <span class="type">int</span> a)</span></span>&#123;</span><br><span class="line">x = a;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">cleanup</span><span class="params">()</span></span>&#123;</span><br><span class="line">x = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"><span class="type">int</span> x;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="function">MyClass myObject <span class="title">GUARDED_BY</span><span class="params">(mu)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">lockAndInit</span><span class="params">(MyClass&amp; myObject)</span> <span class="title">ACQUIRE</span><span class="params">(mu)</span> </span>&#123;</span><br><span class="line">  mu.<span class="built_in">lock</span>();</span><br><span class="line">  myObject.<span class="built_in">init</span>(<span class="number">10</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">cleanupAndUnlock</span><span class="params">(MyClass&amp; myObject)</span> <span class="title">RELEASE</span><span class="params">(mu)</span> </span>&#123;</span><br><span class="line">  myObject.<span class="built_in">cleanup</span>();</span><br><span class="line">&#125;                          <span class="comment">// Warning!  Need to unlock mu.</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">()</span> </span>&#123;</span><br><span class="line">MyClass myObject;<span class="comment">//局部对象掩盖了全局的 myObject 对象，而全局的 myObject 对象受到了 mu 的保护</span></span><br><span class="line"><span class="built_in">lockAndInit</span>(myObject);</span><br><span class="line">myObject.<span class="built_in">doSomething</span>();</span><br><span class="line"><span class="built_in">cleanupAndUnlock</span>(myObject);</span><br><span class="line">myObject.<span class="built_in">doSomething</span>(); </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">(<span class="type">void</span>)</span></span>&#123;</span><br><span class="line"><span class="built_in">test</span>();</span><br><span class="line">myObject.<span class="built_in">doSomething</span>();<span class="comment">// Warning! MyObject is guarded by mu</span></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>宏<code>EXCLUDES</code>用于显式声明函数/方法不应该持有某个特定的 capability。由于 mutex 的实现通常是不可重入的，因此 EXCLUDES 通常被用来预防死锁</p><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><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="meta">#<span class="keyword">define</span> EXCLUDES(...) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))</span></span><br><span class="line"><span class="comment">//实例用法</span></span><br><span class="line">Mutex mu;</span><br><span class="line"><span class="function"><span class="type">int</span> a <span class="title">GUARDED_BY</span><span class="params">(mu)</span></span>;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">clear</span><span class="params">()</span> <span class="title">EXCLUDES</span><span class="params">(mu)</span> </span>&#123;</span><br><span class="line">  mu.<span class="built_in">Lock</span>();</span><br><span class="line">  a = <span class="number">0</span>;</span><br><span class="line">  mu.<span class="built_in">Unlock</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">reset</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  mu.<span class="built_in">Lock</span>();</span><br><span class="line">  <span class="built_in">clear</span>();     <span class="comment">// Warning!  Caller cannot hold &#x27;mu&#x27;. </span></span><br><span class="line">  mu.<span class="built_in">Unlock</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>宏<code>ASSERT_*</code>表示在运行时检测调用线程是否持有 capability，主要有以下两个</p><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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> ASSERT_CAPABILITY(x) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ASSERT_SHARED_CAPABILITY(x) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))</span></span><br></pre></td></tr></table></figure><p>宏<code>NO_THREAD_SAFETY_ANALYSIS</code>表示关闭某个函数/方法的 TSA 检测，通常只用于两种情况：1，该函数/方法可以被做成非线程安全；2、函数/方法太过复杂，TSA 无法进行检测</p><figure class="highlight c++"><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="meta">#<span class="keyword">define</span> NO_THREAD_SAFETY_ANALYSIS \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)__</span></span><br></pre></td></tr></table></figure><p>宏<code>RETURN_CAPABILITY</code>通常用于修饰那些被当作 capability getter 的函数，这些函数会返回 capability 的引用或指针</p><figure class="highlight c++"><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="meta">#<span class="keyword">define</span> RETURN_CAPABILITY(x) \</span></span><br><span class="line"><span class="meta">  THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))</span></span><br></pre></td></tr></table></figure><h4 id="使用-TSA-的一些注意事项"><a href="#使用-TSA-的一些注意事项" class="headerlink" title="使用 TSA 的一些注意事项"></a>使用 TSA 的一些注意事项</h4><p>不过 TSA 是静态检查工具，因此对它的期望不应当过高，在使用的过程当中依然有一些注意事项需要了解：</p><ol><li><p>一般而言，注解通常被当作函数接口的一部分进行解析，因此最好放在头文件当中，而不是 .cc 文件当中。(NO_THREAD_SAFETY_ANALYSIS 除外)</p></li><li><p>TSA 的解析与检测主要在编译期间执行，因此不能对运行时才能确定的条件语句进行检测。例如以下做法是错误的：</p><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="type">bool</span> b = <span class="built_in">needsToLock</span>();</span><br><span class="line"><span class="keyword">if</span> (b) &#123;</span><br><span class="line">mu.<span class="built_in">Lock</span>();</span><br><span class="line">&#125;</span><br><span class="line">...  <span class="comment">// Warning!  Mutex &#x27;mu&#x27; is not held on every path through here.</span></span><br><span class="line"><span class="keyword">if</span> (b) &#123;</span><br><span class="line">mu.<span class="built_in">Unlock</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>TSA 仅依赖于函数的属性的声明，它并不会将函数调用展开并内联到指定位置，因此下面的做法也是错误的(它使用 mu.lock() 进行显式的上锁，却希望使用函数调用来进行解锁)：</p><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><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="keyword">template</span>&lt;<span class="keyword">class</span> <span class="title class_">T</span>&gt;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AutoCleanup</span> &#123;</span><br><span class="line">T* object;</span><br><span class="line"><span class="built_in">void</span> (T::*mp)();</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">     <span class="built_in">AutoCleanup</span>(T* obj, <span class="built_in">void</span> (T::*imp)()) : <span class="built_in">object</span>(obj), <span class="built_in">mp</span>(imp) &#123; &#125;</span><br><span class="line">     ~<span class="built_in">AutoCleanup</span>() &#123; (object-&gt;*mp)(); &#125;</span><br><span class="line">   &#125;;</span><br><span class="line">   </span><br><span class="line">   Mutex mu;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">foo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">   mu.<span class="built_in">Lock</span>();</span><br><span class="line">   <span class="built_in">AutoCleanup</span>&lt;Mutex&gt;(&amp;mu, &amp;Mutex::Unlock);</span><br><span class="line">     <span class="comment">// ...</span></span><br><span class="line">&#125;  <span class="comment">// Warning, mu is not unlocked.</span></span><br></pre></td></tr></table></figure></li><li><p>TSA 无法追踪指针的指向，因此当两个指针指向一个互斥锁时，会导致警告的发生，如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MutexUnlocker</span> &#123;</span><br><span class="line">     Mutex* mu;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">     <span class="built_in">MutexUnlocker</span>(Mutex* m) <span class="built_in">RELEASE</span>(m) : <span class="built_in">mu</span>(m)  &#123; mu-&gt;<span class="built_in">Unlock</span>(); &#125;</span><br><span class="line">     ~<span class="built_in">MutexUnlocker</span>() <span class="built_in">ACQUIRE</span>(mu) &#123; mu-&gt;<span class="built_in">Lock</span>(); &#125;</span><br><span class="line">&#125;;</span><br><span class="line">   </span><br><span class="line">Mutex mutex;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">test</span><span class="params">()</span> <span class="title">REQUIRES</span><span class="params">(mutex)</span> </span>&#123;</span><br><span class="line">&#123;</span><br><span class="line">       <span class="function">MutexUnlocker <span class="title">munl</span><span class="params">(&amp;mutex)</span></span>;  <span class="comment">// unlocks mutex</span></span><br><span class="line">       <span class="built_in">doSomeIO</span>();</span><br><span class="line">     &#125;                              <span class="comment">// Warning: locks munl.mu</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>mun1 中的成员变量 mu 在析构的时候被释放，但 TSA 并不能意识到 mutex 与 mun1.mu 指向了同一个互斥锁。因此，会显示出警告信息：<code>mun1.mu unlocked</code>。</p></li></ol><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><ol><li><a href="https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#reference-guide">Clang 12 documentation - Thread Safty Analysis</a> </li><li><a href="https://my.oschina.net/u/4397303/blog/3281876">clang的线程安全分析模块 thread safety analysis</a></li><li><a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/42958.pdf">clang_thread_safety_annotation.pdf</a></li></ol>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;本文主要涉及到 clang 编译器的线程安全注解功能(Thread Safety Annotation, 以下简称TSA)，主要包含以下内容:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;什么是 TSA？&lt;/li&gt;
&lt;li&gt;TSA 常用的宏定义(按照修饰对象来分类)&lt;/li&gt;
&lt;li&gt;使用 TSA 的注意事项&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;严格来讲，这一篇文章并不涉及 TSA 的所有宏定义，只是解释了一些基本的概念和常用的几个宏定义。我个人认为对于研发工具的学习应当从实际应用出发，先了解常用的功能如何使用，并在后续的开发中陆续补充新的用法。想学语言一样学习开发工具，一上来就抱着文档统统啃下的做法并不务实。&lt;/p&gt;</summary>
    
    
    
    <category term="瑞士军刀" scheme="https://phoenix500526.github.io/categories/%E7%91%9E%E5%A3%AB%E5%86%9B%E5%88%80/"/>
    
    
    <category term="clang" scheme="https://phoenix500526.github.io/tags/clang/"/>
    
    <category term="TSA" scheme="https://phoenix500526.github.io/tags/TSA/"/>
    
    <category term="多线程" scheme="https://phoenix500526.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— lua 服务间是如何交互的</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-lua-%E6%9C%8D%E5%8A%A1%E9%97%B4%E6%98%AF%E5%A6%82%E4%BD%95%E4%BA%A4%E4%BA%92%E7%9A%84/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-lua-%E6%9C%8D%E5%8A%A1%E9%97%B4%E6%98%AF%E5%A6%82%E4%BD%95%E4%BA%A4%E4%BA%92%E7%9A%84/</id>
    <published>2020-11-04T20:21:24.000Z</published>
    <updated>2026-05-27T05:02:59.527Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第七篇文章，探讨了 skynet 框架下，同一 skynet 节点内不同的lua 服务之间是如何通过消息来进行交互，主要包含了以下内容：</p><blockquote><ul><li>lua 服务的消息协议</li><li>lua 服务如何注册自己的消息及对应的回调函数</li><li>lua 服务是如何接受消息的？</li><li>lua 服务是如何发送消息的？</li></ul></blockquote><span id="more"></span><h4 id="lua-服务的消息协议"><a href="#lua-服务的消息协议" class="headerlink" title="lua 服务的消息协议"></a>lua 服务的消息协议</h4><p>skynet 使用 proto 来描述不同的消息协议。在最开始的时候，proto 是一个空表，需要由 <code>skynet.register_protocol</code> 进行消息协议的注册。skynet 在启动 lua 服务的初期会默认注册 lua，response 以及 error 类型的消息协议，这个过程通常在 <code>require &quot;skynet&quot;</code>语句中执行。<code>skynet.register_protocol</code>函数如下：</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.register_protocol</span><span class="params">(class)</span></span></span><br><span class="line">    <span class="keyword">local</span> name = class.name</span><br><span class="line">    <span class="keyword">local</span> id = class.id</span><br><span class="line">    <span class="built_in">assert</span>(proto[name] == <span class="literal">nil</span> <span class="keyword">and</span> proto[id] == <span class="literal">nil</span>)</span><br><span class="line">    <span class="built_in">assert</span>(<span class="built_in">type</span>(name) == <span class="string">&quot;string&quot;</span> <span class="keyword">and</span> <span class="built_in">type</span>(id) == <span class="string">&quot;number&quot;</span> <span class="keyword">and</span> id &gt;=<span class="number">0</span> <span class="keyword">and</span> id &lt;=<span class="number">255</span>)</span><br><span class="line">    proto[name] = class</span><br><span class="line">    proto[id] = class</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    <span class="keyword">local</span> REG = skynet.register_protocol</span><br><span class="line">    <span class="comment">--注册不同的消息类型，有普通的 lua 消息，响应消息以及错误消息</span></span><br><span class="line">    REG &#123;</span><br><span class="line">        name = <span class="string">&quot;lua&quot;</span>,</span><br><span class="line">        id = skynet.PTYPE_LUA,</span><br><span class="line">        pack = skynet.pack,</span><br><span class="line">        <span class="built_in">unpack</span> = skynet.<span class="built_in">unpack</span>,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    REG &#123;</span><br><span class="line">        name = <span class="string">&quot;response&quot;</span>,</span><br><span class="line">        id = skynet.PTYPE_RESPONSE,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    REG &#123;</span><br><span class="line">        name = <span class="string">&quot;error&quot;</span>,</span><br><span class="line">        id = skynet.PTYPE_ERROR,</span><br><span class="line">        <span class="built_in">unpack</span> = <span class="function"><span class="keyword">function</span><span class="params">(...)</span></span> <span class="keyword">return</span> ... <span class="keyword">end</span>,</span><br><span class="line">        dispatch = _error_dispatch,</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>从 skynet 默认注册的消息类型来推断，我们知道一个消息协议应当包含有以下的一些字段：</p><ul><li>name:表明了该消息协议的类型名称</li><li>id:表明该消息协议的类型编号，包括了以下几种不同的类型  <figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">local</span> skynet = &#123;</span><br><span class="line">    <span class="comment">-- read skynet.h</span></span><br><span class="line">    PTYPE_TEXT = <span class="number">0</span>,     <span class="comment">--文本类型</span></span><br><span class="line">    PTYPE_RESPONSE = <span class="number">1</span>, <span class="comment">--响应消息</span></span><br><span class="line">    PTYPE_MULTICAST = <span class="number">2</span>,<span class="comment">--组播消息</span></span><br><span class="line">    PTYPE_CLIENT = <span class="number">3</span>,</span><br><span class="line">    PTYPE_SYSTEM = <span class="number">4</span>,</span><br><span class="line">    PTYPE_HARBOR = <span class="number">5</span>,</span><br><span class="line">    PTYPE_SOCKET = <span class="number">6</span>,</span><br><span class="line">    PTYPE_ERROR = <span class="number">7</span>,    <span class="comment">--错误消息</span></span><br><span class="line">    PTYPE_QUEUE = <span class="number">8</span>,    <span class="comment">-- used in deprecated mqueue, use skynet.queue instead</span></span><br><span class="line">    PTYPE_DEBUG = <span class="number">9</span>,</span><br><span class="line">    PTYPE_LUA = <span class="number">10</span>,     <span class="comment">--lua 服务类型的消息</span></span><br><span class="line">    PTYPE_SNAX = <span class="number">11</span>,</span><br><span class="line">    PTYPE_TRACE = <span class="number">12</span>,   <span class="comment">-- use for debug trace</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>pack:发送消息时所用到的打包函数</li><li>unpack:接收消息时调用的解包函数</li><li>dispatch:由消息提供方指定对应类型消息的处理函数，如果没有指定，则最终会调用 <code>skynet.dispatch(typename, func)</code><br>函数来处理</li></ul><p>说完基本的消息协议，我们来看看 skynet 定义的三种不同类型的消息都有什么作用：</p><blockquote><ol><li>lua 型消息：采用 skynet.pack 和 skynet.unpack 进行消息的打包和解包, 默认调用<code>skynet.dispatch(typename, func)</code>进行消息的派发</li><li>response 型消息：response 消息主要用于处理skynet.call调用和定时器的返回。当源服务向目的服务发送请求，会附带一个 session，目的服务在处理完请求后，会将 session 加入 response 消息中一起通过 <code>skynet.ret</code> 返回给源服务</li><li>error 型消息：当调用 <code>skynet.call</code> 发送错误消息时，源服务可以接收到一个 error 类型的消息</li></ol></blockquote><h4 id="lua-服务如何注册自己的消息及对应的回调函数"><a href="#lua-服务如何注册自己的消息及对应的回调函数" class="headerlink" title="lua 服务如何注册自己的消息及对应的回调函数"></a>lua 服务如何注册自己的消息及对应的回调函数</h4><p>讲完了 lua 服务的消息服务的定义，我们以 example/simplemonitor.lua 中的服务来说明一下，lua 服务之间是如何相互收发信息的。而在这之前，我们需要看看 simplemonitor.lua 定义：</p><figure class="highlight lua"><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"><span class="keyword">local</span> skynet = <span class="built_in">require</span> <span class="string">&quot;skynet&quot;</span></span><br><span class="line"><span class="comment">-- It&#x27;s a simple service exit monitor, you can do something more when a service exit.</span></span><br><span class="line"><span class="keyword">local</span> service_map = &#123;&#125;</span><br><span class="line"></span><br><span class="line">skynet.register_protocol &#123;</span><br><span class="line">    name = <span class="string">&quot;client&quot;</span>,</span><br><span class="line">    id = skynet.PTYPE_CLIENT,   <span class="comment">-- PTYPE_CLIENT = 3</span></span><br><span class="line">    <span class="built_in">unpack</span> = <span class="function"><span class="keyword">function</span><span class="params">()</span></span> <span class="keyword">end</span>,</span><br><span class="line">    dispatch = <span class="function"><span class="keyword">function</span><span class="params">(_, address)</span></span></span><br><span class="line">        <span class="keyword">local</span> w = service_map[address]</span><br><span class="line">        <span class="keyword">if</span> w <span class="keyword">then</span></span><br><span class="line">            <span class="keyword">for</span> watcher <span class="keyword">in</span> <span class="built_in">pairs</span>(w) <span class="keyword">do</span></span><br><span class="line">                skynet.redirect(watcher, address, <span class="string">&quot;error&quot;</span>, <span class="number">0</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">            service_map[address] = <span class="literal">false</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">monitor</span><span class="params">(session, watcher, command, service)</span></span></span><br><span class="line">    <span class="built_in">assert</span>(command, <span class="string">&quot;WATCH&quot;</span>)</span><br><span class="line">    <span class="keyword">local</span> w = service_map[service]</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> w <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">if</span> w == <span class="literal">false</span> <span class="keyword">then</span></span><br><span class="line">            skynet.ret(skynet.pack(<span class="literal">false</span>))</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        w = &#123;&#125;</span><br><span class="line">        service_map[service] = w</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    w[watcher] = <span class="literal">true</span></span><br><span class="line">    skynet.ret(skynet.pack(<span class="literal">true</span>))</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">skynet.start(<span class="function"><span class="keyword">function</span><span class="params">()</span></span></span><br><span class="line">    skynet.dispatch(<span class="string">&quot;lua&quot;</span>, monitor)</span><br><span class="line"><span class="keyword">end</span>)</span><br></pre></td></tr></table></figure><p>如以往的文章所提到的那样，当使用 <code>skynet.newservice</code> 函数启动一个新的 lua 服务时，会执行相应的脚本来完成服务的初始化。在 simplemonitor.lua 脚本中，先执行了 <code>require &quot;skynet&quot;</code>，这不仅会将相应的函数导入到当前 lua 脚本当中，还会执行 <code>skynet.register_protocol</code>为 simplemonitor 注册三种默认消息协议。随后，simplemonitor.lua 又调用了 <code>skynet.register_protocol</code> 注册了一个 client 类型的 lua 消息协议，并指定了对应的 dispatch 函数。随后调用 <code>skynet.start</code> 来启动 simplemonitor 服务。在上一篇文章<a href="/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E5%A6%82%E4%BD%95%E5%9C%A8-lua-%E6%9C%8D%E5%8A%A1%E4%B8%AD%E5%90%AF%E5%8A%A8%E5%8F%A6%E4%B8%80%E4%B8%AA-lua-%E6%9C%8D%E5%8A%A1/">《skynet 源码阅读笔记 —— 如何在 lua 服务中启动另一个 lua 服务》</a> 中提到了 <code>skynet.start</code> 会将 simplemonitor 服务的消息回调函数设置为 <code>skynet.dispatch_message</code>,然后执行 <code>skynet.dipatch(&quot;lua&quot;, monitor)</code>进行服务的初始化。</p><h4 id="lua-服务是如何接受消息的？"><a href="#lua-服务是如何接受消息的？" class="headerlink" title="lua 服务是如何接受消息的？"></a>lua 服务是如何接受消息的？</h4><p>讨论完 lua 服务是如何注册自己的消息类型及定义消息对应的回调函数后，我们来看看 lua 服务是如何接受消息的。我们先来看看 <code>skynet.dispatch</code> 函数的实现：</p><figure class="highlight lua"><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">--skynet.lua</span></span><br><span class="line"><span class="comment">--simplemonitor 的调用形式为 skynet.dispatch(&quot;lua&quot;, monitor)</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.dispatch</span><span class="params">(typename, func)</span></span></span><br><span class="line">    <span class="comment">--取出 lua 型消息对应的协议</span></span><br><span class="line">    <span class="keyword">local</span> p = proto[typename]</span><br><span class="line">    <span class="keyword">if</span> func <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">local</span> ret = p.dispatch</span><br><span class="line">        <span class="comment">--将对应的 dispatch 函数设置为 monitor</span></span><br><span class="line">        p.dispatch = func</span><br><span class="line">        <span class="comment">--返回原来的 dispatch 函数</span></span><br><span class="line">        <span class="keyword">return</span> ret</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> p <span class="keyword">and</span> p.dispatch</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>从上述代码可以看出，当 simplemonitor 服务启动完毕后，对应的 lua 消息协议的 dispatch 函数实际上就是 <code>monitor</code> 函数。接着，我们再来看看 <code>skynet.dispatch_message</code></p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.dispatch_message</span><span class="params">(...)</span></span></span><br><span class="line">    <span class="comment">--调用 raw_dispatch_message 进行消息的转发</span></span><br><span class="line">    <span class="keyword">local</span> succ, err = <span class="built_in">pcall</span>(raw_dispatch_message,...)</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">true</span> <span class="keyword">do</span></span><br><span class="line">        <span class="keyword">local</span> co = tremove(fork_queue,<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">if</span> co == <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">local</span> fork_succ, fork_err = <span class="built_in">pcall</span>(suspend,co,coroutine_resume(co))</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> fork_succ <span class="keyword">then</span></span><br><span class="line">            <span class="keyword">if</span> succ <span class="keyword">then</span></span><br><span class="line">                succ = <span class="literal">false</span></span><br><span class="line">                err = <span class="built_in">tostring</span>(fork_err)</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                err = <span class="built_in">tostring</span>(err) .. <span class="string">&quot;\n&quot;</span> .. <span class="built_in">tostring</span>(fork_err)</span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    <span class="built_in">assert</span>(succ, <span class="built_in">tostring</span>(err))</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">raw_dispatch_message</span><span class="params">(prototype, msg, sz, session, source)</span></span></span><br><span class="line">    <span class="keyword">if</span> prototype == <span class="number">1</span> <span class="keyword">then</span></span><br><span class="line">        ... <span class="comment">--prototype == 1代表响应消息类型</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment">--取出相应的消息协议</span></span><br><span class="line">        <span class="keyword">local</span> p = proto[prototype]</span><br><span class="line">        <span class="keyword">if</span> p == <span class="literal">nil</span> <span class="keyword">then</span></span><br><span class="line">            ...    <span class="comment">--若 p == nil 则调用 c.send 发送一个 ERROR 型消息</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">local</span> f = p.dispatch</span><br><span class="line">        <span class="keyword">if</span> f <span class="keyword">then</span></span><br><span class="line">            <span class="comment">-- co_create 会从协程池中获取一个空的协程，如果没有则创建一个新的协程，并将 dispatch 函数交给这个协程去执行。</span></span><br><span class="line">            <span class="keyword">local</span> co = co_create(f)</span><br><span class="line">            session_coroutine_id[co] = session</span><br><span class="line">            session_coroutine_address[co] = source</span><br><span class="line">            <span class="keyword">local</span> traceflag = p.trace</span><br><span class="line">            <span class="keyword">if</span> traceflag == <span class="literal">false</span> <span class="keyword">then</span></span><br><span class="line">                <span class="comment">-- force off</span></span><br><span class="line">                trace_source[source] = <span class="literal">nil</span></span><br><span class="line">                session_coroutine_tracetag[co] = <span class="literal">false</span></span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="keyword">local</span> tag = trace_source[source]</span><br><span class="line">                <span class="keyword">if</span> tag <span class="keyword">then</span></span><br><span class="line">                    trace_source[source] = <span class="literal">nil</span></span><br><span class="line">                    c.trace(tag, <span class="string">&quot;request&quot;</span>)</span><br><span class="line">                    session_coroutine_tracetag[co] = tag</span><br><span class="line">                <span class="keyword">elseif</span> traceflag <span class="keyword">then</span></span><br><span class="line">                    <span class="comment">-- set running_thread for trace</span></span><br><span class="line">                    running_thread = co</span><br><span class="line">                    skynet.trace()</span><br><span class="line">                <span class="keyword">end</span></span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">            <span class="comment">--启动并执行协程，将协程执行的结果返回给suspend函数，suspend 会根据这个结果执行相应的操作</span></span><br><span class="line">            suspend(co, coroutine_resume(co, session,source, p.<span class="built_in">unpack</span>(msg,sz)))</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            trace_source[source] = <span class="literal">nil</span></span><br><span class="line">            <span class="keyword">if</span> session ~= <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line">                c.send(source, skynet.PTYPE_ERROR, session, <span class="string">&quot;&quot;</span>)</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                unknown_request(session, source, msg, sz, proto[prototype].name)</span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>结合上述带注释的代码，我们描述一下整体的过程：当服务 A 向 simplemonitor 发送一条消息时，会将这条消息放入到 simplemonitor 对应的 snlua 服务所属的次级消息队列当中(skynet当中有多个 snlua 类型的服务，分别对应不同的 lua 服务)。worker 线程会将其取出并消费，在消费的过程当中会调用该消息所指定的 callback 函数。而 <code>skynet.start</code> 已经通过 <code>c.callback(skynet.dispatch_message)</code> 将 simplemonitor 的消息的回调函数设置为 <code>skynet.dispatch_message</code>。此时，worker线程最终就会调用到 <code>raw_dispatch_message</code>函数。这个函数会获得一个新的空的协程来执行消息协议中指定的 dispatch 函数。对应协程一旦执行起来完毕，会调用 <code>coroutine_yield</code> 函数将自身挂起，并返回挂起的原因。<code>suspend</code>会根据这个原因做不同的处理</p><h4 id="lua-服务是如何发送消息的？"><a href="#lua-服务是如何发送消息的？" class="headerlink" title="lua 服务是如何发送消息的？"></a>lua 服务是如何发送消息的？</h4><p>讲完了当 simplemonitor 收到消息的行为，我们再来看看发送消息的行为。假设现在有一个服务 A 需要向另一个服务 B 发送一条消息，那么他需要调用 <code>skynet.send</code> 函数。我们来看看 <code>skynet.send</code> 函数的定义：</p><figure class="highlight lua"><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="function"><span class="keyword">function</span> <span class="title">skynet.send</span><span class="params">(addr, typename, ...)</span></span></span><br><span class="line">    <span class="keyword">local</span> p = proto[typename]</span><br><span class="line">    <span class="keyword">return</span> c.send(addr, p.id, <span class="number">0</span> , p.pack(...))</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p><code>skynet.send</code>会调用 <code>c.send(addr, p.id, 0 , p.pack(...))</code> 函数来发送消息，其中 <code>c.send</code> 函数的参数从左至右分别是目标地址，消息协议类型，session ID，自定义参数列表。<br>我们再来看看 <code>c.send</code> 所对应的函数 <code>lsend</code> 是如何实现的：</p><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><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="comment">//lua-skynet.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">lsend</span><span class="params">(lua_State *L)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> send_message(L, <span class="number">0</span>, <span class="number">2</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">send_message</span><span class="params">(lua_State *L, <span class="type">int</span> source, <span class="type">int</span> idx_type)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">context</span> =</span> lua_touserdata(L, lua_upvalueindex(<span class="number">1</span>));</span><br><span class="line">    <span class="comment">//获得目的地址 addr</span></span><br><span class="line">    <span class="type">uint32_t</span> dest = (<span class="type">uint32_t</span>)lua_tointeger(L, <span class="number">1</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * dest_string = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (dest == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (lua_type(L,<span class="number">1</span>) == LUA_TNUMBER) &#123;</span><br><span class="line">            <span class="keyword">return</span> luaL_error(L, <span class="string">&quot;Invalid service address 0&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        dest_string = get_dest_string(L, <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> type = luaL_checkinteger(L, idx_type+<span class="number">0</span>);</span><br><span class="line">    <span class="type">int</span> session = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//如果没有设置 session，则最后分配一个 ssession</span></span><br><span class="line">    <span class="keyword">if</span> (lua_isnil(L,idx_type+<span class="number">1</span>)) &#123;</span><br><span class="line">        type |= PTYPE_TAG_ALLOCSESSION;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        session = luaL_checkinteger(L,idx_type+<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> mtype = lua_type(L,idx_type+<span class="number">2</span>);</span><br><span class="line">    <span class="keyword">switch</span> (mtype) &#123;</span><br><span class="line">    <span class="keyword">case</span> LUA_TSTRING: &#123;</span><br><span class="line">        <span class="type">size_t</span> len = <span class="number">0</span>;</span><br><span class="line">        <span class="type">void</span> * msg = (<span class="type">void</span> *)lua_tolstring(L,idx_type+<span class="number">2</span>,&amp;len);</span><br><span class="line">        <span class="keyword">if</span> (len == <span class="number">0</span>) &#123;</span><br><span class="line">            msg = <span class="literal">NULL</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//调用 skynet_send 将对应的消息发送到指定服务的次级消息队列当中。</span></span><br><span class="line">        <span class="keyword">if</span> (dest_string) &#123;</span><br><span class="line">            session = skynet_sendname(context, source, dest_string, type, session , msg, len);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            session = skynet_send(context, source, dest, type, session , msg, len);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">case</span> LUA_TLIGHTUSERDATA: &#123;</span><br><span class="line">        <span class="type">void</span> * msg = lua_touserdata(L,idx_type+<span class="number">2</span>);</span><br><span class="line">        <span class="type">int</span> size = luaL_checkinteger(L,idx_type+<span class="number">3</span>);</span><br><span class="line">        <span class="keyword">if</span> (dest_string) &#123;</span><br><span class="line">            session = skynet_sendname(context, source, dest_string, type | PTYPE_TAG_DONTCOPY, session, msg, size);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            session = skynet_send(context, source, dest, type | PTYPE_TAG_DONTCOPY, session, msg, size);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        luaL_error(L, <span class="string">&quot;invalid param %s&quot;</span>, lua_typename(L, lua_type(L,idx_type+<span class="number">2</span>)));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (session &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (session == <span class="number">-2</span>) &#123;</span><br><span class="line">            <span class="comment">// package is too large</span></span><br><span class="line">            lua_pushboolean(L, <span class="number">0</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// send to invalid address</span></span><br><span class="line">        <span class="comment">// todo: maybe throw an error would be better</span></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">    lua_pushinteger(L,session);</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><p>结合上述代码及注释，当一个 lua 服务向另一个 lua 服务发送消息时，会调用<code>skynet.send</code> 函数，这个函数最终会调用 C 层的 <code>send_message</code>函数，通过对调用参数的解析，为消息添加上 type 和 session 字段，并最终调用 <code>skynet_send</code> 函数，这个函数在之前的<a href="/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E6%B6%88%E6%81%AF%E8%B0%83%E5%BA%A6%E6%9C%BA%E5%88%B6/">skynet 源码阅读笔记 —— 消息调度机制</a>说明了它的作用，这里就不多做说明。<code>skynet_send</code>函数将消息压入到指定服务的次级消息队列中，发送的过程就结束了。接下来只需要等待 worker 线程从全局消息队列中取出对应的次级消息队列，并消费相应的消息即可。</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第七篇文章，探讨了 skynet 框架下，同一 skynet 节点内不同的lua 服务之间是如何通过消息来进行交互，主要包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;lua 服务的消息协议&lt;/li&gt;
&lt;li&gt;lua 服务如何注册自己的消息及对应的回调函数&lt;/li&gt;
&lt;li&gt;lua 服务是如何接受消息的？&lt;/li&gt;
&lt;li&gt;lua 服务是如何发送消息的？&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet源码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— 如何在 lua 服务中启动另一个 lua 服务</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E5%A6%82%E4%BD%95%E5%9C%A8-lua-%E6%9C%8D%E5%8A%A1%E4%B8%AD%E5%90%AF%E5%8A%A8%E5%8F%A6%E4%B8%80%E4%B8%AA-lua-%E6%9C%8D%E5%8A%A1/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E5%A6%82%E4%BD%95%E5%9C%A8-lua-%E6%9C%8D%E5%8A%A1%E4%B8%AD%E5%90%AF%E5%8A%A8%E5%8F%A6%E4%B8%80%E4%B8%AA-lua-%E6%9C%8D%E5%8A%A1/</id>
    <published>2020-11-04T20:11:37.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第六篇文章，探讨了 skynet 的 lua 服务之间彼此之间是如何启动的，主要包含了以下内容：</p><blockquote><ul><li>如何 launch 一个 lua 服务</li><li>如何在 lua 服务中创建另一个 lua 服务</li></ul></blockquote><span id="more"></span><p>在上一篇文章中<a href="/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E5%BC%95%E5%AF%BC%E6%9C%8D%E5%8A%A1-bootstrap-%E7%9A%84%E5%90%AF%E5%8A%A8/">《skynet 源码阅读笔记 —— 引导服务 bootstrap 的启动》</a>，我们探讨了 bootstrap 服务的启动细节，其中 bootstrap 服务的核心在于 bootstrap.lua 脚本的执行。而这篇博客会借助 bootstrap.lua 脚本中的部分内容来说明如何在一个 lua 服务内启动其他的 lua 服务</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">--引用 skynet.lua 中的接口</span></span><br><span class="line"><span class="keyword">local</span> skynet = <span class="built_in">require</span> <span class="string">&quot;skynet&quot;</span></span><br><span class="line"><span class="keyword">local</span> harbor = <span class="built_in">require</span> <span class="string">&quot;skynet.harbor&quot;</span></span><br><span class="line"><span class="built_in">require</span> <span class="string">&quot;skynet.manager&quot;</span>    <span class="comment">-- import skynet.launch, ...</span></span><br><span class="line"></span><br><span class="line">skynet.start(<span class="function"><span class="keyword">function</span><span class="params">()</span></span></span><br><span class="line">    <span class="keyword">local</span> standalone = skynet.<span class="built_in">getenv</span> <span class="string">&quot;standalone&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">local</span> launcher = <span class="built_in">assert</span>(skynet.launch(<span class="string">&quot;snlua&quot;</span>,<span class="string">&quot;launcher&quot;</span>))</span><br><span class="line">    skynet.name(<span class="string">&quot;.launcher&quot;</span>, launcher)</span><br><span class="line">    <span class="keyword">local</span> harbor_id = <span class="built_in">tonumber</span>(skynet.<span class="built_in">getenv</span> <span class="string">&quot;harbor&quot;</span> <span class="keyword">or</span> <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">if</span> harbor_id == <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">assert</span>(standalone ==  <span class="literal">nil</span>)</span><br><span class="line">        standalone = <span class="literal">true</span></span><br><span class="line">        skynet.setenv(<span class="string">&quot;standalone&quot;</span>, <span class="string">&quot;true&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">local</span> ok, slave = <span class="built_in">pcall</span>(skynet.newservice, <span class="string">&quot;cdummy&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line">            skynet.abort()</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        skynet.name(<span class="string">&quot;.cslave&quot;</span>, slave)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">if</span> standalone <span class="keyword">then</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">pcall</span>(skynet.newservice,<span class="string">&quot;cmaster&quot;</span>) <span class="keyword">then</span></span><br><span class="line">                skynet.abort()</span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">local</span> ok, slave = <span class="built_in">pcall</span>(skynet.newservice, <span class="string">&quot;cslave&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line">            skynet.abort()</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        skynet.name(<span class="string">&quot;.cslave&quot;</span>, slave)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> standalone <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">local</span> datacenter = skynet.newservice <span class="string">&quot;datacenterd&quot;</span></span><br><span class="line">        skynet.name(<span class="string">&quot;DATACENTER&quot;</span>, datacenter)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    skynet.newservice <span class="string">&quot;service_mgr&quot;</span></span><br><span class="line">    <span class="built_in">pcall</span>(skynet.newservice,skynet.<span class="built_in">getenv</span> <span class="string">&quot;start&quot;</span> <span class="keyword">or</span> <span class="string">&quot;main&quot;</span>)</span><br><span class="line">    skynet.<span class="built_in">exit</span>()</span><br><span class="line"><span class="keyword">end</span>)</span><br></pre></td></tr></table></figure><p>在上述代码中，我们可以看到 bootstrap.lua 在文件的最开始处，执行了 <code>local skynet = require &quot;skynet&quot;</code> 以及 <code>require &quot;skynet.manager&quot;</code>, 这都是为了要在 bootstrap.lua 文件中，引用 skynet 为 lua 服务所设计的 api，对应文件及 api 如下：</p><blockquote><p>lualib/skynet.lua:  <code>skynet.start</code> 和 <code>skynet.newservice</code><br>lualib/skynet/manager.lua: <code>skynet.launch</code> 和 <code>skynet.name</code></p></blockquote><h4 id="skynet-launch-及-skynet-name-的作用"><a href="#skynet-launch-及-skynet-name-的作用" class="headerlink" title="skynet.launch 及 skynet.name 的作用"></a>skynet.launch 及 skynet.name 的作用</h4><p>对于 <code>skynet.start</code>函数我们放到后面讨论，这里先分析 <code>skynet.launch</code> 以及 <code>skynet.name</code> 两个函数，这两个函数定义如下：</p><figure class="highlight lua"><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">--manager.lua</span></span><br><span class="line"><span class="comment">--bootstrap 中是这样调用 skynet.launch 函数的：skynet.launch(&quot;snlua&quot;,&quot;launcher&quot;)</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.launch</span><span class="params">(...)</span></span></span><br><span class="line">    <span class="comment">--相当于执行 c.comand(&quot;LAUNCH&quot;, &quot;snlua laucher&quot;)，</span></span><br><span class="line">    <span class="keyword">local</span> addr = c.command(<span class="string">&quot;LAUNCH&quot;</span>, <span class="built_in">table</span>.<span class="built_in">concat</span>(&#123;...&#125;,<span class="string">&quot; &quot;</span>))</span><br><span class="line">    <span class="keyword">if</span> addr <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">tonumber</span>(<span class="string">&quot;0x&quot;</span> .. <span class="built_in">string</span>.<span class="built_in">sub</span>(addr , <span class="number">2</span>))</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>这里简单地说明一下 c 的意义，c 是定义在 skynet.lua 中的一个变量，其中保存了一张表。这张表可以由函数<code>luaopen_skynet_core</code> 创建。在这张表中定义了一个命令接口 command，对应的实现如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//lua-skynet.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">lcommand</span><span class="params">(lua_State *L)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">context</span> =</span> lua_touserdata(L, lua_upvalueindex(<span class="number">1</span>));</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * cmd = luaL_checkstring(L,<span class="number">1</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * result;</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * parm = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (lua_gettop(L) == <span class="number">2</span>) &#123;</span><br><span class="line">        parm = luaL_checkstring(L,<span class="number">2</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    result = skynet_command(context, cmd, parm);</span><br><span class="line">    <span class="keyword">if</span> (result) &#123;</span><br><span class="line">        lua_pushstring(L, result);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</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="comment">//skynet_service.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span> * <span class="title function_">cmd_launch</span><span class="params">(<span class="keyword">struct</span> skynet_context * context, <span class="type">const</span> <span class="type">char</span> * param)</span> &#123;</span><br><span class="line">    <span class="type">size_t</span> sz = <span class="built_in">strlen</span>(param);</span><br><span class="line">    <span class="type">char</span> tmp[sz+<span class="number">1</span>];</span><br><span class="line">    <span class="built_in">strcpy</span>(tmp,param);</span><br><span class="line">    <span class="type">char</span> * args = tmp;</span><br><span class="line">    <span class="type">char</span> * mod = strsep(&amp;args, <span class="string">&quot; \t\r\n&quot;</span>);</span><br><span class="line">    args = strsep(&amp;args, <span class="string">&quot;\r\n&quot;</span>);</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">inst</span> =</span> skynet_context_new(mod,args);</span><br><span class="line">    <span class="keyword">if</span> (inst == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        id_to_hex(context-&gt;result, inst-&gt;handle);</span><br><span class="line">        <span class="keyword">return</span> context-&gt;result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>lcommand</code> 函数的主要工作便是将对应的命令和参数转发回 C 层的 <code>cmd_launch</code> 函数中，这个函数最终会创建一个新的 snlua 类型的 C 服务 inst。而在创建这个 snlua 服务的过程中也会对其进行初始化，这个过程可见前一篇文章中所提到的 bootstrap 服务的创建及初始化，这里就不再赘述。</p><figure class="highlight lua"><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="function"><span class="keyword">function</span> <span class="title">skynet.name</span><span class="params">(name, handle)</span></span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> globalname(name, handle) <span class="keyword">then</span></span><br><span class="line">        c.command(<span class="string">&quot;NAME&quot;</span>, name .. <span class="string">&quot; &quot;</span> .. skynet.address(handle))</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p><code>skynet.name</code> 函数也会调用 <code>c.command</code> 接口来向对应的服务发送命令，只不过这次发送的是 NAME 命令，并且最终会调用 <code>cmd_name</code>函数来为服务进行命名。</p><h4 id="如何在-lua-服务中创建一个新的-lua-服务"><a href="#如何在-lua-服务中创建一个新的-lua-服务" class="headerlink" title="如何在 lua 服务中创建一个新的 lua 服务"></a>如何在 lua 服务中创建一个新的 lua 服务</h4><p>在说完上面两个 api 后，我们再来看看 <code>skynet.newservice</code> 的作用。skynet 在 lua 层一共有两种不同的创建服务的方式：一种是 <code>skynet.launch</code> 创建用 C 编写的服务，而另一种方式则是调用 <code>skynet.newservice</code> 创建 lua 服务。以上述的 bootstrap 服务和 service_mgr 服务为例，创建 lua 服务的流程大致如下：</p><blockquote><p>1.在 bootstrap 的 <code>start_func</code> 中执行 <code>skynet.newservice &quot;service_mgr&quot;</code>,此时 bootstrap 服务陷入阻塞状态;<br>2.在 service_mgr 服务被创建出来以后，执行 service_mgr.lua 这个脚本，在这个脚本中会执行 <code>skynet.start</code> 函数，表示 service_mgr 服务正式启动，能够正常地接收消息;<br>3.service_mgr 的 <code>skynet.start</code> 返回，bootstrap 服务的<code>skynet.newservice</code>函数返回，并获得了 service_mgr 服务的句柄</p></blockquote><p>了解了这个基本过程后，让我们来看看 <code>skynet.newservice</code> 是如何定义的：</p><figure class="highlight lua"><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="function"><span class="keyword">function</span> <span class="title">skynet.newservice</span><span class="params">(name, ...)</span></span></span><br><span class="line">    <span class="keyword">return</span> skynet.call(<span class="string">&quot;.launcher&quot;</span>, <span class="string">&quot;lua&quot;</span> , <span class="string">&quot;LAUNCH&quot;</span>, <span class="string">&quot;snlua&quot;</span>, name, ...)</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>在上述代码中，bootstrap服务的<code>skynet.newservice</code>向launcher服务发送了一条命令，并阻塞等待launcher的返回执行结果。这条命令会传递到 launcher.lua中，并最终调用command.LAUNCH，进而调用launch_service：</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">command.LAUNCH</span><span class="params">(_, service, ...)</span></span></span><br><span class="line">    launch_service(service, ...)</span><br><span class="line">    <span class="keyword">return</span> NORET</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">launch_service</span><span class="params">(service, ...)</span></span></span><br><span class="line">    <span class="keyword">local</span> param = <span class="built_in">table</span>.<span class="built_in">concat</span>(&#123;...&#125;, <span class="string">&quot; &quot;</span>)</span><br><span class="line">    <span class="comment">--创建一个 lua 服务并获得该服务的句柄</span></span><br><span class="line">    <span class="keyword">local</span> inst = skynet.launch(service, param)</span><br><span class="line">    <span class="keyword">local</span> session = skynet.context()</span><br><span class="line">    <span class="comment">--调用 skynet.response() 获得一个 response 闭包</span></span><br><span class="line">    <span class="keyword">local</span> response = skynet.response()</span><br><span class="line">    <span class="keyword">if</span> inst <span class="keyword">then</span></span><br><span class="line">        <span class="comment">--将服务句柄和服务的命令形式以键值对的形式保存</span></span><br><span class="line">        services[inst] = service .. <span class="string">&quot; &quot;</span> .. param</span><br><span class="line">        <span class="comment">--保存闭包，这个 response 闭包最终会等 skynet.start 返回后再调用</span></span><br><span class="line">        instance[inst] = response</span><br><span class="line">        launch_session[inst] = session</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        response(<span class="literal">false</span>)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">return</span> inst</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>在上述代码中，<code>launch_service</code> 在创建 service_mgr 服务后会调用相应的 service_mgr.lua 脚本。在对应的脚本中有一个 skynet.start 函数，其对应实现如下：</p><figure class="highlight lua"><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="comment">--skynet.lua</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.start</span><span class="params">(start_func)</span></span></span><br><span class="line">    <span class="comment">--将对应服务的回调函数设置为 skynet.dispatch_message</span></span><br><span class="line">    c.callback(skynet.dispatch_message)</span><br><span class="line">    <span class="comment">--执行服务脚本中传入的 start_func 函数</span></span><br><span class="line">    init_thread = skynet.timeout(<span class="number">0</span>, <span class="function"><span class="keyword">function</span><span class="params">()</span></span></span><br><span class="line">        skynet.init_service(start_func)</span><br><span class="line">        init_thread = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">end</span>)</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.init_service</span><span class="params">(start)</span></span></span><br><span class="line">    <span class="keyword">local</span> ok, err = skynet.<span class="built_in">pcall</span>(start)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line">        skynet.<span class="built_in">error</span>(<span class="string">&quot;init service failed: &quot;</span> .. <span class="built_in">tostring</span>(err))</span><br><span class="line">        skynet.send(<span class="string">&quot;.launcher&quot;</span>,<span class="string">&quot;lua&quot;</span>, <span class="string">&quot;ERROR&quot;</span>)</span><br><span class="line">        skynet.<span class="built_in">exit</span>()</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        skynet.send(<span class="string">&quot;.launcher&quot;</span>,<span class="string">&quot;lua&quot;</span>, <span class="string">&quot;LAUNCHOK&quot;</span>)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>在上一篇文章中，我们提到了 snlua 模块在调用 <code>launch_cb</code> 函数时会执行 <code>skynet_callback(context, NULL, NULL);</code> 将回调函数置为 NULL，而在 skynet.start 函数中才将对应服务的回调函数置为 <code>skynet.dispatch_message</code>,然后调用 <code>skynet.init_service(start_func)</code>对服务进行初始化。而 <code>skynet.init_service(start_func)</code> 则会调用 start_func 函数完成对服务真正意义上的初始化，并根据初始化的结果向 launcher 发送成功或失败的消息。以下分别讨论：</p><ul><li><p>当初始化结果成功时，服务会向 launcher 发送 LAUNCHOK 的命令，这会触发 <code>comand.LAUNCHOK</code> 的执行,其中 <code>command.LAUNCHOK</code> 的定义如下：</p><figure class="highlight lua"><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="comment">--launcher.lua</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">command.LAUNCHOK</span><span class="params">(address)</span></span></span><br><span class="line">    <span class="comment">-- init notice</span></span><br><span class="line">    <span class="keyword">local</span> response = instance[address]</span><br><span class="line">    <span class="keyword">if</span> response <span class="keyword">then</span></span><br><span class="line">        response(<span class="literal">true</span>, address)</span><br><span class="line">        instance[address] = <span class="literal">nil</span></span><br><span class="line">        launch_session[address] = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> NORET</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>从上述代码中可以看出，在执行初始化成功后，launcher会将之前调用 <code>launch_service</code> 时保存的闭包取出来执行，传入的第一个参数为 true 表示初始化成功。</p></li><li><p>当初始化结果失败时，服务会向 launcher 发送 ERROR 的命令。</p><figure class="highlight lua"><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="comment">--launcher.lua</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">command.ERROR</span><span class="params">(address)</span></span></span><br><span class="line">    <span class="comment">-- see serivce-src/service_lua.c</span></span><br><span class="line">    <span class="comment">-- init failed</span></span><br><span class="line">    <span class="keyword">local</span> response = instance[address]</span><br><span class="line">    <span class="keyword">if</span> response <span class="keyword">then</span></span><br><span class="line">        response(<span class="literal">false</span>)</span><br><span class="line">        launch_session[address] = <span class="literal">nil</span></span><br><span class="line">        instance[address] = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    services[address] = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">return</span> NORET</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>与前面 <code>command.LAUNCHOK</code>类似，<code>command.ERROR</code>会取出对应的 response 闭包并执行，传入参数为 false 表示初始化失败。随后当<code>skynet.send</code>返回后，调用 <code>skynet.exit</code> 函数移除初始化失败的服务。</p></li></ul><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">--skynet.lua</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">skynet.exit</span><span class="params">()</span></span></span><br><span class="line">    fork_queue = &#123;&#125; <span class="comment">-- no fork coroutine can be execute after skynet.exit</span></span><br><span class="line">    skynet.send(<span class="string">&quot;.launcher&quot;</span>,<span class="string">&quot;lua&quot;</span>,<span class="string">&quot;REMOVE&quot;</span>,skynet.<span class="built_in">self</span>(), <span class="literal">false</span>)</span><br><span class="line">    <span class="comment">-- report the sources that call me</span></span><br><span class="line">    <span class="keyword">for</span> co, session <span class="keyword">in</span> <span class="built_in">pairs</span>(session_coroutine_id) <span class="keyword">do</span></span><br><span class="line">        <span class="keyword">local</span> address = session_coroutine_address[co]</span><br><span class="line">        <span class="keyword">if</span> session~=<span class="number">0</span> <span class="keyword">and</span> address <span class="keyword">then</span></span><br><span class="line">            c.send(address, skynet.PTYPE_ERROR, session, <span class="string">&quot;&quot;</span>)</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">for</span> resp <span class="keyword">in</span> <span class="built_in">pairs</span>(unresponse) <span class="keyword">do</span></span><br><span class="line">        resp(<span class="literal">false</span>)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    <span class="comment">-- report the sources I call but haven&#x27;t return</span></span><br><span class="line">    <span class="keyword">local</span> tmp = &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> session, address <span class="keyword">in</span> <span class="built_in">pairs</span>(watching_session) <span class="keyword">do</span></span><br><span class="line">        tmp[address] = <span class="literal">true</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">for</span> address <span class="keyword">in</span> <span class="built_in">pairs</span>(tmp) <span class="keyword">do</span></span><br><span class="line">        c.send(address, skynet.PTYPE_ERROR, <span class="number">0</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    c.command(<span class="string">&quot;EXIT&quot;</span>)</span><br><span class="line">    <span class="comment">-- 退出服务后让出处理机权限</span></span><br><span class="line">    coroutine_yield <span class="string">&quot;QUIT&quot;</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment">--launcher.lua</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">command.REMOVE</span><span class="params">(_, handle, kill)</span></span></span><br><span class="line">    services[handle] = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">local</span> response = instance[handle]</span><br><span class="line">    <span class="keyword">if</span> response <span class="keyword">then</span></span><br><span class="line">        <span class="comment">-- instance is dead</span></span><br><span class="line">        response(<span class="keyword">not</span> kill)  <span class="comment">-- return nil to caller of newservice, when kill == false</span></span><br><span class="line">        instance[handle] = <span class="literal">nil</span></span><br><span class="line">        launch_session[handle] = <span class="literal">nil</span></span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">-- don&#x27;t return (skynet.ret) because the handle may exit</span></span><br><span class="line">    <span class="keyword">return</span> NORET</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure><p>在执行 <code>skynet.exit</code>的过程中，会向 launcher 发送 REMOVE 命令，而这个命令最终会调用 <code>command.REMOVE</code> 函数。<code>command.REMOVE</code>会取出相应闭包，并判断该闭包是否已经被执行过。这代表了两种情况：一种是因为初始化出错而导致触发了 <code>command.ERROR</code>,这个过程中执行了 response 闭包；另一种就是服务自己调用了 <code>skynet.exit()</code> 自行退出，此时 response 闭包还没有被执行过。</p><p>当 service_mgr 服务的<code>skynet.start</code> 函数返回后，bootstrap 服务也重新进入运行状态，继续启动其他的服务(比如 main 服务)，整体的过程与启动 service_mgr 是相同的。</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第六篇文章，探讨了 skynet 的 lua 服务之间彼此之间是如何启动的，主要包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;如何 launch 一个 lua 服务&lt;/li&gt;
&lt;li&gt;如何在 lua 服务中创建另一个 lua 服务&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet源码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="lua" scheme="https://phoenix500526.github.io/tags/lua/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— 引导服务 bootstrap 的启动</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E5%BC%95%E5%AF%BC%E6%9C%8D%E5%8A%A1-bootstrap-%E7%9A%84%E5%90%AF%E5%8A%A8/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E5%BC%95%E5%AF%BC%E6%9C%8D%E5%8A%A1-bootstrap-%E7%9A%84%E5%90%AF%E5%8A%A8/</id>
    <published>2020-11-04T17:33:14.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第五篇文章，探讨了 skynet 的引导服务 bootstrap 的实现，主要包含了以下内容：</p><blockquote><ul><li>bootstrap 如何启动，属于何种服务</li><li>snlua 服务的加载及初始化</li><li>bootstrap 的主要作用</li></ul></blockquote><span id="more"></span><h4 id="引导服务-bootstrap-的启动"><a href="#引导服务-bootstrap-的启动" class="headerlink" title="引导服务 bootstrap 的启动"></a>引导服务 bootstrap 的启动</h4><p>skynet 在启动的初期，在 <code>skynet_start</code> 函数中创建了两个服务 <code>logger</code> 和 <code>bootstrap</code>。其中 <code>bootstrap</code> 是一个 snlua 类型的服务，主要通过 <code>bootstrap</code> 函数来实现：</p><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><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"><span class="comment">//syknet_start.c</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_start</span><span class="params">(<span class="keyword">struct</span> skynet_config * config)</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    skynet_handle_namehandle(skynet_context_handle(ctx), <span class="string">&quot;logger&quot;</span>);</span><br><span class="line">    <span class="comment">//config-&gt;bootstrap = &quot;snlua bootstrap&quot;</span></span><br><span class="line">    bootstrap(ctx, config-&gt;bootstrap);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">bootstrap</span><span class="params">(<span class="keyword">struct</span> skynet_context * logger, <span class="type">const</span> <span class="type">char</span> * cmdline)</span> &#123;</span><br><span class="line">    <span class="type">int</span> sz = <span class="built_in">strlen</span>(cmdline);</span><br><span class="line">    <span class="type">char</span> name[sz+<span class="number">1</span>];</span><br><span class="line">    <span class="type">char</span> args[sz+<span class="number">1</span>];</span><br><span class="line">    <span class="built_in">sscanf</span>(cmdline, <span class="string">&quot;%s %s&quot;</span>, name, args);</span><br><span class="line">    <span class="comment">//name = snlua, args = &quot;bootstrap&quot;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> *<span class="title">ctx</span> =</span> skynet_context_new(name, args);</span><br><span class="line">    <span class="keyword">if</span> (ctx == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        skynet_error(<span class="literal">NULL</span>, <span class="string">&quot;Bootstrap error : %s\n&quot;</span>, cmdline);</span><br><span class="line">        skynet_context_dispatchall(logger);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，在上述代码中 <code>bootstrap</code> 主要的工作便是调用 <code>skynet_context_new</code> 创建了一个名为 <code>snlua</code> 的服务。我们来看下 <code>skynet_context_new</code> 的代码实现：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> skynet_context*  <span class="title function_">skynet_context_new</span><span class="params">(<span class="type">const</span> <span class="type">char</span> * name, <span class="type">const</span> <span class="type">char</span> *param)</span> &#123;</span><br><span class="line">    <span class="comment">//查询模块名称，查询到则直接返回模块指针，否则将其加载到全局的模块列表中</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_module</span> * <span class="title">mod</span> =</span> skynet_module_query(name);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mod == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">void</span> *inst = skynet_module_instance_create(mod);</span><br><span class="line">    <span class="keyword">if</span> (inst == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">ctx</span> =</span> skynet_malloc(<span class="keyword">sizeof</span>(*ctx));</span><br><span class="line"></span><br><span class="line">    ...     <span class="comment">//为避免粘帖过多代码，此处省略部分 ctx 的赋值操作</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> * <span class="title">queue</span> =</span> ctx-&gt;<span class="built_in">queue</span> = skynet_mq_create(ctx-&gt;handle);</span><br><span class="line">    <span class="comment">// init function maybe use ctx-&gt;handle, so it must init at last</span></span><br><span class="line">    context_inc();</span><br><span class="line"></span><br><span class="line">    CHECKCALLING_BEGIN(ctx)</span><br><span class="line">    <span class="type">int</span> r = skynet_module_instance_init(mod, inst, ctx, param);</span><br><span class="line">    CHECKCALLING_END(ctx)</span><br><span class="line">    <span class="keyword">if</span> (r == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//ctx 的引用计数减 1，skynet_context_release 会在 ctx-&gt;ref == 0 时回收这个 context</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">ret</span> =</span> skynet_context_release(ctx);</span><br><span class="line">        <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">            ctx-&gt;init = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//将次级消息队列放入全局消息队列中</span></span><br><span class="line">        skynet_globalmq_push(<span class="built_in">queue</span>);</span><br><span class="line">        <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">            skynet_error(ret, <span class="string">&quot;LAUNCH %s %s&quot;</span>, name, param ? param : <span class="string">&quot;&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        ... <span class="comment">//错误处理，包括释放已分配的ctx、次级消息队列等</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="snlua-的加载及初始化"><a href="#snlua-的加载及初始化" class="headerlink" title="snlua 的加载及初始化"></a>snlua 的加载及初始化</h4><p>从前面 <code>skynet_context_new</code> 函数中，我们可以看出 <code>snlua</code> 服务的启动需要利用到 <code>skynet_module_instance_create</code>函数进行实例的创建，利用<code>skynet_module_instance_init</code>函数进行初始化，这两个函数最终会调用到对应模块中的 *_create 和 *_init 函数。对于 snlua 模块而言，其对应代码保存在 service-src/service_snlua.c 文件中，最终会编译成为 snlua.so 文件。由于在前面的文章 <a href="/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-skynet-%E7%9A%84%E6%A8%A1%E5%9D%97%E4%B8%8E%E6%9C%8D%E5%8A%A1/">skynet 源码阅读笔记 —— skynet 的模块与服务</a> 中已经说明了模块加载的详细方式，因此这里不多着笔墨说明。我们先来看看 snlua 的基本数据结构，然后直接看相关的模块函数</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//service-src/service_snlua.c</span></span><br><span class="line"><span class="comment">//内存阈值，当 snlua 占用的内存超过阈值则触发警报</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MEMORY_WARNING_REPORT (1024 * 1024 * 32)</span></span><br><span class="line"><span class="comment">//snlua 的基本数据结构</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">snlua</span> &#123;</span></span><br><span class="line">    lua_State * L;                  <span class="comment">//每个 snlua 模块都配备了专属的 lua 环境</span></span><br><span class="line">                                    <span class="comment">//不同的 snlua 服务将不同的 lua 脚本运行在自己的 lua 环境中，彼此之间互不影响</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">ctx</span>;</span>    <span class="comment">//模块所属的服务</span></span><br><span class="line">    <span class="type">size_t</span> mem;</span><br><span class="line">    <span class="type">size_t</span> mem_report;              <span class="comment">//内存阈值</span></span><br><span class="line">    <span class="type">size_t</span> mem_limit;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> snlua* <span class="title function_">snlua_create</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">snlua</span> * <span class="title">l</span> =</span> skynet_malloc(<span class="keyword">sizeof</span>(*l));</span><br><span class="line">    <span class="built_in">memset</span>(l,<span class="number">0</span>,<span class="keyword">sizeof</span>(*l));</span><br><span class="line">    l-&gt;mem_report = MEMORY_WARNING_REPORT;</span><br><span class="line">    l-&gt;mem_limit = <span class="number">0</span>;</span><br><span class="line">    l-&gt;L = lua_newstate(lalloc, l);</span><br><span class="line">    <span class="keyword">return</span> l;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">snlua_init</span><span class="params">(<span class="keyword">struct</span> snlua *l, <span class="keyword">struct</span> skynet_context *ctx, <span class="type">const</span> <span class="type">char</span> * args)</span> &#123;</span><br><span class="line">    <span class="type">int</span> sz = <span class="built_in">strlen</span>(args);</span><br><span class="line">    <span class="type">char</span> * tmp = skynet_malloc(sz);</span><br><span class="line">    <span class="built_in">memcpy</span>(tmp, args, sz);</span><br><span class="line">    <span class="comment">//将 launch_cb 设置为 snlua 服务的回调函数，参数为 l</span></span><br><span class="line">    skynet_callback(ctx, l , launch_cb);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * self = skynet_command(ctx, <span class="string">&quot;REG&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="comment">//self 的值为 :handle</span></span><br><span class="line">    <span class="type">uint32_t</span> handle_id = strtoul(self+<span class="number">1</span>, <span class="literal">NULL</span>, <span class="number">16</span>);</span><br><span class="line">    <span class="comment">// it must be first message</span></span><br><span class="line">    <span class="comment">//向自己发送第一条消息，这条消息将由 launch_cb 进行处理，消息内容为 &quot;bootstrap&quot;</span></span><br><span class="line">    skynet_send(ctx, <span class="number">0</span>, handle_id, PTYPE_TAG_DONTCOPY,<span class="number">0</span>, tmp, sz);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中可知，<code>snlua_create</code> 会负责初始化 <code>snlua</code> 结构体，并将其返回，而 <code>snlua_init</code> 函数则负责将创建好的 snlua 服务的回调函数设置为 <code>launch_cb</code> 函数，并对其发送一个注册命令，完成后向 snlua 服务的次级消息队列发送一条消息。<br>skynet 为每个模块都提供了一组相应的命令，其对应的数据类型为 <code>command_func</code>，skynet 为模块所提供的所有命令都存放在了 <code>cmd_funcs</code> 数组当中</p><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><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">//skynet_service.c</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">command_func</span> &#123;</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *name;   <span class="comment">//命令名称</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * (*func)(<span class="keyword">struct</span> skynet_context * context, <span class="type">const</span> <span class="type">char</span> * param);  <span class="comment">//命令对应的回调函数</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="type">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">command_func</span> <span class="title">cmd_funcs</span>[] =</span> &#123;</span><br><span class="line">    &#123; <span class="string">&quot;TIMEOUT&quot;</span>, cmd_timeout &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;REG&quot;</span>, cmd_reg &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;QUERY&quot;</span>, cmd_query &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;NAME&quot;</span>, cmd_name &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;EXIT&quot;</span>, cmd_exit &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;KILL&quot;</span>, cmd_kill &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;LAUNCH&quot;</span>, cmd_launch &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;GETENV&quot;</span>, cmd_getenv &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;SETENV&quot;</span>, cmd_setenv &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;STARTTIME&quot;</span>, cmd_starttime &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;ABORT&quot;</span>, cmd_abort &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;MONITOR&quot;</span>, cmd_monitor &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;STAT&quot;</span>, cmd_stat &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;LOGON&quot;</span>, cmd_logon &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;LOGOFF&quot;</span>, cmd_logoff &#125;,</span><br><span class="line">    &#123; <span class="string">&quot;SIGNAL&quot;</span>, cmd_signal &#125;,</span><br><span class="line">    &#123; <span class="literal">NULL</span>, <span class="literal">NULL</span> &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>在了解了 <code>command_func</code> 的定义后，我们来看看 <code>skynet_command</code> 函数的实现</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//查找相应的命令，并返回命令函数的执行结果</span></span><br><span class="line"><span class="comment">//snlua 对 skynet_command 的调用形式为 skynet_command(ctx, &quot;REG&quot;, NULL)</span></span><br><span class="line"><span class="type">const</span> <span class="type">char</span>* <span class="title function_">skynet_command</span><span class="params">(<span class="keyword">struct</span> skynet_context * context, <span class="type">const</span> <span class="type">char</span> * cmd , <span class="type">const</span> <span class="type">char</span> * param)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">command_func</span> * <span class="title">method</span> =</span> &amp;cmd_funcs[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">while</span>(method-&gt;name) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">strcmp</span>(cmd, method-&gt;name) == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> method-&gt;func(context, param);</span><br><span class="line">        &#125;</span><br><span class="line">        ++method;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//cmd_reg(ctx, NULL)</span></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="type">char</span>* <span class="title function_">cmd_reg</span><span class="params">(<span class="keyword">struct</span> skynet_context * context, <span class="type">const</span> <span class="type">char</span> * param)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (param == <span class="literal">NULL</span> || param[<span class="number">0</span>] == <span class="string">&#x27;\0&#x27;</span>) &#123;</span><br><span class="line">        <span class="comment">//将回调函数的执行结果和 handle 拼接在一起，并返回</span></span><br><span class="line">        <span class="built_in">sprintf</span>(context-&gt;result, <span class="string">&quot;:%x&quot;</span>, context-&gt;handle);</span><br><span class="line">        <span class="comment">//context-&gt;result 是用来存放 context-&gt;cb 的执行结果的</span></span><br><span class="line">        <span class="keyword">return</span> context-&gt;result;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (param[<span class="number">0</span>] == <span class="string">&#x27;.&#x27;</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> skynet_handle_namehandle(context-&gt;handle, param + <span class="number">1</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        skynet_error(context, <span class="string">&quot;Can&#x27;t register global name %s in C&quot;</span>, param);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中，我们可以简单地总结一下 snlua 的启动流程：</p><blockquote><ol><li>skynet 调用 bootstrap 函数创建了一个 snlua 服务</li><li>在 bootstrap 创建服务的过程中，会先从全局的 modules 中查找 snlua 模块是否已加载，如果没有则加载到内存当中。</li><li>加载完毕后，先调用 snlua_create 函数分配一个 snlua 结构体，该结构体中包含了一个独立的 lua 运行状态，用于执行相应的 lua 脚本</li><li>创建好对应的 snlua 模块实例后，执行 snlua_init 函数为其进行初始化。初始化的过程中负责设置服务的回调函数，并向 snlua 服务发送一个注册命令，随后向 snlua 服务发送一条消息</li><li>将 snlua 的消息队列压入全局的消息队列当中<br>完成上述的 5 个步骤后，一个 snlua 服务就算是启动起来了。</li></ol></blockquote><h4 id="bootstrap-服务的主要工作"><a href="#bootstrap-服务的主要工作" class="headerlink" title="bootstrap 服务的主要工作"></a>bootstrap 服务的主要工作</h4><p>在前面的内容当中，我们看到了 snlua 模块在初始化的过程当中会向自己发送一条消息，这样做的目的是为了自身的服务启动起来。因为在 skynet 当中，服务要依靠消息来驱动。snlua 在初始化过程当中向自身发送了一条消息，当 snlua 服务创建完毕后，worker 线程便会消息队列当中取出消息并执行相应的回调函数 <code>launch_cb</code> 函数进行消费，这样就能够将 snlua 服务运转起来。我们来看一下 <code>launch_cb</code> 的实现：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//msg 的值为 bootstrap</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">launch_cb</span><span class="params">(<span class="keyword">struct</span> skynet_context * context, <span class="type">void</span> *ud, <span class="type">int</span> type, <span class="type">int</span> session, <span class="type">uint32_t</span> source , <span class="type">const</span> <span class="type">void</span> * msg, <span class="type">size_t</span> sz)</span> &#123;</span><br><span class="line">    assert(type == <span class="number">0</span> &amp;&amp; session == <span class="number">0</span>);</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">snlua</span> *<span class="title">l</span> =</span> ud;</span><br><span class="line">    <span class="comment">//重设回调函数</span></span><br><span class="line">    skynet_callback(context, <span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="type">int</span> err = init_cb(l, context, msg, sz);</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        skynet_command(context, <span class="string">&quot;EXIT&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></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><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">init_cb</span><span class="params">(<span class="keyword">struct</span> snlua *l, <span class="keyword">struct</span> skynet_context *ctx, <span class="type">const</span> <span class="type">char</span> * args, <span class="type">size_t</span> sz)</span> &#123;</span><br><span class="line">    lua_State *L = l-&gt;L;</span><br><span class="line">    l-&gt;ctx = ctx;</span><br><span class="line">    <span class="comment">//暂停 lua 的 GC 机制</span></span><br><span class="line">    lua_gc(L, LUA_GCSTOP, <span class="number">0</span>);</span><br><span class="line">    lua_pushboolean(L, <span class="number">1</span>);  <span class="comment">/* signal for libraries to ignore env. vars. */</span></span><br><span class="line">    lua_setfield(L, LUA_REGISTRYINDEX, <span class="string">&quot;LUA_NOENV&quot;</span>);</span><br><span class="line">    luaL_openlibs(L);</span><br><span class="line">    lua_pushlightuserdata(L, ctx);</span><br><span class="line">    lua_setfield(L, LUA_REGISTRYINDEX, <span class="string">&quot;skynet_context&quot;</span>);</span><br><span class="line">    <span class="comment">//判断 skynet.codecache 是否为与 package.loaded 当中。如果不在则调用 codecache 进行加载</span></span><br><span class="line">    luaL_requiref(L, <span class="string">&quot;skynet.codecache&quot;</span>, codecache , <span class="number">0</span>);</span><br><span class="line">    lua_pop(L,<span class="number">1</span>);</span><br><span class="line">    <span class="comment">//设置相关的全局变量</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *path = optstring(ctx, <span class="string">&quot;lua_path&quot;</span>,<span class="string">&quot;./lualib/?.lua;./lualib/?/init.lua&quot;</span>);</span><br><span class="line">    lua_pushstring(L, path);</span><br><span class="line">    lua_setglobal(L, <span class="string">&quot;LUA_PATH&quot;</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *cpath = optstring(ctx, <span class="string">&quot;lua_cpath&quot;</span>,<span class="string">&quot;./luaclib/?.so&quot;</span>);</span><br><span class="line">    lua_pushstring(L, cpath);</span><br><span class="line">    lua_setglobal(L, <span class="string">&quot;LUA_CPATH&quot;</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *service = optstring(ctx, <span class="string">&quot;luaservice&quot;</span>, <span class="string">&quot;./service/?.lua&quot;</span>);</span><br><span class="line">    lua_pushstring(L, service);</span><br><span class="line">    lua_setglobal(L, <span class="string">&quot;LUA_SERVICE&quot;</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *preload = skynet_command(ctx, <span class="string">&quot;GETENV&quot;</span>, <span class="string">&quot;preload&quot;</span>);</span><br><span class="line">    lua_pushstring(L, preload);</span><br><span class="line">    lua_setglobal(L, <span class="string">&quot;LUA_PRELOAD&quot;</span>);</span><br><span class="line">    <span class="comment">//traceback 将 L 栈的回溯信息压入栈</span></span><br><span class="line">    lua_pushcfunction(L, traceback);</span><br><span class="line">    assert(lua_gettop(L) == <span class="number">1</span>);</span><br><span class="line">    <span class="comment">//lua 服务的加载器为 loader.lua</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * loader = optstring(ctx, <span class="string">&quot;lualoader&quot;</span>, <span class="string">&quot;./lualib/loader.lua&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> r = luaL_loadfile(L,loader);</span><br><span class="line">    <span class="keyword">if</span> (r != LUA_OK) &#123;</span><br><span class="line">        skynet_error(ctx, <span class="string">&quot;Can&#x27;t load %s : %s&quot;</span>, loader, lua_tostring(L, <span class="number">-1</span>));</span><br><span class="line">        report_launcher_error(ctx);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//args = bootstrap</span></span><br><span class="line">    lua_pushlstring(L, args, sz);</span><br><span class="line">    <span class="comment">//利用 loader 将 bootstrap.lua 脚本执行起来。</span></span><br><span class="line">    r = lua_pcall(L,<span class="number">1</span>,<span class="number">0</span>,<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (r != LUA_OK) &#123;</span><br><span class="line">        skynet_error(ctx, <span class="string">&quot;lua loader error : %s&quot;</span>, lua_tostring(L, <span class="number">-1</span>));</span><br><span class="line">        report_launcher_error(ctx);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    lua_settop(L,<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (lua_getfield(L, LUA_REGISTRYINDEX, <span class="string">&quot;memlimit&quot;</span>) == LUA_TNUMBER) &#123;</span><br><span class="line">        <span class="type">size_t</span> limit = lua_tointeger(L, <span class="number">-1</span>);</span><br><span class="line">        l-&gt;mem_limit = limit;</span><br><span class="line">        skynet_error(ctx, <span class="string">&quot;Set memory limit to %.2f M&quot;</span>, (<span class="type">float</span>)limit / (<span class="number">1024</span> * <span class="number">1024</span>));</span><br><span class="line">        lua_pushnil(L);</span><br><span class="line">        lua_setfield(L, LUA_REGISTRYINDEX, <span class="string">&quot;memlimit&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    lua_pop(L, <span class="number">1</span>);</span><br><span class="line">    <span class="comment">//重启 lua 的 GC 机制</span></span><br><span class="line">    lua_gc(L, LUA_GCRESTART, <span class="number">0</span>);</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="type">static</span> <span class="type">int</span> <span class="title function_">codecache</span><span class="params">(lua_State *L)</span> &#123;</span><br><span class="line">    luaL_Reg l[] = &#123;</span><br><span class="line">        &#123; <span class="string">&quot;clear&quot;</span>, cleardummy &#125;,</span><br><span class="line">        &#123; <span class="string">&quot;mode&quot;</span>, cleardummy &#125;,</span><br><span class="line">        &#123; <span class="literal">NULL</span>, <span class="literal">NULL</span> &#125;,</span><br><span class="line">    &#125;;</span><br><span class="line">    luaL_newlib(L,l);</span><br><span class="line">    lua_getglobal(L, <span class="string">&quot;loadfile&quot;</span>);</span><br><span class="line">    lua_setfield(L, <span class="number">-2</span>, <span class="string">&quot;loadfile&quot;</span>);</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><p>从上述代码中可以看出，bootstrap服务(即前面的 snlua 服务)在触发时，会调用 <code>init_cb</code> 来代替 <code>lauch_cb</code> 函数。简单地来说，<code>init_cb</code> 中最主要的部分便是设置相应的环境变量以及加载器loader。其中，环境变量的意义如下：</p><blockquote><p>LUA_PATH：Lua搜索路径，在config.lua_path指定。<br>LUA_CPATH：C模块的搜索路径，在config.lua_cpath指定。<br>LUA_SERVICE：Lua服务的搜索路径，在config.luaservice指定。<br>LUA_PRELOAD：预加载脚本，这些脚本会在所有服务开始之前执行，可以用它来初始化一些全局的设置。</p></blockquote><p>在设置好相应的环境变量后，<code>init_cb</code> 会执行 loader.lua，并将 bootstrap.lua 传进去。loader.lua 的主要作用是对环境变量以及传入的参数进行一些文本处理，然后找到对应的文件去执行，这里的参数主要是指 <code>bootstrap</code>，最终会执行 /service/bootstrap.lua 文件。其中 bootstrap.lua 的源码如下：</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">--将 skynet.lua 中定义的函数引用到当前文件</span></span><br><span class="line"><span class="keyword">local</span> skynet = <span class="built_in">require</span> <span class="string">&quot;skynet&quot;</span></span><br><span class="line"><span class="keyword">local</span> harbor = <span class="built_in">require</span> <span class="string">&quot;skynet.harbor&quot;</span></span><br><span class="line"><span class="built_in">require</span> <span class="string">&quot;skynet.manager&quot;</span>    <span class="comment">-- import skynet.launch, ...</span></span><br><span class="line">skynet.start(<span class="function"><span class="keyword">function</span><span class="params">()</span></span></span><br><span class="line">    <span class="keyword">local</span> standalone = skynet.<span class="built_in">getenv</span> <span class="string">&quot;standalone&quot;</span></span><br><span class="line">    <span class="comment">--利用 skynet.launch 启动一个 launcher</span></span><br><span class="line">    <span class="keyword">local</span> launcher = <span class="built_in">assert</span>(skynet.launch(<span class="string">&quot;snlua&quot;</span>,<span class="string">&quot;launcher&quot;</span>))</span><br><span class="line">    skynet.name(<span class="string">&quot;.launcher&quot;</span>, launcher)</span><br><span class="line">    <span class="comment">--确认当前的 skynet 节点是主节点还是从节点</span></span><br><span class="line">    <span class="keyword">local</span> harbor_id = <span class="built_in">tonumber</span>(skynet.<span class="built_in">getenv</span> <span class="string">&quot;harbor&quot;</span> <span class="keyword">or</span> <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">if</span> harbor_id == <span class="number">0</span> <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">assert</span>(standalone ==  <span class="literal">nil</span>)</span><br><span class="line">        standalone = <span class="literal">true</span></span><br><span class="line">        skynet.setenv(<span class="string">&quot;standalone&quot;</span>, <span class="string">&quot;true&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">local</span> ok, slave = <span class="built_in">pcall</span>(skynet.newservice, <span class="string">&quot;cdummy&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line">            skynet.abort()</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        skynet.name(<span class="string">&quot;.cslave&quot;</span>, slave)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">if</span> standalone <span class="keyword">then</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">pcall</span>(skynet.newservice,<span class="string">&quot;cmaster&quot;</span>) <span class="keyword">then</span></span><br><span class="line">                skynet.abort()</span><br><span class="line">            <span class="keyword">end</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">local</span> ok, slave = <span class="built_in">pcall</span>(skynet.newservice, <span class="string">&quot;cslave&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> ok <span class="keyword">then</span></span><br><span class="line">            skynet.abort()</span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">        skynet.name(<span class="string">&quot;.cslave&quot;</span>, slave)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> standalone <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">local</span> datacenter = skynet.newservice <span class="string">&quot;datacenterd&quot;</span></span><br><span class="line">        skynet.name(<span class="string">&quot;DATACENTER&quot;</span>, datacenter)</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    skynet.newservice <span class="string">&quot;service_mgr&quot;</span></span><br><span class="line">    <span class="built_in">pcall</span>(skynet.newservice,skynet.<span class="built_in">getenv</span> <span class="string">&quot;start&quot;</span> <span class="keyword">or</span> <span class="string">&quot;main&quot;</span>)</span><br><span class="line">    skynet.<span class="built_in">exit</span>()</span><br><span class="line"><span class="keyword">end</span>)</span><br></pre></td></tr></table></figure><p>从上述 lua 代码中，我们可以看出 bootstrap.lua 的主要工作如下：</p><blockquote><ol><li>启动<code>launcher</code>服务，这个服务是一个通用的服务启动器，如果我们需要在lua创建一个 C 服务就需要用到它</li><li>启动<code>datacenterd</code>服务</li><li>启动<code>service_mgr</code>服务</li><li>根据 config 中的 start 字段，指定相应的 lua 脚本，在 bootstrap 服务中启动的是 main.lua 脚本</li></ol></blockquote><p>到目前为止，bootstrap 服务的基本内容大概就说完了，而相关的一些其他一部分未说明清楚的部分(如main.lua, skynet.newservice, skynet_launch 等)则留在其他文章中讨论</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第五篇文章，探讨了 skynet 的引导服务 bootstrap 的实现，主要包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;bootstrap 如何启动，属于何种服务&lt;/li&gt;
&lt;li&gt;snlua 服务的加载及初始化&lt;/li&gt;
&lt;li&gt;bootstrap 的主要作用&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet源码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="lua" scheme="https://phoenix500526.github.io/tags/lua/"/>
    
    <category term="bootstrap服务" scheme="https://phoenix500526.github.io/tags/bootstrap%E6%9C%8D%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— skynet中的定时器机制</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-skynet%E4%B8%AD%E7%9A%84%E5%AE%9A%E6%97%B6%E5%99%A8%E6%9C%BA%E5%88%B6/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-skynet%E4%B8%AD%E7%9A%84%E5%AE%9A%E6%97%B6%E5%99%A8%E6%9C%BA%E5%88%B6/</id>
    <published>2020-11-04T17:22:37.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第四篇文章，探讨了 skynet 的定时器机制，主要包含了以下内容：</p><blockquote><ul><li>定时器的基本数据结构</li><li>starttime、current 以及 current_point 的意义，包括了 CLOCK_REALTIME 以及 CLOCK_MONOTONIC 等内容</li><li>time、near 数组以及 t 数组的意义</li><li>time、near 数组以及 t 数组三者之间的联系</li></ul></blockquote><span id="more"></span><h4 id="基本数据结构"><a href="#基本数据结构" class="headerlink" title="基本数据结构"></a>基本数据结构</h4><p>要了解 skynet 的定时器机制，需要先了解 skynet 中的 <code>timer</code> 的数据结构及初始化代码(skynet 中所有 timer 相关的代码都存放于 skynet_timer.c 文件中)：</p><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><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"><span class="meta">#<span class="keyword">define</span> TIME_NEAR_SHIFT 8</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TIME_NEAR (1 &lt;&lt; TIME_NEAR_SHIFT)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TIME_LEVEL_SHIFT 6</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TIME_LEVEL (1 &lt;&lt; TIME_LEVEL_SHIFT)</span></span><br><span class="line"><span class="comment">// TIME_NEAR_MASK = 0x11111111</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TIME_NEAR_MASK (TIME_NEAR-1)</span></span><br><span class="line"><span class="comment">// TIME_LEVEL_MASK = 0x111111</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TIME_LEVEL_MASK (TIME_LEVEL-1)</span></span><br><span class="line"><span class="comment">//超时事件</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timer_event</span> &#123;</span></span><br><span class="line">    <span class="type">uint32_t</span> handle;    <span class="comment">//标记该超时时间所对应的服务</span></span><br><span class="line">    <span class="type">int</span> session;        <span class="comment">//超时事件发送消息所属的 handle</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="class"><span class="keyword">struct</span> <span class="title">timer_node</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timer_node</span> *<span class="title">next</span>;</span></span><br><span class="line">    <span class="type">uint32_t</span> expire;    <span class="comment">//超时事件</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="class"><span class="keyword">struct</span> <span class="title">link_list</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timer_node</span> <span class="title">head</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timer_node</span> *<span class="title">tail</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timer</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">link_list</span> <span class="title">near</span>[<span class="title">TIME_NEAR</span>];</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">link_list</span> <span class="title">t</span>[4][<span class="title">TIME_LEVEL</span>];</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">    <span class="type">uint32_t</span> time;</span><br><span class="line">    <span class="type">uint32_t</span> starttime;     </span><br><span class="line">    <span class="type">uint64_t</span> current;       </span><br><span class="line">    <span class="type">uint64_t</span> current_point; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">timer</span> * <span class="title">TI</span> =</span> <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure><p>从上述数据结构的定义中可以知道，skynet 采用 <code>timer_event</code> 来表示超时事件，其中 <code>handle</code> 代表了该超时事件属于哪个服务，而 <code>session</code> 则代表向对应服务所发送的超时消息的 session。skynet 采用了带头节点的单链表来存储多个定时器。</p><h4 id="starttime、current-以及-current-point-的意义"><a href="#starttime、current-以及-current-point-的意义" class="headerlink" title="starttime、current 以及 current_point 的意义"></a>starttime、current 以及 current_point 的意义</h4><p>要想了解 上述三个字段的具体意义，我们需要先了解 timer 是如何被初始化，以及节点是如何添加到 timer 当中的。在说明 <code>skynet_timer_init</code> 之前，需要花点时间说明 <code>clock_gettime</code> 中不同的时间类别，也就是所谓的 clock_id. <code>clock_gettime</code> 支持多种不同的 <code>clk_id</code>, 其中包括但不限于:<code>CLOCK_REALTIME</code>、<code>CLOCK_MONOTONIC</code>、<code>CLOCK_PROCESS_CPUTIMEID</code> 和 <code>CLOCK_THREAD_CPUTIME_ID</code></p><blockquote><ul><li>CLOCK_REALTIME：墙上时间(wall time)，也就是我们现实生活中所用的时间，由变量xtime来记录。系统每次启动时将CMOS上的RTC时间读入xtime，这个值是”自1970-01-01起经历的秒数、本秒中经历的纳秒数”，每来一个timer interrupt，也需要去更新xtime。其值为从 1970-01-01:00:00:00 至今所流逝的时间。</li><li>CLOCK_MONOTONIC：单调时间(monotonic time)，代表的从系统启动至今所流逝的时间，由变量jiffies来记录。系统每次启动时jiffies初始化为0，每来一个timer interrupt，jiffies加1，也就是说它代表系统启动后流逝的tick数。jiffies一定是单调递增的</li><li>CLOCK_PROCESS_CPUTIMEID：进程专属的 CPU 时钟，代表从进程启动后至今所流逝的时间</li><li>CLOCK_THREAD_CPUTIME_ID：线程专属单 CPU 时钟，代表从线程启动后至今所流逝的时间</li></ul></blockquote><p>其中，<code>CLOCK_REALTIME</code> 和 <code>CLOCK_MONOTONIC</code> 的区别在于 <code>CLOCK_REALTIME</code> 的值可以受到系统时间跳变或 NTP 的影响， 而<code>CLOCK_MONOTONIC</code> 不会受到影响，因此常用 <code>CLOCK_MONOTONIC</code> 来计算系统启动后两个先后发生的事件之间的时间差</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建一个 timer 结构，并将其中 near 以及 t 链表数组清空</span></span><br><span class="line"><span class="type">static</span> <span class="keyword">struct</span> timer* <span class="title function_">timer_create_timer</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timer</span> *<span class="title">r</span>=</span>(<span class="keyword">struct</span> timer *)skynet_malloc(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> timer));</span><br><span class="line">    <span class="built_in">memset</span>(r,<span class="number">0</span>,<span class="keyword">sizeof</span>(*r));</span><br><span class="line">    <span class="type">int</span> i,j;</span><br><span class="line">    <span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;TIME_NEAR;i++) &#123;</span><br><span class="line">        link_clear(&amp;r-&gt;near[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;<span class="number">4</span>;i++) &#123;</span><br><span class="line">        <span class="keyword">for</span> (j=<span class="number">0</span>;j&lt;TIME_LEVEL;j++) &#123;</span><br><span class="line">            link_clear(&amp;r-&gt;t[i][j]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    SPIN_INIT(r)</span><br><span class="line">    r-&gt;current = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> r;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span>  <span class="title function_">skynet_timer_init</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    TI = timer_create_timer();</span><br><span class="line">    <span class="type">uint32_t</span> current = <span class="number">0</span>;</span><br><span class="line">    systime(&amp;TI-&gt;starttime, &amp;current);</span><br><span class="line">    <span class="comment">//TI-&gt;starttime 保存了当前墙上时间，精确到秒</span></span><br><span class="line">    <span class="comment">//TI-&gt;current 从 starttime 启动后到当前的时间，精确到 10 ms</span></span><br><span class="line">    TI-&gt;current = current;</span><br><span class="line">    <span class="comment">//TI-&gt;current_point 代表是精确到 10 ms的单调时间，表示从系统启动到当前所流失的时间</span></span><br><span class="line">    TI-&gt;current_point = gettime();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获得当前系统的墙上时间，并将其中的整秒部分存入 sec 中，将纳秒部分转化为以 10 ms 为精度的 centisecond,并存入 cs 中</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">systime</span><span class="params">(<span class="type">uint32_t</span> *sec, <span class="type">uint32_t</span> *cs)</span> &#123;</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">ti</span>;</span></span><br><span class="line">    clock_gettime(CLOCK_REALTIME, &amp;ti);</span><br><span class="line">    *sec = (<span class="type">uint32_t</span>)ti.tv_sec;</span><br><span class="line">    *cs = (<span class="type">uint32_t</span>)(ti.tv_nsec / <span class="number">10000000</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">tv</span>;</span></span><br><span class="line">    gettimeofday(&amp;tv, <span class="literal">NULL</span>);</span><br><span class="line">    *sec = tv.tv_sec;</span><br><span class="line">    *cs = tv.tv_usec / <span class="number">10000</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//获得当前系统的单调时间，并将其转换成为以 10ms 为精度的时间格式</span></span><br><span class="line"><span class="type">static</span> <span class="type">uint64_t</span> <span class="title function_">gettime</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">uint64_t</span> t;</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timespec</span> <span class="title">ti</span>;</span></span><br><span class="line">    clock_gettime(CLOCK_MONOTONIC, &amp;ti);</span><br><span class="line">    t = (<span class="type">uint64_t</span>)ti.tv_sec * <span class="number">100</span>;</span><br><span class="line">    t += ti.tv_nsec / <span class="number">10000000</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">tv</span>;</span></span><br><span class="line">    gettimeofday(&amp;tv, <span class="literal">NULL</span>);</span><br><span class="line">    t = (<span class="type">uint64_t</span>)tv.tv_sec * <span class="number">100</span>;</span><br><span class="line">    t += tv.tv_usec / <span class="number">10000</span>;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">    <span class="keyword">return</span> t;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中，我们可以知道 <code>starttime</code> 代表的是 timer 初始化的墙上时间，精确到秒，而 <code>current</code> 则相当于 timer 启动后至今的时间差(也就是 timer 的运行时间)，精度为 10 ms，而 <code>current_point</code> 则相当于从系统开机至今经过的时间，精度同样为 10ms</p><h4 id="time、near-数组以及-t-数组的意义"><a href="#time、near-数组以及-t-数组的意义" class="headerlink" title="time、near 数组以及 t 数组的意义"></a>time、near 数组以及 t 数组的意义</h4><p>timer 一旦完成初始化后，就会交给 timer 线程去使用，为了了解上述三个字段的含义以及定时器背后的流程，我们需要先阅读 timer 线程的线程函数</p><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><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">//skynet_timer.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span>* <span class="title function_">thread_timer</span><span class="params">(<span class="type">void</span> *p)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">monitor</span> * <span class="title">m</span> =</span> p;</span><br><span class="line">    skynet_initthread(THREAD_TIMER);</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        skynet_updatetime();</span><br><span class="line">        skynet_socket_updatetime();</span><br><span class="line">        CHECK_ABORT</span><br><span class="line">        <span class="title function_">wakeup</span><span class="params">(m,m-&gt;count<span class="number">-1</span>)</span>;</span><br><span class="line">        usleep(<span class="number">2500</span>);</span><br><span class="line">        <span class="keyword">if</span> (SIG) &#123;</span><br><span class="line">            signal_hup();</span><br><span class="line">            SIG = <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// wakeup socket thread</span></span><br><span class="line">    skynet_socket_exit();</span><br><span class="line">    <span class="comment">// wakeup all worker thread</span></span><br><span class="line">    pthread_mutex_lock(&amp;m-&gt;mutex);</span><br><span class="line">    m-&gt;quit = <span class="number">1</span>;</span><br><span class="line">    pthread_cond_broadcast(&amp;m-&gt;cond);</span><br><span class="line">    pthread_mutex_unlock(&amp;m-&gt;mutex);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码可以看出，timer 的调用主要通过 <code>skynet_updatetime</code> 函数来实现(<code>skynet_socket_updatetime</code> 函数的部分会放到网络当中讲)。继续追踪相应的函数：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">add_node</span><span class="params">(<span class="keyword">struct</span> timer *T,<span class="keyword">struct</span> timer_node *node)</span> &#123;</span><br><span class="line">    <span class="type">uint32_t</span> time=node-&gt;expire;     <span class="comment">//节点的超时时间</span></span><br><span class="line">    <span class="type">uint32_t</span> current_time=T-&gt;time;  </span><br><span class="line">    <span class="comment">//判断 time 和 current_time 之间的间隔是否小于 256 个 tick(2560ms)</span></span><br><span class="line">    <span class="keyword">if</span> ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) &#123;</span><br><span class="line">        link(&amp;T-&gt;near[time&amp;TIME_NEAR_MASK],node);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="type">int</span> i;</span><br><span class="line">        <span class="type">uint32_t</span> mask=TIME_NEAR &lt;&lt; TIME_LEVEL_SHIFT;</span><br><span class="line">        <span class="comment">//找到合适的 level 添加节点</span></span><br><span class="line">        <span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;<span class="number">3</span>;i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> ((time|(mask<span class="number">-1</span>))==(current_time|(mask<span class="number">-1</span>))) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            mask &lt;&lt;= TIME_LEVEL_SHIFT;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        link(&amp;T-&gt;t[i][((time&gt;&gt;(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) &amp; TIME_LEVEL_MASK)],node);    </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">//将 t[level][idx] 中的链表取出，并将其中的节点插入 near 当中 </span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">move_list</span><span class="params">(<span class="keyword">struct</span> timer *T, <span class="type">int</span> level, <span class="type">int</span> idx)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">timer_node</span> *<span class="title">current</span> =</span> link_clear(&amp;T-&gt;t[level][idx]);</span><br><span class="line">    <span class="keyword">while</span> (current) &#123;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">timer_node</span> *<span class="title">temp</span>=</span>current-&gt;next;</span><br><span class="line">        add_node(T,current);</span><br><span class="line">        current=temp;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_updatetime</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//获得以 10 ms 为精度的单调时间</span></span><br><span class="line">    <span class="type">uint64_t</span> cp = gettime();</span><br><span class="line">    <span class="keyword">if</span>(cp &lt; TI-&gt;current_point) &#123;</span><br><span class="line">        skynet_error(<span class="literal">NULL</span>, <span class="string">&quot;time diff error: change from %lld to %lld&quot;</span>, cp, TI-&gt;current_point);</span><br><span class="line">        TI-&gt;current_point = cp;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cp != TI-&gt;current_point) &#123;</span><br><span class="line">        <span class="comment">//获得时间差</span></span><br><span class="line">        <span class="type">uint32_t</span> diff = (<span class="type">uint32_t</span>)(cp - TI-&gt;current_point);</span><br><span class="line">        TI-&gt;current_point = cp;</span><br><span class="line">        <span class="comment">//更新 timer 的运行时间</span></span><br><span class="line">        TI-&gt;current += diff;</span><br><span class="line">        <span class="type">int</span> i;</span><br><span class="line">        <span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;diff;i++) &#123;</span><br><span class="line">            timer_update(TI);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">timer_update</span><span class="params">(<span class="keyword">struct</span> timer *T)</span> &#123;</span><br><span class="line">    SPIN_LOCK(T);</span><br><span class="line">    <span class="comment">// try to dispatch timeout 0 (rare condition)</span></span><br><span class="line">    timer_execute(T);   <span class="comment">//看看 near[T-&gt;time &amp; TIME_NEAR_MASK] 中的链表是否为空</span></span><br><span class="line">    <span class="comment">// shift time first, and then dispatch timer message</span></span><br><span class="line">    timer_shift(T); <span class="comment">//移动链表，将 t 中链表移动值</span></span><br><span class="line">    timer_execute(T);</span><br><span class="line">    SPIN_UNLOCK(T);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">void</span> <span class="title function_">timer_execute</span><span class="params">(<span class="keyword">struct</span> timer *T)</span> &#123;</span><br><span class="line">    <span class="comment">//取出 time 的低 8 位，idx 代表了当前超时</span></span><br><span class="line">    <span class="type">int</span> idx = T-&gt;time &amp; TIME_NEAR_MASK;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">while</span> (T-&gt;near[idx].head.next) &#123;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">timer_node</span> *<span class="title">current</span> =</span> link_clear(&amp;T-&gt;near[idx]);</span><br><span class="line">        SPIN_UNLOCK(T);</span><br><span class="line">        <span class="comment">// dispatch_list don&#x27;t need lock T</span></span><br><span class="line">        dispatch_list(current);</span><br><span class="line">        SPIN_LOCK(T);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">timer_shift</span><span class="params">(<span class="keyword">struct</span> timer *T)</span> &#123;</span><br><span class="line">    <span class="type">int</span> mask = TIME_NEAR;</span><br><span class="line">    <span class="type">uint32_t</span> ct = ++T-&gt;time;</span><br><span class="line">    <span class="comment">//ct == 0 代表发生了溢出</span></span><br><span class="line">    <span class="keyword">if</span> (ct == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//将 t[3][0] 中链表取出并依次添加</span></span><br><span class="line">        move_list(T, <span class="number">3</span>, <span class="number">0</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="type">uint32_t</span> time = ct &gt;&gt; TIME_NEAR_SHIFT;</span><br><span class="line">        <span class="type">int</span> i=<span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> ((ct &amp; (mask<span class="number">-1</span>))==<span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">int</span> idx=time &amp; TIME_LEVEL_MASK;</span><br><span class="line">            <span class="keyword">if</span> (idx!=<span class="number">0</span>) &#123;</span><br><span class="line">                move_list(T, i, idx);</span><br><span class="line">                <span class="keyword">break</span>;              </span><br><span class="line">            &#125;</span><br><span class="line">            mask &lt;&lt;= TIME_LEVEL_SHIFT;</span><br><span class="line">            time &gt;&gt;= TIME_LEVEL_SHIFT;</span><br><span class="line">            ++i;</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>在上述代码中，可以看到每调用一次 <code>timer_shift</code>， <code>time</code>就会自增 1，而 <code>skynet_updatetime</code> 中一共执行了 diff 次 <code>timer_shift</code>。因此 <code>time</code> 代表了**从 timer 启动后至今一共经历了多少次 tick(一次 tick 的长度为 10ms)**。而且从 <code>timer_shift</code> 函数我们可以看出<code>time</code>和<code>near</code>数组以及<code>t</code>数组关系：</p><div style="text-align:center"> <img src="/img/skynet-timer/time-near-t-relationship.svg"/></div><div class="image-caption" align="center">time 与 near 以及 t 的关系</div><p>如上图所示，skynet 按照超时时间的紧迫程度为 timer 划分出 5 个槽，其中紧急程度为 near &gt; level0 &gt; level1 &gt; level2 &gt; level3。其中，<code>near</code> 中的定时器节点超时时间相差最大不超过 2^8 = 256 次 tick，而对于同一个 level 而言，t[level] 中的定时器超时时间间隔不超过 2^6 = 64 次 tick。 <code>time</code> 中不同的位域代表了不同的紧急程度。<code>timer_execute</code> 每次只对 <code>near</code> 中的定时器执行超时操作。</p><p>了解了上述内容，我们就能够明白 skynet 是怎么样运转定时器的：<strong>skynet 的 timer 线程会不断触发 <code>skynet_update</code> 函数，在该函数中会不断执行 <code>timer_execute</code>对 <code>near</code> 中的定时器执行超时操作。执行完毕后，调用<code>timer_shift</code> 从 <code>t[0]</code>~`t[3]<code>中选择合适的定时器节点加入到</code>near` 中，这一过程就相当于提高了定时器节点的紧急程度(因为随着时间的流逝，定时器节点的紧急程度会越来越向 near 逼近)。</strong></p><p>讲完了 skynet 定时器的运转流程，最后来看看为什么在函数 <code>move_list</code> 中，当 <code>time</code> 发生回绕时，为什么直接将 <code>t[3]</code> 放到 <code>near</code> 当中？这主要是因为添加节点采用的是位运算的方式，因此当发生 time 发生回绕时，低位会全部变为0，因此 <code>t[0]</code> ～ <code>t[3]</code> 都会被接连移动到 <code>near</code> 当中, 所以出于效率的考虑，直接将 <code>t[3]</code> 移入 <code>near</code> 即可</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第四篇文章，探讨了 skynet 的定时器机制，主要包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;定时器的基本数据结构&lt;/li&gt;
&lt;li&gt;starttime、current 以及 current_point 的意义，包括了 CLOCK_REALTIME 以及 CLOCK_MONOTONIC 等内容&lt;/li&gt;
&lt;li&gt;time、near 数组以及 t 数组的意义&lt;/li&gt;
&lt;li&gt;time、near 数组以及 t 数组三者之间的联系&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet源码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="Timer" scheme="https://phoenix500526.github.io/tags/Timer/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— 配置文件的加载</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD/</id>
    <published>2020-11-04T17:15:48.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第三篇文章，主要探讨 skynet 的配置文件加载机制，包含了以下内容：</p><blockquote><ul><li>main 函数是如何启动配置文件加载的？</li><li>让代码描述自身：使用 lua 作为配置语言，并利用 lua 的解释器来解析配置文件</li><li>main 函数是如何解析配置文件的？</li></ul></blockquote><span id="more"></span><h4 id="skynet-中-main-函数的流程"><a href="#skynet-中-main-函数的流程" class="headerlink" title="skynet 中 main 函数的流程"></a>skynet 中 main 函数的流程</h4><p>skynet 的 main 函数位于 skynet_main.c 文件当中，其定义如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> &#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * config_file = <span class="literal">NULL</span> ;</span><br><span class="line">    <span class="keyword">if</span> (argc &gt; <span class="number">1</span>) &#123;</span><br><span class="line">        config_file = argv[<span class="number">1</span>];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n&quot;</span></span><br><span class="line">            <span class="string">&quot;usage: skynet configfilename\n&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    skynet_globalinit();</span><br><span class="line">    skynet_env_init();</span><br><span class="line">    sigign();</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_config</span> <span class="title">config</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> LUA_CACHELIB</span></span><br><span class="line">    <span class="comment">// init the lock of code cache</span></span><br><span class="line">    luaL_initcodecache();</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">    <span class="comment">//创建一个新的 lua 环境，并将 lua 库加载进去, 该函数使用默认分配函数来进行创建</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">lua_State</span> *<span class="title">L</span> =</span> luaL_newstate();</span><br><span class="line">    luaL_openlibs(L);   <span class="comment">// link lua lib</span></span><br><span class="line">    <span class="comment">//将 load_config 加载为 lua 代码块，&quot;t&quot; 表示代码块的类型是文本类型，代码块的名称为 “=[skynet config]”</span></span><br><span class="line">    <span class="type">int</span> err =  luaL_loadbufferx(L, load_config, <span class="built_in">strlen</span>(load_config), <span class="string">&quot;=[skynet config]&quot;</span>, <span class="string">&quot;t&quot;</span>);</span><br><span class="line">    assert(err == LUA_OK);</span><br><span class="line">    <span class="comment">//向 lua 的虚拟栈中压入 config_file,作为 load_config 的参数</span></span><br><span class="line">    lua_pushstring(L, config_file);</span><br><span class="line">    <span class="comment">//lua_pcall 的第二个参数表示传递的参数个数，第三个参数表示期望的结果数量，第四个参数表示错误处理函数</span></span><br><span class="line">    err = lua_pcall(L, <span class="number">1</span>, <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="comment">//如果出错，则返回存放在虚拟栈栈顶的错误信息</span></span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>,<span class="string">&quot;%s\n&quot;</span>,lua_tostring(L,<span class="number">-1</span>));</span><br><span class="line">        lua_close(L);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    _init_env(L);</span><br><span class="line">    config.thread =  optint(<span class="string">&quot;thread&quot;</span>,<span class="number">8</span>);</span><br><span class="line">    config.module_path = optstring(<span class="string">&quot;cpath&quot;</span>,<span class="string">&quot;./cservice/?.so&quot;</span>);</span><br><span class="line">    config.harbor = optint(<span class="string">&quot;harbor&quot;</span>, <span class="number">1</span>);</span><br><span class="line">    config.bootstrap = optstring(<span class="string">&quot;bootstrap&quot;</span>,<span class="string">&quot;snlua bootstrap&quot;</span>);</span><br><span class="line">    config.daemon = optstring(<span class="string">&quot;daemon&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">    config.logger = optstring(<span class="string">&quot;logger&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">    config.logservice = optstring(<span class="string">&quot;logservice&quot;</span>, <span class="string">&quot;logger&quot;</span>);</span><br><span class="line">    config.profile = optboolean(<span class="string">&quot;profile&quot;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    lua_close(L);</span><br><span class="line">    skynet_start(&amp;config);</span><br><span class="line">    skynet_globalexit();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用过 skynet 的人都知道，skynet 在启动时相应的配置文件作为参数传递给 skynet 进程，例如 <code>skynet example/config</code>。从代码上可以看出，skynet 的 <code>main</code> 函数主要流程可以分为 3 个部分：</p><blockquote><ol><li>初始化运行环境，并通过 C API 调用相应的 lua 脚本解析配置文件，然后结果保存在 config 结构体中</li><li>以 config 为参数启动 skynet 进入事件循环</li><li>执行 skynet_globalexit() 完成 skynet 的退出准备</li></ol></blockquote><h4 id="使用-lua-语言描述配置文件"><a href="#使用-lua-语言描述配置文件" class="headerlink" title="使用 lua 语言描述配置文件"></a>使用 lua 语言描述配置文件</h4><p>在了解 skynet 是如何加载配置文件前，我们先来看看配置文件究竟长什么样？skynet 在 example 目录下提供了示范的 <code>config.path</code> 文件以及 <code>config</code> 文件</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">--config.path 文件</span></span><br><span class="line">root = <span class="string">&quot;./&quot;</span></span><br><span class="line">luaservice = root..<span class="string">&quot;service/?.lua;&quot;</span>..root..<span class="string">&quot;test/?.lua;&quot;</span>..root..<span class="string">&quot;examples/?.lua;&quot;</span>..root..<span class="string">&quot;test/?/init.lua&quot;</span></span><br><span class="line">lualoader = root .. <span class="string">&quot;lualib/loader.lua&quot;</span></span><br><span class="line">lua_path = root..<span class="string">&quot;lualib/?.lua;&quot;</span>..root..<span class="string">&quot;lualib/?/init.lua&quot;</span></span><br><span class="line">lua_cpath = root .. <span class="string">&quot;luaclib/?.so&quot;</span></span><br><span class="line">snax = root..<span class="string">&quot;examples/?.lua;&quot;</span>..root..<span class="string">&quot;test/?.lua&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">--config 文件</span></span><br><span class="line"><span class="comment">--include 为 load_config 中定义的脚本调用接口</span></span><br><span class="line">include <span class="string">&quot;config.path&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">-- preload = &quot;./examples/preload.lua&quot;   -- run preload.lua before every lua service run</span></span><br><span class="line">thread = <span class="number">8</span></span><br><span class="line">logger = <span class="literal">nil</span></span><br><span class="line">logpath = <span class="string">&quot;.&quot;</span></span><br><span class="line">harbor = <span class="number">1</span></span><br><span class="line">address = <span class="string">&quot;127.0.0.1:2526&quot;</span></span><br><span class="line">master = <span class="string">&quot;127.0.0.1:2013&quot;</span></span><br><span class="line">start = <span class="string">&quot;main&quot;</span>  <span class="comment">-- main script</span></span><br><span class="line">bootstrap = <span class="string">&quot;snlua bootstrap&quot;</span>   <span class="comment">-- The service for bootstrap</span></span><br><span class="line">standalone = <span class="string">&quot;0.0.0.0:2013&quot;</span></span><br><span class="line"><span class="comment">-- snax_interface_g = &quot;snax_g&quot;</span></span><br><span class="line"><span class="built_in">cpath</span> = root..<span class="string">&quot;cservice/?.so&quot;</span></span><br><span class="line"><span class="comment">-- daemon = &quot;./skynet.pid&quot;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>skynet 的配置文件本身使用了 lua 语言来描述对应的选项，而 skynet 也是通过 C API 来调用对应的 lua 脚本对配置文件进行解析。使用 lua 语言来描述配置选项，相较于以普通的文本来描述配置文件有以下几个好处：</p><blockquote><ol><li>lua 作为一门脚本语言，本身提供了解析器及灵活丰富的语法，不仅表达能力强于文本语言，而且 C/C++ 都为其提供了良好的支持，简单易用</li><li>使用 lua 语言描述配置文件，则配置文件本身也可以运行。你可以在配置文件中定义并调用函数，要求用户输入数据或者访问系统的环境变量等，这些都是文本语言所难以实现的。</li><li>lua 实现的配置文件可以扩展性强，当需要向配置文件中添加新的配置机制会更加方便。</li></ol></blockquote><h4 id="配置文件解析脚本"><a href="#配置文件解析脚本" class="headerlink" title="配置文件解析脚本"></a>配置文件解析脚本</h4><p>这个解析配置文件的脚本的内容则是以 C 字符串的形式保存在 <code>load_config</code> 变量当中。我们将其以 lua 代码的形式展示在下方：</p><figure class="highlight lua"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">-- load_config 的内容：</span></span><br><span class="line"><span class="keyword">local</span> result = &#123;&#125;</span><br><span class="line"><span class="comment">--获取相应的环境变量</span></span><br><span class="line"><span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">getenv</span><span class="params">(name)</span></span> <span class="keyword">return</span> <span class="built_in">assert</span>(<span class="built_in">os</span>.<span class="built_in">getenv</span>(name), <span class="string">[[os.getenv() failed: ]]</span> .. name) <span class="keyword">end</span></span><br><span class="line"><span class="comment">--获取路径的分隔符，在 linux 下 sep = /</span></span><br><span class="line"><span class="keyword">local</span> sep = <span class="built_in">package</span>.<span class="built_in">config</span>:<span class="built_in">sub</span>(<span class="number">1</span>,<span class="number">1</span>)</span><br><span class="line"><span class="comment">--将 . 和 / 合并得到了当前目录的相对路径 ./</span></span><br><span class="line"><span class="keyword">local</span> current_path = <span class="string">[[.]]</span>..sep</span><br><span class="line"></span><br><span class="line"><span class="comment">--定义脚本调用接口 include</span></span><br><span class="line"><span class="keyword">local</span> <span class="function"><span class="keyword">function</span> <span class="title">include</span><span class="params">(filename)</span></span></span><br><span class="line">    <span class="keyword">local</span> last_path = current_path</span><br><span class="line">    <span class="comment">--以最后一个/为分界将 filename 分割为路径 path 和文件名 name</span></span><br><span class="line">    <span class="keyword">local</span> <span class="built_in">path</span>, name = filename:<span class="built_in">match</span>(<span class="string">[[(.*]]</span>..sep..<span class="string">[[)(.*)$]]</span>)</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">path</span> <span class="keyword">then</span></span><br><span class="line">        <span class="comment">--若 path 为绝对路径，则起始字符为 /</span></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">path</span>:<span class="built_in">sub</span>(<span class="number">1</span>,<span class="number">1</span>) == sep <span class="keyword">then</span>    <span class="comment">-- root</span></span><br><span class="line">            current_path = <span class="built_in">path</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="comment">--path 为相对路径的情况</span></span><br><span class="line">            current_path = current_path .. <span class="built_in">path</span></span><br><span class="line">        <span class="keyword">end</span></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        <span class="comment">--若 path 为 nil，则说明 filename 不包含路径</span></span><br><span class="line">        name = filename</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line">    <span class="comment">--打开配置文件</span></span><br><span class="line">    <span class="keyword">local</span> f = <span class="built_in">assert</span>(<span class="built_in">io</span>.<span class="built_in">open</span>(current_path .. name))</span><br><span class="line">    <span class="comment">--读取配置文件中的所有内容</span></span><br><span class="line">    <span class="keyword">local</span> code = <span class="built_in">assert</span>(f:<span class="built_in">read</span> <span class="string">[[*a]]</span>)</span><br><span class="line">    <span class="comment">--如果配置文件中存在形如 $(环境变量) 的字符串，则调用 getenv 将其替换成环境变量的值</span></span><br><span class="line">    code = <span class="built_in">string</span>.<span class="built_in">gsub</span>(code, <span class="string">[[%$([%w_%d]+)]]</span>, <span class="built_in">getenv</span>)</span><br><span class="line">    f:<span class="built_in">close</span>()</span><br><span class="line">    <span class="comment">--将 code 中的内容以文本的形式加载到 result,其中 t 代表文本模式。</span></span><br><span class="line">    <span class="built_in">assert</span>(<span class="built_in">load</span>(code,<span class="string">[[@]]</span>..filename,<span class="string">[[t]]</span>,result))()</span><br><span class="line">    current_path = last_path</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"><span class="comment">--设置 result 的元表，这样在调用 include 的过程中，如果 result 中没有对应的键则会自动调用 include 函数</span></span><br><span class="line"><span class="built_in">setmetatable</span>(result, &#123; <span class="built_in">__index</span> = &#123; include = include &#125; &#125;)</span><br><span class="line"><span class="comment">--config_name 是变长参数</span></span><br><span class="line"><span class="keyword">local</span> config_name = ...</span><br><span class="line"><span class="comment">--使用 include 调用 config_name 脚本</span></span><br><span class="line">include(config_name)</span><br><span class="line"><span class="comment">--调用完 include 后将 result 的元表清除，避免遍历 result 时收到元表的影响</span></span><br><span class="line"><span class="built_in">setmetatable</span>(result, <span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">return</span> result</span><br></pre></td></tr></table></figure><h4 id="main-函数中解析脚本的流程"><a href="#main-函数中解析脚本的流程" class="headerlink" title="main 函数中解析脚本的流程"></a>main 函数中解析脚本的流程</h4><p>在了解了配置文件的内容及 load_config 的解析流程后，我们就可以来分析一下 <code>main</code> 函数加载配置文件的详细过程了</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_env.c</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_env</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">    lua_State *L;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="type">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">skynet_env</span> *<span class="title">E</span> =</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_env_init</span><span class="params">()</span> &#123;</span><br><span class="line">    E = skynet_malloc(<span class="keyword">sizeof</span>(*E));</span><br><span class="line">    SPIN_INIT(E)</span><br><span class="line">    E-&gt;L = luaL_newstate();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//skynet_main.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> _init_env(lua_State *L) &#123;</span><br><span class="line">    lua_pushnil(L);  <span class="comment">/* first key */</span></span><br><span class="line">    <span class="keyword">while</span> (lua_next(L, <span class="number">-2</span>) != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">int</span> keyt = lua_type(L, <span class="number">-2</span>);</span><br><span class="line">        <span class="keyword">if</span> (keyt != LUA_TSTRING) &#123;</span><br><span class="line">            <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Invalid config table\n&quot;</span>);</span><br><span class="line">            <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span> * key = lua_tostring(L,<span class="number">-2</span>);</span><br><span class="line">        <span class="keyword">if</span> (lua_type(L,<span class="number">-1</span>) == LUA_TBOOLEAN) &#123;</span><br><span class="line">            <span class="type">int</span> b = lua_toboolean(L,<span class="number">-1</span>);</span><br><span class="line">            skynet_setenv(key,b ? <span class="string">&quot;true&quot;</span> : <span class="string">&quot;false&quot;</span> );</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">const</span> <span class="type">char</span> * value = lua_tostring(L,<span class="number">-1</span>);</span><br><span class="line">            <span class="keyword">if</span> (value == <span class="literal">NULL</span>) &#123;</span><br><span class="line">                <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Invalid config table key = %s\n&quot;</span>, key);</span><br><span class="line">                <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            skynet_setenv(key,value);</span><br><span class="line">        &#125;</span><br><span class="line">        lua_pop(L,<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    lua_pop(L,<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    skynet_env_init();</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">//创建一个新的 lua 环境，并将 lua 库加载进去, 该函数使用默认分配函数来进行创建</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">lua_State</span> *<span class="title">L</span> =</span> luaL_newstate();</span><br><span class="line">    luaL_openlibs(L);   <span class="comment">// link lua lib</span></span><br><span class="line">    <span class="comment">//将 load_config 加载为 lua 代码块，&quot;t&quot; 表示代码块的类型是文本类型，代码块的名称为 “=[skynet config]”</span></span><br><span class="line">    <span class="type">int</span> err =  luaL_loadbufferx(L, load_config, <span class="built_in">strlen</span>(load_config), <span class="string">&quot;=[skynet config]&quot;</span>, <span class="string">&quot;t&quot;</span>);</span><br><span class="line">    assert(err == LUA_OK);</span><br><span class="line">    <span class="comment">//向 lua 的虚拟栈中压入 config_file,作为 load_config 的参数</span></span><br><span class="line">    lua_pushstring(L, config_file);</span><br><span class="line">    <span class="comment">//lua_pcall 的第二个参数表示传递的参数个数，第三个参数表示期望的结果数量，第四个参数表示错误处理函数</span></span><br><span class="line">    err = lua_pcall(L, <span class="number">1</span>, <span class="number">1</span>, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        <span class="comment">//如果出错，则返回存放在虚拟栈栈顶的错误信息</span></span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>,<span class="string">&quot;%s\n&quot;</span>,lua_tostring(L,<span class="number">-1</span>));</span><br><span class="line">        lua_close(L);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//将配置文件的内容添加到环境变量当中</span></span><br><span class="line">    _init_env(L);</span><br><span class="line">    <span class="comment">//opt*(key, value) 函数会以 key 为键值访问环境变量，如果设置了该环境变量则返回对应的值，若没有设置该环境变量则将其设为 value</span></span><br><span class="line">    config.thread =  optint(<span class="string">&quot;thread&quot;</span>,<span class="number">8</span>);</span><br><span class="line">    config.module_path = optstring(<span class="string">&quot;cpath&quot;</span>,<span class="string">&quot;./cservice/?.so&quot;</span>);</span><br><span class="line">    config.harbor = optint(<span class="string">&quot;harbor&quot;</span>, <span class="number">1</span>);</span><br><span class="line">    config.bootstrap = optstring(<span class="string">&quot;bootstrap&quot;</span>,<span class="string">&quot;snlua bootstrap&quot;</span>);</span><br><span class="line">    config.daemon = optstring(<span class="string">&quot;daemon&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">    config.logger = optstring(<span class="string">&quot;logger&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line">    config.logservice = optstring(<span class="string">&quot;logservice&quot;</span>, <span class="string">&quot;logger&quot;</span>);</span><br><span class="line">    config.profile = optboolean(<span class="string">&quot;profile&quot;</span>, <span class="number">1</span>);</span><br><span class="line">    lua_close(L);</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中可以看出，skynet 读取配置文件的大致流程为：先调用 <code>skynet_env_init</code> 函数初始化一个全局的 lua 环境，接着创建一个新的 lua 环境，并在该环境中使用 <code>luaL_loadbufferx</code>将 load_config 加载进来，然后使用 <code>lua_pushstring</code>将配置文件 config_file 压入 lua 的虚拟栈中。最后使用 <code>lua_pcall</code> 调用 load_config 脚本完成配置文件的解析。解析完毕后，调用 <code>_init_env</code> 将解析结果保存为环境变量。在需要时调用相关类型的 <code>opt</code> 函数读取相应的配置项。<br>最后我们来看看 <code>skynet_env</code> 的定义及相应函数的实现：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_env.c</span></span><br><span class="line"><span class="comment">// skynet_env 维护了一个全局的 lua 环境</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_env</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">    lua_State *L;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="type">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">skynet_env</span> *<span class="title">E</span> =</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="comment">//从全局的 lua 环境中查找全局变量 key</span></span><br><span class="line"><span class="type">const</span> <span class="type">char</span>*  <span class="title function_">skynet_getenv</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *key)</span> &#123;</span><br><span class="line">    SPIN_LOCK(E)</span><br><span class="line">    lua_State *L = E-&gt;L;</span><br><span class="line">    lua_getglobal(L, key);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * result = lua_tostring(L, <span class="number">-1</span>);</span><br><span class="line">    lua_pop(L, <span class="number">1</span>);</span><br><span class="line">    SPIN_UNLOCK(E)</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//将 &#123;key, value&#125; 保存为全局的 lua 环境的全局变量</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_setenv</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *key, <span class="type">const</span> <span class="type">char</span> *value)</span> &#123;</span><br><span class="line">    SPIN_LOCK(E)</span><br><span class="line">    lua_State *L = E-&gt;L;</span><br><span class="line">    lua_getglobal(L, key);</span><br><span class="line">    assert(lua_isnil(L, <span class="number">-1</span>));</span><br><span class="line">    lua_pop(L,<span class="number">1</span>);</span><br><span class="line">    lua_pushstring(L,value);</span><br><span class="line">    lua_setglobal(L,key);</span><br><span class="line">    SPIN_UNLOCK(E)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//skynet_main.c</span></span><br><span class="line"><span class="comment">//optboolean 和 optstring 函数的实现逻辑与 optint 相似。</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">optint</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *key, <span class="type">int</span> opt)</span> &#123;</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * str = skynet_getenv(key);</span><br><span class="line">    <span class="keyword">if</span> (str == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="type">char</span> tmp[<span class="number">20</span>];</span><br><span class="line">        <span class="built_in">sprintf</span>(tmp,<span class="string">&quot;%d&quot;</span>,opt);</span><br><span class="line">        skynet_setenv(key, tmp);</span><br><span class="line">        <span class="keyword">return</span> opt;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> strtol(str, <span class="literal">NULL</span>, <span class="number">10</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第三篇文章，主要探讨 skynet 的配置文件加载机制，包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;main 函数是如何启动配置文件加载的？&lt;/li&gt;
&lt;li&gt;让代码描述自身：使用 lua 作为配置语言，并利用 lua 的解释器来解析配置文件&lt;/li&gt;
&lt;li&gt;main 函数是如何解析配置文件的？&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="lua语言" scheme="https://phoenix500526.github.io/tags/lua%E8%AF%AD%E8%A8%80/"/>
    
    <category term="配置文件" scheme="https://phoenix500526.github.io/tags/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— skynet 的模块与服务</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-skynet-%E7%9A%84%E6%A8%A1%E5%9D%97%E4%B8%8E%E6%9C%8D%E5%8A%A1/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-skynet-%E7%9A%84%E6%A8%A1%E5%9D%97%E4%B8%8E%E6%9C%8D%E5%8A%A1/</id>
    <published>2020-11-04T17:09:27.000Z</published>
    <updated>2026-05-27T05:02:59.527Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第二篇文章，探讨了 skynet 的模块加载与服务的启动功能，主要包含了以下内容：</p><blockquote><ul><li>基本概念：模块与服务是什么？</li><li>模块的加载</li><li>服务的启动</li><li>流程的回顾</li></ul></blockquote><span id="more"></span><h4 id="1-基本概念：模块与服务"><a href="#1-基本概念：模块与服务" class="headerlink" title="1.基本概念：模块与服务"></a>1.基本概念：模块与服务</h4><p>*<em>模块(module)**：在skynet中，模块是指符合规范的 C 共享库文件。一个符合规范的 C 共享库应当具备 <code>*_create</code>、<code>*_signal</code>、<code>*_release</code> 以及 <code>*_init</code> 四个接口。其中 \</em> 代表模块名称。其中模块的接口及定义如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_module.h</span></span><br><span class="line"><span class="comment">//每一个模块都应当提供 create、init、release 以及 signal 等四个接口</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="type">void</span> * (*skynet_dl_create)(<span class="type">void</span>);</span><br><span class="line"><span class="keyword">typedef</span> <span class="title function_">int</span> <span class="params">(*skynet_dl_init)</span><span class="params">(<span class="type">void</span> * inst, <span class="keyword">struct</span> skynet_context *, <span class="type">const</span> <span class="type">char</span> * parm)</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="title function_">void</span> <span class="params">(*skynet_dl_release)</span><span class="params">(<span class="type">void</span> * inst)</span>;</span><br><span class="line"><span class="keyword">typedef</span> <span class="title function_">void</span> <span class="params">(*skynet_dl_signal)</span><span class="params">(<span class="type">void</span> * inst, <span class="type">int</span> signal)</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_module</span> &#123;</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * name; <span class="comment">//模块名称</span></span><br><span class="line">    <span class="type">void</span> * module;     <span class="comment">//用于访问对应so库的句柄，由dlopen函数获得</span></span><br><span class="line">    skynet_dl_create create;</span><br><span class="line">    skynet_dl_init init;</span><br><span class="line">    skynet_dl_release release;</span><br><span class="line">    skynet_dl_signal signal;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//skynet_module.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAX_MODULE_TYPE 32</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//modules 列表，用于存放全部用到的 module </span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">modules</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> count;  <span class="comment">//存放的 module 的数量</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> * path;  <span class="comment">//path由配置文件中的module_path提供</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_module</span> <span class="title">m</span>[<span class="title">MAX_MODULE_TYPE</span>];</span>    <span class="comment">//存储module的数组</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="type">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">modules</span> * <span class="title">M</span> =</span> <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure><p>**服务(service)**：相对于模块是静态的概念，服务则是动态的概念，指的是运行在独立上下文中的模块。<br>skynet 提供了这样的一种机制：用户可以将自定义的模块放置到 skynet 指定的目录下。当 skynet 使用到对应的服务时，会将该模块加载到 modules 当中，并为其创建一个独立的上下文环境(context)。这样不同的服务的运行环境相互透明，交互则通过消息队列来进行。</p><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><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"><span class="comment">//skynet_server.c:</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> &#123;</span></span><br><span class="line">    <span class="type">void</span> * instance;    <span class="comment">//调用模块的 *_create 函数创建对应的服务实例</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_module</span> * <span class="title">mod</span>;</span> <span class="comment">//指向对应的模块</span></span><br><span class="line">    <span class="type">void</span> * cb_ud;   <span class="comment">//回调函数所需参数</span></span><br><span class="line">    skynet_cb cb;   <span class="comment">//回调函数</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> *<span class="title">queue</span>;</span>    <span class="comment">//服务所属的消息队列</span></span><br><span class="line">    FILE * logfile;     <span class="comment">//日志文件句柄</span></span><br><span class="line">    <span class="type">uint64_t</span> cpu_cost;  <span class="comment">// in microsec</span></span><br><span class="line">    <span class="type">uint64_t</span> cpu_start; <span class="comment">// in microsec</span></span><br><span class="line">    <span class="type">char</span> result[<span class="number">32</span>];    <span class="comment">//存放回调函数的执行结果</span></span><br><span class="line">    <span class="type">uint32_t</span> handle;    <span class="comment">//位于该上下文环境中的一个服务的句柄</span></span><br><span class="line">    <span class="type">int</span> session_id;     <span class="comment">//session_id 用来将请求和响应匹配起来</span></span><br><span class="line">    <span class="type">int</span> ref;            <span class="comment">//引用计数，当 ref == 0 时回收内存</span></span><br><span class="line">    <span class="type">int</span> message_count;  <span class="comment">//消息队列中消息的数量？</span></span><br><span class="line">    <span class="type">bool</span> init;          <span class="comment">//是否完成了初始化</span></span><br><span class="line">    <span class="type">bool</span> endless;       <span class="comment">//该服务是否是一个无限循环</span></span><br><span class="line">    <span class="type">bool</span> profile;       </span><br><span class="line"></span><br><span class="line">    CHECKCALLING_DECL</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="2-模块的加载"><a href="#2-模块的加载" class="headerlink" title="2.模块的加载"></a>2.模块的加载</h4><p>在 skynet 中，模块的加载主要通过 <code>skynet_module_query</code> 函数来完成。当 skynet 启动时会先执行 <code>skynet_module_init</code> 函数对全局模块列表 modules 进行初始化。当需要使用到某个服务时，skynet 会调用 <code>skynet_context_new</code> 函数为其创建上下文，这个过程当中会调用 <code>skynet_module_query(name)</code> 函数，该函数会根据 name 查找相应的模块。如果该模块尚未被加载，则将其加载到 modules 当中。具体代码如下</p><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><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="comment">//skynet_module.c</span></span><br><span class="line"><span class="comment">//根据模块名查找对应的模块，如果找不到且 modules 中尚有空间则将模块加载进来</span></span><br><span class="line"><span class="keyword">struct</span> skynet_module * <span class="title function_">skynet_module_query</span><span class="params">(<span class="type">const</span> <span class="type">char</span> * name)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_module</span> * <span class="title">result</span> =</span> _query(name);</span><br><span class="line">    <span class="keyword">if</span> (result)</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line"></span><br><span class="line">    SPIN_LOCK(M)</span><br><span class="line">    <span class="comment">//双重检测可以避免以下情形：两个不同的服务 A 和 B 同时调用了一个服务 C，在 A 查找 C 中的模块时，B 进入自旋等待状态。</span></span><br><span class="line">    <span class="comment">//当 A 调用结束后会将 C 模块插入 modules 中，此时如果 B 再执行插入则会导致重复插入</span></span><br><span class="line">    result = _query(name); <span class="comment">// double check</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (result == <span class="literal">NULL</span> &amp;&amp; M-&gt;count &lt; MAX_MODULE_TYPE) &#123;</span><br><span class="line">        <span class="type">int</span> index = M-&gt;count;</span><br><span class="line">        <span class="comment">//返回相应动态库的句柄</span></span><br><span class="line">        <span class="type">void</span> * dl = _try_open(M,name);</span><br><span class="line">        <span class="keyword">if</span> (dl) &#123;</span><br><span class="line">            M-&gt;m[index].name = name;</span><br><span class="line">            M-&gt;m[index].module = dl;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (open_sym(&amp;M-&gt;m[index]) == <span class="number">0</span>) &#123;</span><br><span class="line">                M-&gt;m[index].name = skynet_strdup(name);</span><br><span class="line">                M-&gt;count ++;</span><br><span class="line">                result = &amp;M-&gt;m[index];</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    SPIN_UNLOCK(M)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">open_sym</span><span class="params">(<span class="keyword">struct</span> skynet_module *mod)</span> &#123;</span><br><span class="line">    mod-&gt;create = get_api(mod, <span class="string">&quot;_create&quot;</span>);</span><br><span class="line">    mod-&gt;init = get_api(mod, <span class="string">&quot;_init&quot;</span>);</span><br><span class="line">    mod-&gt;release = get_api(mod, <span class="string">&quot;_release&quot;</span>);</span><br><span class="line">    mod-&gt;signal = get_api(mod, <span class="string">&quot;_signal&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> mod-&gt;init == <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//从动态库中找到对应的 api 并将其函数地址返回</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span>* <span class="title function_">get_api</span><span class="params">(<span class="keyword">struct</span> skynet_module *mod, <span class="type">const</span> <span class="type">char</span> *api_name)</span> &#123;</span><br><span class="line">    <span class="type">size_t</span> name_size = <span class="built_in">strlen</span>(mod-&gt;name);</span><br><span class="line">    <span class="type">size_t</span> api_size = <span class="built_in">strlen</span>(api_name);</span><br><span class="line">    <span class="type">char</span> tmp[name_size + api_size + <span class="number">1</span>];</span><br><span class="line">    <span class="built_in">memcpy</span>(tmp, mod-&gt;name, name_size);</span><br><span class="line">    <span class="built_in">memcpy</span>(tmp+name_size, api_name, api_size+<span class="number">1</span>);</span><br><span class="line">    <span class="type">char</span> *ptr = <span class="built_in">strrchr</span>(tmp, <span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (ptr == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        ptr = tmp;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        ptr = ptr + <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dlsym(mod-&gt;module, ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中可以看出，加载模块需要先调用 <code>_try_open()</code> 函数去打开对应的 .so 文件, 并通过 <code>open_sym</code> 函数来将对应的 api 存放到 <code>module</code> 结构体中相应的函数指针处。.so 文件中的 api 命名统一按照 “module_function” 的格式命名。</p><h4 id="3-服务的启动"><a href="#3-服务的启动" class="headerlink" title="3.服务的启动"></a>3.服务的启动</h4><p>skynet 中服务的创建主要通过 <code>skynet_context_new</code> 来完成，其代码定义如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_server.c</span></span><br><span class="line"><span class="keyword">struct</span> skynet_context* <span class="title function_">skynet_context_new</span><span class="params">(<span class="type">const</span> <span class="type">char</span> * name, <span class="type">const</span> <span class="type">char</span> *param)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_module</span> * <span class="title">mod</span> =</span> skynet_module_query(name);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (mod == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">void</span> *inst = skynet_module_instance_create(mod);</span><br><span class="line">    <span class="keyword">if</span> (inst == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">ctx</span> =</span> skynet_malloc(<span class="keyword">sizeof</span>(*ctx));</span><br><span class="line">    CHECKCALLING_INIT(ctx)</span><br><span class="line"></span><br><span class="line">    ctx-&gt;mod = mod;</span><br><span class="line">    ctx-&gt;instance = inst;</span><br><span class="line">    <span class="comment">//此处将引用置为 2 的原因是因为在 skynet_handle_register 中会将 ctx 保存起来，增加一次引用。</span></span><br><span class="line">    <span class="comment">//之后再将 ctx 返回给对应的变量，增加了一次引用，因此 ref = 2</span></span><br><span class="line">    ctx-&gt;ref = <span class="number">2</span>;</span><br><span class="line">    ctx-&gt;cb = <span class="literal">NULL</span>;</span><br><span class="line">    ctx-&gt;cb_ud = <span class="literal">NULL</span>;</span><br><span class="line">    ctx-&gt;session_id = <span class="number">0</span>;</span><br><span class="line">    ctx-&gt;logfile = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    ctx-&gt;init = <span class="literal">false</span>;</span><br><span class="line">    ctx-&gt;endless = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    ctx-&gt;cpu_cost = <span class="number">0</span>;</span><br><span class="line">    ctx-&gt;cpu_start = <span class="number">0</span>;</span><br><span class="line">    ctx-&gt;message_count = <span class="number">0</span>;</span><br><span class="line">    ctx-&gt;profile = G_NODE.profile;</span><br><span class="line">    <span class="comment">// Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle</span></span><br><span class="line">    ctx-&gt;handle = <span class="number">0</span>;    </span><br><span class="line">    ctx-&gt;handle = skynet_handle_register(ctx);</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> * <span class="title">queue</span> =</span> ctx-&gt;<span class="built_in">queue</span> = skynet_mq_create(ctx-&gt;handle);</span><br><span class="line">    <span class="comment">// init function maybe use ctx-&gt;handle, so it must init at last</span></span><br><span class="line">    context_inc();</span><br><span class="line"></span><br><span class="line">    CHECKCALLING_BEGIN(ctx)</span><br><span class="line">    <span class="type">int</span> r = skynet_module_instance_init(mod, inst, ctx, param);</span><br><span class="line">    CHECKCALLING_END(ctx)</span><br><span class="line">    <span class="keyword">if</span> (r == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//skynet_context_release 会在 ctx-&gt;ref == 0 时回收这个 context</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">ret</span> =</span> skynet_context_release(ctx);</span><br><span class="line">        <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">            ctx-&gt;init = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        skynet_globalmq_push(<span class="built_in">queue</span>);</span><br><span class="line">        <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">            skynet_error(ret, <span class="string">&quot;LAUNCH %s %s&quot;</span>, name, param ? param : <span class="string">&quot;&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        skynet_error(ctx, <span class="string">&quot;FAILED launch %s&quot;</span>, name);</span><br><span class="line">        <span class="type">uint32_t</span> handle = ctx-&gt;handle;</span><br><span class="line">        skynet_context_release(ctx);</span><br><span class="line">        skynet_handle_retire(handle);</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">drop_t</span> <span class="title">d</span> =</span> &#123; handle &#125;;</span><br><span class="line">        skynet_mq_release(<span class="built_in">queue</span>, drop_message, &amp;d);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中我们可以看出 <code>skynet_context_new</code> 的主要工作为如下：</p><blockquote><ol><li>在 modules 中查找对应的模块名称，如果存在则直接返回模块的句柄，不存在则将模块加载进内存，并保存在 modules 当中</li><li>调用 module 的 create api 创建 module 的实例 inst</li><li>分配 skynet_context 结构体并为其赋上相应的值</li><li>调用 module 的 init api 为 inst 进行初始化<br>   如果初始化成功，则将该 context 中的次级消息队列 queue 放入到全局消息队列当中，然后返回创建好的服务(context)<br>   如果失败则释放分配的 skynet_context, 为服务分配的 handle 以及专属的次级消息队列, 然后返回 NULL。</li></ol></blockquote><p>上述代码中需要注意的，<code>ctx-&gt;ref</code>的初始值为 2。这是因为当 <code>skynet_context_new</code> 执行完毕后，会有两个地方引用了新创建好的 context。一个是 <code>skynet_context_new</code> 的调用者，它会保存返回的 context 指针; 另一个则是 <code>skynet_handle_register</code> 函数，该函数会将新创建的 context 保存在 <code>handle_storage</code> 的 <code>slot</code> 字段中<br>接下来，我们来看看 <code>skynet_context_new</code> 中的几个模块相关的函数：<code>skynet_module_instance_create</code>、 <code>skynet_module_instance_init</code></p><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><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">//skynet_module.c</span></span><br><span class="line"><span class="type">void</span>* <span class="title function_">skynet_module_instance_create</span><span class="params">(<span class="keyword">struct</span> skynet_module *m)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (m-&gt;create) &#123;</span><br><span class="line">        <span class="keyword">return</span> m-&gt;create();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (<span class="type">void</span> *)(<span class="type">intptr_t</span>)(~<span class="number">0</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="type">int</span> <span class="title function_">skynet_module_instance_init</span><span class="params">(<span class="keyword">struct</span> skynet_module *m, <span class="type">void</span> * inst, <span class="keyword">struct</span> skynet_context *ctx, <span class="type">const</span> <span class="type">char</span> * parm)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> m-&gt;init(inst, ctx, parm);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_module_instance_release</span><span class="params">(<span class="keyword">struct</span> skynet_module *m, <span class="type">void</span> *inst)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (m-&gt;release) &#123;</span><br><span class="line">        m-&gt;release(inst);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_module_instance_signal</span><span class="params">(<span class="keyword">struct</span> skynet_module *m, <span class="type">void</span> *inst, <span class="type">int</span> signal)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (m-&gt;signal) &#123;</span><br><span class="line">        m-&gt;signal(inst, signal);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，<code>skynet_module_instance_create</code> 的返回值 <code>(void *)(intptr_t)(~0)</code> 引起了我的好奇。这个地址的值为 <code>0xffffffff</code>, 代表的是内存地址的最底端的地址。它主要的作用就是为了和 <code>NULL</code> 作区分。当 skynet 调用对应模块的 <code>_create</code> 函数时, 如果此时内存耗尽，无法创建模块对象，则会返回 <code>NULL</code>。如果用户在没有定义 <code>_create</code> 函数情况下也使用 <code>NULL</code> 做返回值，则无法区分这两种情况。</p><h4 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h4><p>简单地来讲，skynet 的模块加载与服务创建的整体过程为：<br>当 skynet 启动时会先执行 <code>skynet_module_init</code> 进行 modules 的创建，随后调用 <code>skynet_context_new</code> 创建新的服务。在这个过程当中， skynet 先会自动根据配置文件中指定的模块路径进行 module 的加载。完成加载后的 module 将被保存在全局的 modules 当中。随后，分配 <code>skynet_context</code> 结构体并进行相应赋值。在赋值的过程中会调用到 module 的 <code>_create</code>, <code>_init</code> 等 api。如果分配成功则将 <code>context</code> 返回给调用者，失败返回 <code>NULL</code>。创建好的服务彼此透明，运行在不同的 <code>skynet_context</code> 下，不同的服务之间的交互必须通过消息队列进行转发</p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第二篇文章，探讨了 skynet 的模块加载与服务的启动功能，主要包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;基本概念：模块与服务是什么？&lt;/li&gt;
&lt;li&gt;模块的加载&lt;/li&gt;
&lt;li&gt;服务的启动&lt;/li&gt;
&lt;li&gt;流程的回顾&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet源码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="动态库" scheme="https://phoenix500526.github.io/tags/%E5%8A%A8%E6%80%81%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>skynet 源码阅读笔记 —— 消息调度机制</title>
    <link href="https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E6%B6%88%E6%81%AF%E8%B0%83%E5%BA%A6%E6%9C%BA%E5%88%B6/"/>
    <id>https://phoenix500526.github.io/2020/11/04/skynet-%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-%E2%80%94%E2%80%94-%E6%B6%88%E6%81%AF%E8%B0%83%E5%BA%A6%E6%9C%BA%E5%88%B6/</id>
    <published>2020-11-04T16:35:38.000Z</published>
    <updated>2026-05-27T05:02:59.528Z</updated>
    
    <content type="html"><![CDATA[<h4 id="文前导读"><a href="#文前导读" class="headerlink" title="文前导读"></a>文前导读</h4><p>skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第一篇文章，主要探讨 skynet 的消息调度机制，包含了以下内容：</p><blockquote><ul><li>基本数据结构：包括消息队列以及消息本身的数据结构</li><li>消息调度的过程：主要包括了消息是如何生产，又如何消费的</li><li>多线程模型：skynet 的线程池设计，如何为进程设置合理的优先级使其拥有较高的 CPU 利用率，如何做线程监控设计(monitor) 以及如何高效地实现线程安全。</li></ul></blockquote><span id="more"></span><h4 id="基本数据结构之消息队列"><a href="#基本数据结构之消息队列" class="headerlink" title="基本数据结构之消息队列"></a>基本数据结构之消息队列</h4><p>skynet 采用了<strong>二级消息队列</strong>模式，其中顶层消息队列为 <code>global_queue</code>，而底层的消息队列为 <code>message_queue</code>，它们的具体定义如下：</p><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><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="comment">//skynet_mq.c</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span>   <span class="comment">//自旋锁，避免多个线程同时向一个队列中 push 消息时导致的竞态问题</span></span><br><span class="line">    <span class="type">uint32_t</span> handle;    <span class="comment">//服务句柄。表明该消息队列具体属于哪个服务</span></span><br><span class="line">    <span class="type">int</span> cap;            <span class="comment">//消息队列的容量</span></span><br><span class="line">    <span class="type">int</span> head;</span><br><span class="line">    <span class="type">int</span> tail;</span><br><span class="line">    <span class="type">int</span> release;        <span class="comment">//是否可以释放信息</span></span><br><span class="line">    <span class="type">int</span> in_global;      <span class="comment">//是否位于全局队列当中</span></span><br><span class="line">    <span class="type">int</span> overload;       <span class="comment">//是否过载</span></span><br><span class="line">    <span class="type">int</span> overload_threshold; <span class="comment">//过载上限</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_message</span> *<span class="title">queue</span>;</span>   <span class="comment">//指向消息队列中存放消息的一片内存区域</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> *<span class="title">next</span>;</span>     <span class="comment">//指向下个次级消息队列的指针</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">global_queue</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> *<span class="title">head</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> *<span class="title">tail</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">spinlock</span> <span class="title">lock</span>;</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>skynet 的消息队列形式如下：</p><div style="text-align:center"> <img src="/img/skynet-message-queue/two-level-queue.svg" width=800 height=505/></div><div class="image-caption" align="center">二级消息队列</div><h4 id="基本数据结构之消息"><a href="#基本数据结构之消息" class="headerlink" title="基本数据结构之消息"></a>基本数据结构之消息</h4><p>skynet 中一共支持两种不同的消息，一种为本地消息<code>skynet_message</code> ，另一种则为远程消息 <code>remote_message</code>。其中，<code>skynet_message</code>和 <code>remote_message</code> 如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_mq.h</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_message</span> &#123;</span></span><br><span class="line">    <span class="type">uint32_t</span> source;<span class="comment">//发送的源地址</span></span><br><span class="line">    <span class="comment">//session 用于将请求包和响应包匹配起来。当一个服务向另一个服务发起请求时，会产生一个 session</span></span><br><span class="line">    <span class="comment">//当响应端处理完毕后，会将 session 原样返回，这样请求端就可以根据 session 找到对应的结果</span></span><br><span class="line">    <span class="type">int</span> session;</span><br><span class="line">    <span class="type">void</span> * data;</span><br><span class="line">    <span class="type">size_t</span> sz;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//skynet_harbor.h</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> GLOBALNAME_LENGTH 16</span></span><br><span class="line"><span class="comment">//remote_name 代表一个远程 skynet 节点。</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">remote_name</span> &#123;</span></span><br><span class="line"><span class="type">char</span> name[GLOBALNAME_LENGTH];</span><br><span class="line"><span class="type">uint32_t</span> handle;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">remote_message</span> &#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">remote_name</span> <span class="title">destination</span>;</span></span><br><span class="line"><span class="type">const</span> <span class="type">void</span> * message;</span><br><span class="line"><span class="type">size_t</span> sz;</span><br><span class="line"><span class="type">int</span> type;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这里解释一下上述消息定义中的 <code>session</code> 和 <code>type</code> 字段。**<code>session</code> 主要用来匹配一对请求和响应<strong>。当一个服务向另一个服务提起请求时，会生成一个 session，并跟随请求包一并发送出去。接收端接收到包并处理完毕后，再将同样的 session 返回。这样，编写服务的人只需要在服务的 callback 函数中记录下所有发送出去的 session 就可以在收到每个消息后调用正确的处理函数。而 **<code>type</code> 主要是用来区分不同的消息包类型</strong>。type 的定义如下：</p><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="meta">#<span class="keyword">define</span> PTYPE_TEXT 0<span class="comment">//文本类型</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_RESPONSE 1<span class="comment">//响应包</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_MULTICAST 2<span class="comment">//组播包</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_CLIENT 3<span class="comment">//客户端消息</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_SYSTEM 4<span class="comment">//系统消息</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_HARBOR 5<span class="comment">//集群内其他的 skynet 节点发来的消息</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_TAG_DONTCOPY 0x10000<span class="comment">//禁止拷贝</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PTYPE_TAG_ALLOCSESSION 0x20000<span class="comment">//分配新的 session</span></span></span><br></pre></td></tr></table></figure><h4 id="谁生产，谁消费？"><a href="#谁生产，谁消费？" class="headerlink" title="谁生产，谁消费？"></a>谁生产，谁消费？</h4><p>在 skynet 中，每个服务都拥有自己的一个次级消息队列。一个服务给另一个服务发送消息的过程，本质上就是将一个 skynet_message 压入到目标服务的次级消息队列当中。当一个服务的次级消息队列非空时，skynet 会将其push 到全局消息队列当中。而消息的消费，则是由线程池中的 worker 线程来完成，其大致的框图如下：</p><div style="text-align:center"> <img src="/img/skynet-message-queue/producer-consumer-manager.svg" width=800/></div><div class="image-caption" align="center">生产者消费者管理者模型</div><h6 id="消息消费的过程"><a href="#消息消费的过程" class="headerlink" title="消息消费的过程"></a>消息消费的过程</h6><p>在 skynet 启动的时候，会根据配置文件的 <code>thread</code> 字段初始化线程池。其中线程池中的前三个线程是 <code>monitor</code>, <code>timer</code> 和 <code>socket</code> 线程。其中，monitor 线程主要负责检查每个服务是否陷入了死循环，socket 线程负责网络相关操作，timer 线程则负责定时器。对应代码如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_start.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span>* <span class="title function_">thread_worker</span><span class="params">(<span class="type">void</span> *p)</span> &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">worker_parm</span> *<span class="title">wp</span> =</span> p;</span><br><span class="line"><span class="type">int</span> id = wp-&gt;id;</span><br><span class="line"><span class="type">int</span> weight = wp-&gt;weight;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">monitor</span> *<span class="title">m</span> =</span> wp-&gt;m;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_monitor</span> *<span class="title">sm</span> =</span> m-&gt;m[id];</span><br><span class="line">skynet_initthread(THREAD_WORKER);</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> * <span class="title">q</span> =</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="keyword">while</span> (!m-&gt;quit) &#123;</span><br><span class="line">q = skynet_context_message_dispatch(sm, q, weight);</span><br><span class="line"><span class="keyword">if</span> (q == <span class="literal">NULL</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> (pthread_mutex_lock(&amp;m-&gt;mutex) == <span class="number">0</span>) &#123;</span><br><span class="line">++ m-&gt;sleep;</span><br><span class="line"><span class="comment">// &quot;spurious wakeup&quot; is harmless,</span></span><br><span class="line"><span class="comment">// because skynet_context_message_dispatch() can be call at any time.</span></span><br><span class="line"><span class="keyword">if</span> (!m-&gt;quit)</span><br><span class="line">pthread_cond_wait(&amp;m-&gt;cond, &amp;m-&gt;mutex);</span><br><span class="line">-- m-&gt;sleep;</span><br><span class="line"><span class="keyword">if</span> (pthread_mutex_unlock(&amp;m-&gt;mutex)) &#123;</span><br><span class="line"><span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;unlock mutex error&quot;</span>);</span><br><span class="line"><span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">start</span><span class="params">(<span class="type">int</span> thread)</span> &#123;</span><br><span class="line"><span class="type">pthread_t</span> pid[thread+<span class="number">3</span>];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">monitor</span> *<span class="title">m</span> =</span> skynet_malloc(<span class="keyword">sizeof</span>(*m));</span><br><span class="line"><span class="built_in">memset</span>(m, <span class="number">0</span>, <span class="keyword">sizeof</span>(*m));</span><br><span class="line">m-&gt;count = thread;</span><br><span class="line">m-&gt;sleep = <span class="number">0</span>;</span><br><span class="line">m-&gt;m = skynet_malloc(thread * <span class="keyword">sizeof</span>(<span class="keyword">struct</span> skynet_monitor *));</span><br><span class="line"><span class="type">int</span> i;</span><br><span class="line"><span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;thread;i++) &#123;</span><br><span class="line">m-&gt;m[i] = skynet_monitor_new();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (pthread_mutex_init(&amp;m-&gt;mutex, <span class="literal">NULL</span>)) &#123;</span><br><span class="line"><span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Init mutex error&quot;</span>);</span><br><span class="line"><span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (pthread_cond_init(&amp;m-&gt;cond, <span class="literal">NULL</span>)) &#123;</span><br><span class="line"><span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Init cond error&quot;</span>);</span><br><span class="line"><span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//创建 monitor 线程负责监视所有的 worker 线程</span></span><br><span class="line">create_thread(&amp;pid[<span class="number">0</span>], thread_monitor, m);</span><br><span class="line">create_thread(&amp;pid[<span class="number">1</span>], thread_timer, m);</span><br><span class="line">create_thread(&amp;pid[<span class="number">2</span>], thread_socket, m);</span><br><span class="line"><span class="comment">//worker 线程的权重值</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> weight[] = &#123; </span><br><span class="line"><span class="number">-1</span>, <span class="number">-1</span>, <span class="number">-1</span>, <span class="number">-1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line"><span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, </span><br><span class="line"><span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">2</span>, </span><br><span class="line"><span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, <span class="number">3</span>, &#125;;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">worker_parm</span> <span class="title">wp</span>[<span class="title">thread</span>];</span></span><br><span class="line"><span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;thread;i++) &#123;</span><br><span class="line">wp[i].m = m;</span><br><span class="line">wp[i].id = i;</span><br><span class="line"><span class="keyword">if</span> (i &lt; <span class="keyword">sizeof</span>(weight)/<span class="keyword">sizeof</span>(weight[<span class="number">0</span>])) &#123;</span><br><span class="line">wp[i].weight= weight[i];</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">wp[i].weight = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line">create_thread(&amp;pid[i+<span class="number">3</span>], thread_worker, &amp;wp[i]);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;thread+<span class="number">3</span>;i++) &#123;</span><br><span class="line">pthread_join(pid[i], <span class="literal">NULL</span>); </span><br><span class="line">&#125;</span><br><span class="line">free_monitor(m);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在上述代码中，我们可以看出 skynet 创建线程池的流程，先创建好 monitor、socket 和 timer 这三个线程，然后创建相应数量的 worker 线程，而每个 worker 线程最终会调用 <code>skynet_context_message_dispatch</code>函数从全局消息队列中获取消息。<code>skynet_context_message_dispatch</code> 的定义如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// skynet_start.c</span></span><br><span class="line"><span class="keyword">struct</span> message_queue* <span class="title function_">skynet_context_message_dispatch</span><span class="params">(<span class="keyword">struct</span> skynet_monitor *sm, <span class="keyword">struct</span> message_queue *q, <span class="type">int</span> weight)</span> &#123;</span><br><span class="line"><span class="comment">//从全局消息队列中取出一个次级消息队列</span></span><br><span class="line"><span class="keyword">if</span> (q == <span class="literal">NULL</span>) &#123;</span><br><span class="line">q = skynet_globalmq_pop();</span><br><span class="line"><span class="keyword">if</span> (q==<span class="literal">NULL</span>)</span><br><span class="line"><span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//获得该次级消息队列所对应的服务的句柄</span></span><br><span class="line"><span class="type">uint32_t</span> handle = skynet_mq_handle(q);</span><br><span class="line"><span class="comment">//获取服务上下文</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_context</span> * <span class="title">ctx</span> =</span> skynet_handle_grab(handle);</span><br><span class="line">    <span class="comment">//若取出的服务没有上下文，则重取一个新的次级消息队列</span></span><br><span class="line"><span class="keyword">if</span> (ctx == <span class="literal">NULL</span>) &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">drop_t</span> <span class="title">d</span> =</span> &#123; handle &#125;;</span><br><span class="line">skynet_mq_release(q, drop_message, &amp;d);</span><br><span class="line"><span class="keyword">return</span> skynet_globalmq_pop();</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> i,n=<span class="number">1</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_message</span> <span class="title">msg</span>;</span></span><br><span class="line">    <span class="comment">//根据不同的权重从消息队列中获得不同数量的消息</span></span><br><span class="line"><span class="keyword">for</span> (i=<span class="number">0</span>;i&lt;n;i++) &#123;</span><br><span class="line"><span class="keyword">if</span> (skynet_mq_pop(q,&amp;msg)) &#123;</span><br><span class="line">skynet_context_release(ctx);</span><br><span class="line"><span class="keyword">return</span> skynet_globalmq_pop();</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (i==<span class="number">0</span> &amp;&amp; weight &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">n = skynet_mq_length(q);</span><br><span class="line">n &gt;&gt;= weight;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> overload = skynet_mq_overload(q);</span><br><span class="line"><span class="keyword">if</span> (overload) &#123;</span><br><span class="line">skynet_error(ctx, <span class="string">&quot;May overload, message queue length = %d&quot;</span>, overload);</span><br><span class="line">&#125;</span><br><span class="line">skynet_monitor_trigger(sm, msg.source , handle);</span><br><span class="line"><span class="keyword">if</span> (ctx-&gt;cb == <span class="literal">NULL</span>) &#123;</span><br><span class="line">skynet_free(msg.data);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">dispatch_message(ctx, &amp;msg);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">skynet_monitor_trigger(sm, <span class="number">0</span>,<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line">assert(q == ctx-&gt;<span class="built_in">queue</span>);</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> *<span class="title">nq</span> =</span> skynet_globalmq_pop();</span><br><span class="line"><span class="keyword">if</span> (nq) &#123;</span><br><span class="line"><span class="comment">// If global mq is not empty , push q back, and return next queue (nq)</span></span><br><span class="line"><span class="comment">// Else (global mq is empty or block, don&#x27;t push q back, and return q again (for next dispatch)</span></span><br><span class="line">skynet_globalmq_push(q);</span><br><span class="line">q = nq;</span><br><span class="line">&#125; </span><br><span class="line">skynet_context_release(ctx);</span><br><span class="line"><span class="keyword">return</span> q;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结合 <code>strat</code> 和 <code>skynet_context_message_dispatch</code>，我们可以知道 skynet 的消息调度机制的全貌：当 skynet 启动时会初始化线程池，其中线程池内总共包含 4 种线程：<code>monitor</code>、<code>timer</code>、<code>socket</code> 和 <code>worker</code>，其中<code>worker</code> 具有不同的权重值。<strong>每个 <code>worker</code> 会不断从全局消息队列中取出某个服务的次级消息队列，并根据权重值的不同从消息队列中取出若干个消息，然后调用服务所属的 callback 函数消费消息。</strong>权重与取出的消息个数的关系如下：</p><blockquote><p>-1 ：从次级消息队列取出一个消息进行处理<br>0  ：从次级消息队列中取出所有消息进行处理<br>1  ：从次级消息队列中取出一半的消息进行处理<br>2  ：从次级消息队列中取出四分之一的消息进行处理<br>3  ：从次级消息队列中取出八分之一的消息进行处理</p></blockquote><p><strong>这种分配优先级的做法，使得 CPU 的运转效率尽可能的高</strong>。当线程足够多时，如果每次都只处理一个消息，虽然可以避免一些服务饿死，但却可能会使得消息队列中出现大量消息堆积。如果每次都处理一整个消息队列中的消息，则可能会使一些服务中的消息长时间得不到相应，从而导致服务饿死。为线程配置权重的做法是一个非常好的折中方案</p><h6 id="消息生产的过程"><a href="#消息生产的过程" class="headerlink" title="消息生产的过程"></a>消息生产的过程</h6><p>skynet 中不同的服务运行在不同的上下文当中，彼此之间的交互只能通过消息队列进行转发。不同服务之间转发消息的接口为 <code>skynet_send</code> ，其定义如下：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_server.c</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">skynet_send</span><span class="params">(<span class="keyword">struct</span> skynet_context * context, <span class="type">uint32_t</span> source, <span class="type">uint32_t</span> destination , <span class="type">int</span> type, <span class="type">int</span> session, <span class="type">void</span> * data, <span class="type">size_t</span> sz)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> ((sz &amp; MESSAGE_TYPE_MASK) != sz) &#123;</span><br><span class="line">skynet_error(context, <span class="string">&quot;The message to %x is too large&quot;</span>, destination);</span><br><span class="line"><span class="keyword">if</span> (type &amp; PTYPE_TAG_DONTCOPY) &#123;</span><br><span class="line">skynet_free(data);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="number">-2</span>;</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">//_filter_args:根据 type 中的 PTYPE_TAG_DONTCOPY 和 PTYPE_TAG_ALLOCSESSION 位域对参数进行一些相应的处理</span></span><br><span class="line">    <span class="comment">// PTYPE_TAG_DONTCOPY：表示不要拷贝 data 的副本，直接在 data 上进行处理</span></span><br><span class="line">    <span class="comment">// PTYPE_TAG_ALLOCSESSION: 表示为消息分配一个新的 session</span></span><br><span class="line">_filter_args(context, type, &amp;session, (<span class="type">void</span> **)&amp;data, &amp;sz);</span><br><span class="line"><span class="keyword">if</span> (source == <span class="number">0</span>) &#123;</span><br><span class="line">source = context-&gt;handle;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (destination == <span class="number">0</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> (data) &#123;</span><br><span class="line">skynet_error(context, <span class="string">&quot;Destination address can&#x27;t be 0&quot;</span>);</span><br><span class="line">skynet_free(data);</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> session;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (skynet_harbor_message_isremote(destination)) &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">remote_message</span> * <span class="title">rmsg</span> =</span> skynet_malloc(<span class="keyword">sizeof</span>(*rmsg));</span><br><span class="line">rmsg-&gt;destination.handle = destination;</span><br><span class="line">rmsg-&gt;message = data;</span><br><span class="line">rmsg-&gt;sz = sz &amp; MESSAGE_TYPE_MASK;</span><br><span class="line">rmsg-&gt;type = sz &gt;&gt; MESSAGE_TYPE_SHIFT;</span><br><span class="line">skynet_harbor_send(rmsg, source, session);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">struct</span> skynet_message smsg;</span><br><span class="line">smsg.source = source;</span><br><span class="line">smsg.session = session;</span><br><span class="line">smsg.data = data;</span><br><span class="line">smsg.sz = sz;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (skynet_context_push(destination, &amp;smsg)) &#123;</span><br><span class="line">skynet_free(data);</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> session;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中，<code>skynet_send</code> 使用了 <code>source</code> 和 <code>destination</code> 来标记消息的发送端和接收端，这两个参数的本质就是能够在全网范围内唯一标识一个服务的 handle。handle 为一个 32 位无符号整数，其中高 8 位为 harbor id，用来表示服务所属的 skynet 节点，而剩余的 24 位则用于表示该 skynet 内的唯一一个服务。不管最终调用的函数是 <code>skynet_harbor_send</code> 还是 <code>skynet_context_push</code>，最后都会回归到 <code>skynet_mq_push</code> 这个函数中。<strong>因此，skynet 中发送消息的本质就是往目标服务的次级消息队列中压入消息。</strong></p><h4 id="监工机制-——-monitor-线程的工作"><a href="#监工机制-——-monitor-线程的工作" class="headerlink" title="监工机制 —— monitor 线程的工作"></a>监工机制 —— monitor 线程的工作</h4><p>说完了 skynet 消息调度中消息的生产与消费，我们来稍微看一看 monitor 线程(监工) 是如何监管 worker 线程的工作的。在这之前我们先看看 monitor 的定义：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//skynet_start.c</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">monitor</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> count;<span class="comment">//monitor 所监视的 worker 线程的数量</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">skynet_monitor</span> ** <span class="title">m</span>;</span><span class="comment">//存放所有的 skynet_monitor 的数组，worker 和 skynet_monitor 一一对应</span></span><br><span class="line">    <span class="type">pthread_cond_t</span> cond;</span><br><span class="line">    <span class="type">pthread_mutex_t</span> mutex;</span><br><span class="line">    <span class="type">int</span> sleep;<span class="comment">//休眠时间</span></span><br><span class="line">    <span class="type">int</span> quit;<span class="comment">//退出标志</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//skynet_monitor.c</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">skynet_monitor</span> &#123;</span></span><br><span class="line"><span class="type">int</span> version;<span class="comment">//版本号</span></span><br><span class="line"><span class="type">int</span> check_version;<span class="comment">//前一个版本号</span></span><br><span class="line"><span class="type">uint32_t</span> source;<span class="comment">//源地址</span></span><br><span class="line"><span class="type">uint32_t</span> destination;<span class="comment">//目标地址</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如前面所提到的，当 skynet 启动线程池时，第一个创建的线程便是 monitor 线程，它的运行函数如下：</p><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><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"><span class="comment">//skynet_start.c</span></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> *<span class="title function_">thread_monitor</span><span class="params">(<span class="type">void</span> *p)</span> &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">monitor</span> * <span class="title">m</span> =</span> p;</span><br><span class="line"><span class="type">int</span> i;</span><br><span class="line"><span class="type">int</span> n = m-&gt;count;</span><br><span class="line">skynet_initthread(THREAD_MONITOR);</span><br><span class="line"><span class="keyword">for</span> (;;) &#123;</span><br><span class="line"><span class="comment">//CHECK_ABORT : if (G_NODE.total == 0) break;</span></span><br><span class="line">CHECK_ABORT</span><br><span class="line"><span class="title function_">for</span> <span class="params">(i=<span class="number">0</span>;i&lt;n;i++)</span> &#123;</span><br><span class="line">skynet_monitor_check(m-&gt;m[i]);</span><br><span class="line">&#125;</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">CHECK_ABORT</span><br><span class="line"><span class="title function_">sleep</span><span class="params">(<span class="number">1</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//skynet_monitor.c</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_monitor_check</span><span class="params">(<span class="keyword">struct</span> skynet_monitor *sm)</span> &#123;</span><br><span class="line">    <span class="comment">//版本号相同时</span></span><br><span class="line"><span class="keyword">if</span> (sm-&gt;version == sm-&gt;check_version) &#123;</span><br><span class="line">        <span class="comment">//若目标地址不为 0，则 sm 所对应那个 worker 可能陷入了死循环</span></span><br><span class="line"><span class="keyword">if</span> (sm-&gt;destination) &#123;</span><br><span class="line">skynet_context_endless(sm-&gt;destination);</span><br><span class="line">skynet_error(<span class="literal">NULL</span>, <span class="string">&quot;A message from [ :%08x ] to [ :%08x ] maybe in an endless loop (version = %d)&quot;</span>, sm-&gt;source , sm-&gt;destination, sm-&gt;version);</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//版本号不同</span></span><br><span class="line">sm-&gt;check_version = sm-&gt;version;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>monitor 的监管逻辑非常简单，每隔 5 s 便为每个 worker 线程执行一次 <code>skynet_monitor_check</code> 函数。<br>我们再来看看 <code>skynet_monitor_trigger</code> 函数的实现：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// skynet_start.c</span></span><br><span class="line"><span class="keyword">struct</span> message_queue* <span class="title function_">skynet_context_message_dispatch</span><span class="params">(<span class="keyword">struct</span> skynet_monitor *sm, <span class="keyword">struct</span> message_queue *q, <span class="type">int</span> weight)</span> &#123;</span><br><span class="line">...</span><br><span class="line"><span class="type">int</span> overload = skynet_mq_overload(q);</span><br><span class="line"><span class="keyword">if</span> (overload) &#123;</span><br><span class="line">skynet_error(ctx, <span class="string">&quot;May overload, message queue length = %d&quot;</span>, overload);</span><br><span class="line">&#125;</span><br><span class="line">skynet_monitor_trigger(sm, msg.source , handle);</span><br><span class="line"><span class="keyword">if</span> (ctx-&gt;cb == <span class="literal">NULL</span>) &#123;</span><br><span class="line">skynet_free(msg.data);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">dispatch_message(ctx, &amp;msg);</span><br><span class="line">&#125;</span><br><span class="line">    skynet_monitor_trigger(sm, <span class="number">0</span>,<span class="number">0</span>);</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//skynet_monitor.c</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_monitor_trigger</span><span class="params">(<span class="keyword">struct</span> skynet_monitor *sm, <span class="type">uint32_t</span> source, <span class="type">uint32_t</span> destination)</span> &#123;</span><br><span class="line">sm-&gt;source = source;</span><br><span class="line">sm-&gt;destination = destination;</span><br><span class="line">    <span class="comment">//递增 version</span></span><br><span class="line">ATOM_INC(&amp;sm-&gt;version);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上述代码中，我们可以看出 monitor 线程的工作原理。我们来还原一下 monitor 的工作场景：</p><blockquote><ol><li>当一个 worker 线程(记为w)从消息队列中取出一个次级消费队列进行消费。在执行 <code>dispatch_message(ctx, &amp;msg);</code>之前会先调用 <code>skynet_monitor_trigger</code>函数，此时对应的 skynet_monitor(记为w_sm) 有 <code>w_sm-&gt;version = 1</code>， <code>w_sm-&gt;check_version = 0</code> 成立。随后 w 进入了消息消费过程。</li><li>此时 monitor 刚好对 w_sm 执行了 <code>skynet_monitor_check</code>函数，使得有 <code>w_sm-&gt;version == w_sm-&gt;check_version == 1</code> 成立。</li><li>当 w 在消费过程中陷入了死循环并超过第二步 5 s 的时间后，monitor 再一次对 w_sm 执行 <code>skynet_monitor_check</code>函数。这一次 monitor 发现条件  <code>w_sm-&gt;version == w_sm-&gt;check_version</code> 成立，于是向用户返回一条错误日志。</li><li>若 w 在第二步 5 s 的时间内完成了消息消费的过程，则会将 <code>w_sm-&gt;source</code> 和 <code>w_sm-&gt;destination</code> 都设置为 0。 这样即使 monitor 即使检测到 <code>w_sm-&gt;version == w_sm-&gt;check_version</code> 也不会产生错误日志。</li></ol></blockquote><h4 id="如何实现线程安全"><a href="#如何实现线程安全" class="headerlink" title="如何实现线程安全"></a>如何实现线程安全</h4><p>在 skynet 的消息调度机制中，可能涉及到竞态问题的地方主要有往全局消息队列中执行<code>push</code>和<code>pop</code>操作、往次级消息队列中执行 <code>push</code> 和 <code>pop</code> 操作以及消息的消费过程</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> message_queue * <span class="title function_">skynet_globalmq_pop</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">global_queue</span> *<span class="title">q</span> =</span> Q;</span><br><span class="line">SPIN_LOCK(q)</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">message_queue</span> *<span class="title">mq</span> =</span> q-&gt;head;</span><br><span class="line"><span class="keyword">if</span>(mq) &#123;</span><br><span class="line">q-&gt;head = mq-&gt;next;</span><br><span class="line"><span class="keyword">if</span>(q-&gt;head == <span class="literal">NULL</span>) &#123;</span><br><span class="line">assert(mq == q-&gt;tail);</span><br><span class="line">q-&gt;tail = <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line">mq-&gt;next = <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line">SPIN_UNLOCK(q)</span><br><span class="line"><span class="keyword">return</span> mq;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_globalmq_push</span><span class="params">(<span class="keyword">struct</span> message_queue * <span class="built_in">queue</span>)</span> &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">global_queue</span> *<span class="title">q</span>=</span> Q;</span><br><span class="line"></span><br><span class="line">SPIN_LOCK(q)</span><br><span class="line">assert(<span class="built_in">queue</span>-&gt;next == <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">if</span>(q-&gt;tail) &#123;</span><br><span class="line">q-&gt;tail-&gt;next = <span class="built_in">queue</span>;</span><br><span class="line">q-&gt;tail = <span class="built_in">queue</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">q-&gt;head = q-&gt;tail = <span class="built_in">queue</span>;</span><br><span class="line">&#125;</span><br><span class="line">SPIN_UNLOCK(q)</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">void</span> <span class="title function_">skynet_mq_push</span><span class="params">(<span class="keyword">struct</span> message_queue *q, <span class="keyword">struct</span> skynet_message *message)</span> &#123;</span><br><span class="line">assert(message);</span><br><span class="line">SPIN_LOCK(q)</span><br><span class="line">q-&gt;<span class="built_in">queue</span>[q-&gt;tail] = *message;</span><br><span class="line"><span class="keyword">if</span> (++ q-&gt;tail &gt;= q-&gt;cap) &#123;</span><br><span class="line">q-&gt;tail = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (q-&gt;head == q-&gt;tail) &#123;</span><br><span class="line">expand_queue(q);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (q-&gt;in_global == <span class="number">0</span>) &#123;</span><br><span class="line">q-&gt;in_global = MQ_IN_GLOBAL;</span><br><span class="line">skynet_globalmq_push(q);</span><br><span class="line">&#125;</span><br><span class="line">SPIN_UNLOCK(q)</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">skynet_mq_pop</span><span class="params">(<span class="keyword">struct</span> message_queue *q, <span class="keyword">struct</span> skynet_message *message)</span> &#123;</span><br><span class="line"><span class="type">int</span> ret = <span class="number">1</span>;</span><br><span class="line">SPIN_LOCK(q)</span><br><span class="line"><span class="keyword">if</span> (q-&gt;head != q-&gt;tail) &#123;</span><br><span class="line">*message = q-&gt;<span class="built_in">queue</span>[q-&gt;head++];</span><br><span class="line">ret = <span class="number">0</span>;</span><br><span class="line"><span class="type">int</span> head = q-&gt;head;</span><br><span class="line"><span class="type">int</span> tail = q-&gt;tail;</span><br><span class="line"><span class="type">int</span> cap = q-&gt;cap;</span><br><span class="line"><span class="keyword">if</span> (head &gt;= cap) &#123;</span><br><span class="line">q-&gt;head = head = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> length = tail - head;</span><br><span class="line"><span class="keyword">if</span> (length &lt; <span class="number">0</span>) &#123;</span><br><span class="line">length += cap;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">while</span> (length &gt; q-&gt;overload_threshold) &#123;</span><br><span class="line">q-&gt;overload = length;</span><br><span class="line">q-&gt;overload_threshold *= <span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// reset overload_threshold when queue is empty</span></span><br><span class="line">q-&gt;overload_threshold = MQ_OVERLOAD;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (ret) &#123;</span><br><span class="line">q-&gt;in_global = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line">SPIN_UNLOCK(q)</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>skynet 的全局消息队列会被很多的线程访问，而且同一个服务可以同时接收多个不同服务所发送来的信息，因此这两个队列的访问频率都较高，而且对这两个队列的压入和弹出操作都非常快，使用自旋锁回避互斥锁更加经济。服务的 callback 不必是线程安全的，因为每次 worker 都会从全局消息队列中将整个次级消息队列取出，因此其他线程无法同时访问到同一个次级消息队列，自然也就不会面临竞态问题。</p><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><p>[1]. <a href="https://blog.codingnow.com/2012/09/the_design_of_skynet.html">Skynet 设计综述 —— 云风</a><br>[2].<a href="https://manistein.github.io/blog/post/server/skynet/skynet%E6%BA%90%E7%A0%81%E8%B5%8F%E6%9E%90/">skynet源码赏析</a></p>]]></content>
    
    
    <summary type="html">&lt;h4 id=&quot;文前导读&quot;&gt;&lt;a href=&quot;#文前导读&quot; class=&quot;headerlink&quot; title=&quot;文前导读&quot;&gt;&lt;/a&gt;文前导读&lt;/h4&gt;&lt;p&gt;skynet 是一个由云风所写的轻量级在线游戏服务器框架。本文为 skynet 框架源码剖析系列的第一篇文章，主要探讨 skynet 的消息调度机制，包含了以下内容：&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;基本数据结构：包括消息队列以及消息本身的数据结构&lt;/li&gt;
&lt;li&gt;消息调度的过程：主要包括了消息是如何生产，又如何消费的&lt;/li&gt;
&lt;li&gt;多线程模型：skynet 的线程池设计，如何为进程设置合理的优先级使其拥有较高的 CPU 利用率，如何做线程监控设计(monitor) 以及如何高效地实现线程安全。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="skynet源码剖析" scheme="https://phoenix500526.github.io/categories/skynet%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/"/>
    
    
    <category term="多线程" scheme="https://phoenix500526.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
    <category term="skynet框架" scheme="https://phoenix500526.github.io/tags/skynet%E6%A1%86%E6%9E%B6/"/>
    
    <category term="C语言" scheme="https://phoenix500526.github.io/tags/C%E8%AF%AD%E8%A8%80/"/>
    
    <category term="消息队列" scheme="https://phoenix500526.github.io/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/"/>
    
  </entry>
  
  <entry>
    <title>学习的奥义：刻意练习与相关方法</title>
    <link href="https://phoenix500526.github.io/2020/11/04/%E5%AD%A6%E4%B9%A0%E7%9A%84%E5%A5%A5%E4%B9%89%EF%BC%9A%E5%88%BB%E6%84%8F%E7%BB%83%E4%B9%A0%E4%B8%8E%E7%9B%B8%E5%85%B3%E6%96%B9%E6%B3%95/"/>
    <id>https://phoenix500526.github.io/2020/11/04/%E5%AD%A6%E4%B9%A0%E7%9A%84%E5%A5%A5%E4%B9%89%EF%BC%9A%E5%88%BB%E6%84%8F%E7%BB%83%E4%B9%A0%E4%B8%8E%E7%9B%B8%E5%85%B3%E6%96%B9%E6%B3%95/</id>
    <published>2020-11-04T16:16:15.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>人类社会只有发明了发明的方法之后才能发展，人们只有学习了学习的方法之后才能进步。   &#160; &#160; &#160; &#160; &#160; &#160;&#160; &#160; &#160; &#160;&#160; &#160; &#160; &#160;&#160; &#160; &#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160; &#160;&#160;——查理·芒格</p></blockquote><p>&#160; &#160; &#160; &#160;常言道：“一万小时出天才”，然而事实证明，常言未必正确。“一万个小时”这个理论最早是在格拉德威尔的《异类》一书中被提出，并随着这本书的畅销而红遍大江南北。这个理论告诉了人们一个道理：人人都能成为天才，只要你好好练习上一万个小时。可是，对于一个努力上进的年轻人来说，一个片面夸大训练时长而忽略训练方法的理论很可能一点帮助都没有。事实上，这一理论存在两个缺陷：首先，对于不同的行业，想要达到顶尖水平所需要花费的时间是不同，其次，一万个小时的训练指的是刻意练习，与一般练习存在着本质的不同。要想了解刻意练习，我们得先知道我们人类的大脑是怎么掌握一个技能的。</p><span id="more"></span><h4 id="人脑是如何习得技能的？"><a href="#人脑是如何习得技能的？" class="headerlink" title="人脑是如何习得技能的？"></a>人脑是如何习得技能的？</h4><p>&#160; &#160; &#160; &#160;可能看过《黑客帝国》的人会希望有朝一日，人类科技足够发达，能够像电影当中那样，无需经过长时间的学习就能直接将技能下载到人脑当中。不过电影是电影，现实归现实，实际上人脑对技能的习得很可能不是一个软件过程，而是一个……硬件过程。<strong>伦敦大学学院的研究人员们发现，如果大脑想要掌握一项新技能，仅依靠神经元细胞的参与是不够的，还必须依赖于另一种细胞——少突胶质细胞的参与。</strong>研究人员设计了这样一个实验<sup>[1]</sup>，他们准备了两组实验小鼠，其中实验组为正常成年小鼠，对照组则为在成年后去除髓鞘调控因子的小鼠。其中，<strong>髓鞘调控因子负责调控少突胶质前体细胞的分化，使之形成少突胶质细胞，而少突胶质细胞则能够分泌髓磷脂，并包裹在神经细胞轴突上形成髓鞘。</strong>接着，研究人员对小鼠平时运动用的跑轮进行了改造，拆除了其中部分横杆，使其变成了一种间隔不规律的跑轮。小鼠在这种跑轮上奔跑时，由于眼睛看不到后爪前面的“路况”，会时不时一爪踩空。通过让两组小鼠进行长时间的奔跑，并观测两组小鼠的运动表现来判断两组小鼠的学习能力。最终实验结果表明，正常组的小鼠在经历多次后爪踏空后，逐渐学会了新的奔跑方式，即每次都把后爪跨到前爪所踏上的横杆上，这样就能避免后爪踩空，而对照组的小鼠始终无法适应这种新的跑轮，奔跑的速度远远比不上实验组小鼠。之所以会有这样的实验结果，是因为在小鼠学习新的技能时，需要对相应的神经回路进行刺激，而髓鞘的形成正是对这一长期刺激应激性反馈。髓鞘不仅能够对神经元上起到绝缘的作用，使神经元上的电位信号能够更快更好地传导，而且还能形成突起，延伸至临近的轴突并将其包裹起来。想象一下，如果把神经元看成是一条条的导线，那么髓鞘不仅是导线外面的绝缘皮，而且还能把相关的导线捆成电缆，把信号传递的羊肠小道变成高速公路！所有被我们熟练掌握并且运用自如的技能都是“长”在我们脑子里的！<strong>不仅如此，研究人员还发现尽管中枢神经的髓鞘化过程大多发生年少时，但在成年后的相当长一段时间内，个体仍存在大量的少突胶质前体细胞细胞，在不同的情况下分化出少突胶质细胞，形成新的髓鞘，并且这种分化能力会随着年龄的增长而减弱。</strong>这也从一个侧面解释了为什么学习能力会随着年龄的增长而降低。事实上，我认为，未来脑科学发达到能从细胞层面观测到大脑的不同，也许科学家们就能够爱因斯坦的大脑中发现天才之谜。</p><div style="text-align:center"> <img src="/img/learning/oligodendrocyte.webp" width=665 height=390/></div><div class="image-caption" align="center">少突胶质细胞</div> <h4 id="刻意练习与15-87"><a href="#刻意练习与15-87" class="headerlink" title="刻意练习与15.87%"></a>刻意练习与15.87%</h4><p>&#160; &#160; &#160; &#160;如果要问近年什么学习理论最火，那答案一定是刻意练习。我们前面所提到“一万个小时”法则中的一万个小时，最早便是由安德斯·埃里克森在1993关于柏林小提琴学生的研究报告中首次提出。不过，<strong>埃里克森对于格拉德威尔片面强调时间而忽略方法的做法颇有微词，他在《刻意练习》一书当中指出，获得杰出表现的关键不是练习的时间长短，而是练习的方式是否正确，而刻意练习正是检验练习方式是否正确的不二法门。</strong>总的来讲，刻意练习是一套学习理念，而不是一套学习方法，它主要包括了以下四个方面：</p><blockquote><p>①、选取恰当的目标和任务<br>②、对学习内容有针对性地进行分块，对每一小块进行大量的重复练习。<br>③、建立完善的即时反馈机制，一有错误马上纠正<br>④、练习时要求注意力高度集中，全力以赴，并要注意劳逸结合。</p></blockquote><p>&#160; &#160; &#160; &#160;说来也简单，刻意练习的原则就这么四个，要检验你的方法到底科学不科学，对号入座逐一判断便可。然而这个办法对第一条原则可能不那么好使，因为刻意练习要求你适当地脱离舒适区，进入学习区学习。如果把舒适区的内容看成是你已经掌握的旧知识，而把学习区的内容看成是你尚未掌握的新知识，那么新旧知识之间的比例要怎样才能算得上“适当”？对于这个问题，亚利桑那大学和布朗大学的研究者们给出了他们的答案，它有一个数值上的最优解——15.87%。研究者先通过数学模型进行理论计算，接着又利用了一个AI神经网络学习算法和模拟生物大脑的神经网络模型进行了模拟实验，结果得出了这样的一个精确解。现在人工智能的神经网络学习算法学习一项新的技能和我们人类学习过程有点类似，研究者不断地给神经网络输入数据，并让神经网络先做出自己的预判，随后数据再给出反馈。如果预判结果错误，那么调整神经网络的相关参数；如果预判正确，则加深当前已有的参数。如果研究人员“喂食”的数据太过简单，神经网络每次预判都正确，那么这显然不是一个学习的过程，而是一个巩固已有知识的过程；如果“喂食”的数据太难，神经网络每次预判结果都错误，那么就会导致神经网络的参数东变一下，西变一下，神经网络变得无所适从，学习速度肯定快不了。研究者们对比测试了不同的错误率，得出如下的实验结果：</p><div style="text-align:center"> <img src="/img/learning/eighty-five-percent-rule.webp"/></div><div class="image-caption" align="center">错误率与训练次数的关系[图片来源于《The Eighty Five Percent Rules for Optimal Learning》]</div> <p>&#160; &#160; &#160; &#160;其中，图A中的横坐标代表错误率，纵坐标代表训练次数，而色温代表学习效果，颜色越暖，学习效果越好。从图A中我们可以看出，当错误率ER大概为0.16左右的时候，相同训练次数下训练出来的神经网络进行预判的准确度最高。而图B当中，横坐标代表训练次数，纵坐标代表训练的相关准确度，实线代表理论计算的结果，而虚线则表示模拟实验后得出来的实际结果。从图B当中，我们不仅可以看出这个理论结果和实际结果高度接近，而且在错误率ER为0.16时，相同训练次数下训练出来的神经网络相关度提升最快也最高。<strong>这给了我们一个启示，那就是所学知识太容易，你学不快，所学知识太难，你学不好。当你所学知识中，84%为旧知识，16%为新知识的时候，你能学得又快又好。</strong></p><h4 id="学习上的刻意练习"><a href="#学习上的刻意练习" class="headerlink" title="学习上的刻意练习"></a>学习上的刻意练习</h4><p>&#160;&#160;&#160;&#160;正如上面所提到的那样，刻意练习是一种练习理念，更多的是一种心法，而不是一种算法。那么，要怎样利用刻意练习来指导自己的学习呢？对于这个问题，我稍微做了一些调研，目前一些有较为成熟的研究理论的学习方法主要有交错式学习法与间隔学习法。<strong>所谓的交错式学习法，就是指将一段技能或者一种新的知识，分拆成若干个部分，在各个部分之间交错学习的一种方法。对于这种学习方法，有一个关键，那便是所交错的内容之间必须要在某种程度上存在一定的关联。</strong>如果你练习网球，你最好交错式地练习发球，截击，扣杀和步法，而不是交错式地练习发球，花式溜冰，背诵英文单词和用Java语言写代码。<strong>交错式学习法要求学习者克服不同知识之间的相互干扰，更加关注所学知识之间的异同，既有助于促进知识的保持与迁移，又能帮助你将当前知识与其他相关知识相联系，建立起完整的知识网络，是一种经过大量研究证明的有效的学习策略。</strong><br>&#160;&#160;所谓的间隔学习法，则是一种基于间隔效应和考试效应的学习方法<sup>[4]</sup>。<strong>简单来说，在学习的过程中，将知识分成几个部分，每个部分之间间隔一定的时间来学习所获得的学习效果要优于在一大段时间内将知识全部学完的效果，这种现象在心理学上被称之为“间隔效应 (Spacing Effect)”。</strong>以背诵单词为例，相较于一次性背30分钟的单词，将其分成早晚2次，每次各15分钟，你的记忆效果会更好。<strong>另外，有研究指出，人的记忆并不是单独存在的，而是与记忆时的一些情景一起被编码进大脑，因此在背诵的时候多尝试切换一些不同的学习环境会使你的背诵效果更好。而所谓的考试效应(Testing Effect)则是指在学习的过程中，通过考试的形式进行学习的知识往往会容易被编码成为长期记忆，而这也是符合刻意练习的原则，一方面你越是通过回忆运用的方式进行主动学习，那么就越容易记住你所学的知识，另一方面，考试也能为你的学习情况提供及时反馈，帮助你调整自己的学习策略。</strong>因此，结合间隔效应和考试效应，完整的间隔学习法应当是这个样子的：</p><blockquote><p>1、将知识拆分成多个部分，并且将多个部分的学习间隔到一天当中的不同时间段内<br>2、如果有条件，尽可能多尝试切换记忆场景<br>3、每次学习的过程当中，只记关键词，而在学习结束后，通过回忆的方式及时将笔记补充完整<br>4、随时进行测试，一旦出现错误，及时纠正，并将错过的题目添加到未来的试题当中。</p></blockquote><p>&#160;&#160;&#160;&#160;<strong>事实上，分隔式学习对于记忆型的学习任务效果非常好，而交错式学习由于需要关注相关知识之间的异同，对理解型的学习任务效果较好。</strong>我个人认为在交错式学习当中也可以运用考试效应，通过自我测试来驱动学习，强化学习效果。甚至，还可以将分隔式学习和交错式学习结合起来。<strong>毕竟，兵无常势，水无常形，在学习的这个问题上没有通用大法，对应不同的学习任务应当采用适当的学习策略。</strong></p><h4 id="37-背诵指南"><a href="#37-背诵指南" class="headerlink" title="37%背诵指南"></a>37%背诵指南</h4><p>&#160;&#160;&#160;&#160;在讨论这个问题之前，我们先来看一个现实例子，假设你是一个居住在北京的年轻人，你正准备买一所房子。按道理来讲，认真思考，仔细斟酌是一个理性消费者所具备的基本素质，然而北京异常激烈的房地产市场却没有给你留下任何考虑权衡的机会，一所房子你现在不要，转身就被下家给买走了。不仅如此，房地产市场的价格还会随着时间的增长而上涨。在这种情形下，你陷入了一个两难境地，一方面，如果直接上来就买房子，那么有非常高的可能会对这所房子后悔。另一方面，如果你一直处于观望状态，那么你又极有可能会错过让你满意的房子，甚至最后你连不满意的房子都买不起。在这种情况下，你作为一个理性消费者，你应当采取什么策略？显然这两种极端都不可取，比较好的策略应该是这两种策略的折中，先观望一段时间，等到时机成熟，该出手时就出手。那么问题来了，什么时候才算“时机成熟”？现在，数学家告诉我们，这个时机问题不仅是有解的，而且还是有最优解的。<strong>在数学上这被称之为“最优停止(optimal stopping)”问题，它为我们提供了一个指导，即在不收集足量数据就无法做出理性决策，而过多收集数据则会影响决策的情况下，你应当花多少时间来收集信息，花多少时间来进行选择。</strong>具体的做法是，假定你在一年以内必须要把房子买下来，否则房价会涨到你无法接受的地步，那么你应当先花四个半月(12*37%=4.44个月)作为观望期。在这段时间内只是尽可能地看房子而不做决策，一旦观望期结束，接下来看的房子中只要有一个比观望期中最好的房子一样好，甚至还要更好，不要犹豫，立刻将其拿下。<strong>当然，这种方法不能保证你一定拿到最好的房子，但在所有可能的策略当中，这种做法让你找到足够好的房子的概率是最高的。</strong></p><div style="text-align:center"> <img src="/img/learning/housebuying-problem.webp"/></div><div class="image-caption" align="center">买房问题</div> <p>&#160;&#160;&#160;&#160;现在回到我们的背诵问题上，如果你现在要背诵一篇课文，你只有一个小时的时间，你要怎样分配“记”和“忆”的时间？要知道，如果你上来就直接背，一个小时过去你很可能背不了多少，如果花大量的时间记，导致背诵的时间大大缩短，你的背诵效果肯定也大打折扣。在1917年时，哥伦比亚大学的心理学家阿瑟·盖茨就做过一个关于“记”与“忆”在时间分配上的最佳百分比的实验。他招募了一批学生，并要求他们按照事先约定好的不同的时间配比进行背诵。<strong>最终的实验结果表明，对于低年级学生，取得最佳背诵效果的“记”与“忆”的时间配比大约为40%，而对于高年级学生而言，取得最佳最佳背诵效果的“记”与“忆”的时间配比大约为33%<sup>[8]</sup>。</strong>考虑到背诵问题与买房子问题之间虽然有相似性，但还是有些许不同，最佳分割点在33%到40%之间波动还是可以接受的。因此，如果你只有一个小时来背诵课文，你最佳的做法应当是这个样子：</p><blockquote><p>1、先花大约22分钟(60*36.5%=21.9分钟，其中36.5%为33%与40%的平均值)进行熟读，也就是“记”的过程<br>2、一旦过了22分钟，直接脱离文本进行回忆背诵，实在背不下去了就瞄一眼文本，然后接着背。<br>3、一旦你能够把课文完整的背出来，哪怕磕磕绊绊，只要没有遗漏和错误，结束此次背诵。<br>4、间隔一段时间后，通过回忆背诵的方式进行巩固复习</p></blockquote><h4 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h4><p>[1] <a href="https://www.guokr.com/article/439351/">学习新技能，只靠神经元可不够</a><br>[2] <a href="https://doi.org/10.1101/255182%26nbsp">Robert C. Wilson et al., The Eighty-Five Percent Rule for Optimal Learning</a><br>[3] <a href="http://blogs.scientificamerican.com/">Cody Kommers, How Wrong Should You Be?</a>, January 14, 2019.<br>[4]  <a href="https://fs.blog/2018/12/spacing-effect/">The Spacing Effect: How to Improve Learning and Maximize Retention. </a><br>[5] 《刻意练习：如何从新手到大师》 ——    [美] 安德斯·艾利克森（Anders Ericsson） / 罗伯特·普尔（Robert Pool）<br>[6] 《如何学习》——  [美]本迪尼克特·凯里<br>[7] 《科学学习：斯坦福黄金学习法则》  ——  [美]丹尼尔 L. 施瓦茨（Daniel L. Schwartz） / 杰西卡<br>[8] 《算法之美：指导工作与生活的算法》 ——   [美]布莱恩·克里斯汀 / [美]汤姆·格里菲思<br>[9] 这个实验的最终结果被发表在杂志《心理学档案》(Archives of Psychology)上，文章名称为《以背诵为记忆要素》(Recitation as a Factor in Memorizing)，作者为Arthur Gates</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;人类社会只有发明了发明的方法之后才能发展，人们只有学习了学习的方法之后才能进步。   &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160; &amp;#160;&amp;#160; &amp;#160; &amp;#160; &amp;#160;&amp;#160; &amp;#160; &amp;#160; &amp;#160;&amp;#160; &amp;#160; &amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160; &amp;#160;&amp;#160;——查理·芒格&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;#160; &amp;#160; &amp;#160; &amp;#160;常言道：“一万小时出天才”，然而事实证明，常言未必正确。“一万个小时”这个理论最早是在格拉德威尔的《异类》一书中被提出，并随着这本书的畅销而红遍大江南北。这个理论告诉了人们一个道理：人人都能成为天才，只要你好好练习上一万个小时。可是，对于一个努力上进的年轻人来说，一个片面夸大训练时长而忽略训练方法的理论很可能一点帮助都没有。事实上，这一理论存在两个缺陷：首先，对于不同的行业，想要达到顶尖水平所需要花费的时间是不同，其次，一万个小时的训练指的是刻意练习，与一般练习存在着本质的不同。要想了解刻意练习，我们得先知道我们人类的大脑是怎么掌握一个技能的。&lt;/p&gt;</summary>
    
    
    
    <category term="学而时嬉之" scheme="https://phoenix500526.github.io/categories/%E5%AD%A6%E8%80%8C%E6%97%B6%E5%AC%89%E4%B9%8B/"/>
    
    
    <category term="思维与认知" scheme="https://phoenix500526.github.io/tags/%E6%80%9D%E7%BB%B4%E4%B8%8E%E8%AE%A4%E7%9F%A5/"/>
    
    <category term="方法论" scheme="https://phoenix500526.github.io/tags/%E6%96%B9%E6%B3%95%E8%AE%BA/"/>
    
    <category term="主题阅读" scheme="https://phoenix500526.github.io/tags/%E4%B8%BB%E9%A2%98%E9%98%85%E8%AF%BB/"/>
    
    <category term="读书笔记" scheme="https://phoenix500526.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>意志力的科学与科学的意志力</title>
    <link href="https://phoenix500526.github.io/2020/11/04/%E6%84%8F%E5%BF%97%E5%8A%9B%E7%9A%84%E7%A7%91%E5%AD%A6%E4%B8%8E%E7%A7%91%E5%AD%A6%E7%9A%84%E6%84%8F%E5%BF%97%E5%8A%9B/"/>
    <id>https://phoenix500526.github.io/2020/11/04/%E6%84%8F%E5%BF%97%E5%8A%9B%E7%9A%84%E7%A7%91%E5%AD%A6%E4%B8%8E%E7%A7%91%E5%AD%A6%E7%9A%84%E6%84%8F%E5%BF%97%E5%8A%9B/</id>
    <published>2020-11-04T15:47:06.000Z</published>
    <updated>2026-05-27T05:02:59.529Z</updated>
    
    <content type="html"><![CDATA[<p>   当我们谈论到成功的品质时，你最应该想到的是哪个？是中国励志成功学所提倡的好口才与好人脉，还是美国心灵鸡汤式的正面思维，比如特别强调自尊与自信？其实都不是，你最应当想到的是智力和意志力！心理学家通过研究发现，在众多人们所认为的与成功相关的品质当中，最能够预示一个人成功的品质是智力和意志力。虽然到目前为止，科学家们尚未找到能永久提高智力的方法，但幸运的是他们找到了提升意志力的方法，而佛罗里达州立大学的心理学家罗伊 · 鲍迈斯特(Roy Baumeister)和科学记者约翰 · 提尔内(John Tierney)所写的《意志力》正是对这一研究成果通俗有趣的一个介绍。该书借助于各种实验与统计，而非名人轶事，不仅告诉了我们关于意志力的科学，更告诉了我们什么是科学的意志力。</p><div style="text-align:center"> <img src="/img/willpower/willpower-book.webp" width=200 height=250/></div><div class="image-caption" align="center">《意志力：关于自控、专注和效率的心理学》</div><span id="more"></span><h3 id="破除意志力神话"><a href="#破除意志力神话" class="headerlink" title="破除意志力神话"></a>破除意志力神话</h3><p>  意志力是什么？在很多人的心中，意志力是一种形而上的精神力量，可以通过教育、修行来提升。然而，根据实验表明，意志力是存在于人脑当中的一个生理过程，正如同人们使用肌肉那样，意志力的使用也要消耗能量，而且用多了还会导致疲劳。不仅如此，<strong>意志力还是一种通用资源</strong>，不管你是用它来解决学习问题，还是培养某项习惯，归根到底，<strong>你只有一种意志力！</strong>也许你会认为你有很多种意志力，有学习用的意志力，有规范作息的意志力等等，但事实是，如果你因为某些任务消耗了意志力，那么生活中所有方面的意志力都会受到损耗，同样地，如果你通过某种训练提升了意志力，那么生活中所有方面的意志力都会得到改善。这也就完美的解释了，为什么每当临近期末考试，努力学习的学生不仅总是穿脏袜子，还会比平时吃进更多的垃圾食品，为什么自律的人看似从小习惯做起，却往往能将自律覆盖到生活中的方方面面。</p><p>  意志力虽然是人类最伟大的力量，但是意志力并不是万金油，并不能解决所有的问题。<strong>一项研究表明，意志力在工作和学习方面起的效果最大，而在促进情绪适应与人际关系方面效果中等，在节食和饮食方面效果最差。</strong>因此，想要指望靠意志力节食减肥最终只会以失败收场。<strong>真正自律的人是不会事事依赖意志力的，他们靠的是科学方法！将宝贵的意志力资源节省下来，用于对付紧急的情况才是意志力科学的使用方式。</strong></p><h3 id="是什么吸干了我们的意志力"><a href="#是什么吸干了我们的意志力" class="headerlink" title="是什么吸干了我们的意志力"></a>是什么吸干了我们的意志力</h3><div style="text-align:center"> <img src="/img/willpower/willpower-drain.webp" /></div><div class="image-caption" align="center">是什么吸干了我们的意志力</div><p>  正如之前提到的，如果使用意志力要消耗能量，那么当人们处于低血糖状态时是否就缺乏意志力呢？答案还真是如此，<em>科学家在研究葡萄糖与自我控制之间的联系时发现，低血糖患者在与正常人相比时，更难集中注意力，更难控制自己的负面情绪。</em>一项报告指出，在暴力犯罪分子当中，低血糖体征非常普遍，甚至还有律师将其用来为被告辩护。另一项来自芬兰的研究则发现，仅通过对即将刑满释放人员的葡萄糖耐受能力的检测，就能以80%的准确率预测罪犯在出狱后是否会再次暴力犯罪。脑电图可以为我们揭示其中的奥妙，当人们进入到“自我损耗”状态时，即人们对自身思维、行为和感受能力减弱的过程，大脑的伏隔核活动增强，而杏仁核的活动减弱，而葡萄糖则可以逆转这一过程。虽然糖尿病患者的血糖含量很高，但是由于他们身体无法合成胰岛素，因而导致他们无法高效地利用血液当中的葡萄糖，就如同作者所说那样，守着一大堆柴火却找不到一根火柴。想要去图书馆学习时更加高效吗？那就带上几块巧克力。<strong>不过要注意的是，对于一般的代糖而言，由于无法提高血糖浓度，因此无法起到恢复意志力的作用。</strong>另外，在吉姆·洛尔(Jim Loehr)和托尼·施瓦兹(Tony Schwartz)的《精力管理》一书当中也曾提到过，<strong>为了维持身体内稳定的血糖供应，在正餐时应当尽量选取低GI值(即血糖生成指数)、低脂肪和高蛋白的食物，而对于加餐，则应当选用一些快速补充血糖的食物，如巧克力，糖果</strong>等。</p><p>  同样地，过分地使用意志力导致意志力透支，也会使我们在困难面前屈服。<strong>一项实验表明，频繁做决策会消耗一个人意志力，当意志力降低时，人们做出权衡的能力就会下降，开始倾向于最保守或最容易做出的决定。决策疲劳一旦出现，人们更加容易接受推荐值！</strong>汽车经销商深谙此道，在给购买新车的客户推荐新车时，往往不停让顾客先对一些花钱少的配置做出选择，等到顾客陷入了决策疲劳以后，再向顾客兜售一揽子搭配好的推荐配置。除了决策疲劳以外，过分压抑或放纵自己的情绪，也会导致意志力的损耗。在鲍迈斯特领导的一项实验当中，实验人员招募一批志愿者，并分成了三组。在这个实验中，实验人员让被试者看电影，并告知他们他们看电影期间的面部表情将会被摄影机所记录下来。其中第一组被试者被要求在看电影时，努力压抑自己的情感，不表露出任何情绪（面无表情组），第二组则被要求他们在看电影时尽可能的放大自己的表情，这样面部表情就能反应出他们的真实感受（表情夸张组），而第三组则作为对照组，实验人员除了要求他们看电影以外，没有提出任何要求（表情自然组）。实验结束后，再让所有的被试人员参与耐力测验——握力器测试。研究人员通过对比实验数据发现，电影对对照组的耐力没有影响，但是另外两组被试者看电影之后比看电影之前放弃得更早，而且两组的前后差异是相同的，这说明<strong>伪装是有成本的，不管你是压抑自己的情绪，还是放大自己的情绪，都会消耗相同的意志力。</strong></p><p>  显然，<strong>睡眠不足是消耗意志力的一个大敌</strong>。一方面，睡眠不足会导致人们出现困倦的情况，而保持清醒需要高度消耗一个人的意志力。另一方面，睡眠的缺乏还会影响葡萄糖的加工，使得意志力的恢复速度变得更慢。<strong>在压力的情况下，维持抗压的状态也需要消耗大量的意志力。</strong></p><h3 id="意志力应当如何锻炼"><a href="#意志力应当如何锻炼" class="headerlink" title="意志力应当如何锻炼"></a>意志力应当如何锻炼</h3><div style="text-align:center"> <img src="/img/willpower/willpower-training.webp" width=600 height=471/></div><div class="image-caption" align="center">意志力应当如何锻炼</div><p>  既然意志力能够培养，那我们应当怎么培养？在难以坚持的日子里，让自己干下一大碗心灵鸡汤？<strong>科学实验表明，这些方法都没有用，如果你想要提升自己的意志力，唯一的途径就是刻意练习！</strong>在这项实验当中，实验人员以意志力“力量“和”耐力“的变化量为实验标准，对自我损耗状态的能量消耗原因进行了探究，到底是因为需要使用意志力来抑制你这个反应鼓励你那个反应？还是因为需要使用意志力来监控你的行为？抑或是需要意志力来改变你的心情？实验者将被试者分为了三组，并事先测试了他们意志力的基准值，再让他们进行一项快速损耗意志力的任务，随后又再一次测试了意志力损耗及恢复的情况，作为实验的对照数据。其中第一组被试人员被要求回到家后，在接下来的两周内注意保持自己的身姿，每当一想到身姿时，他们就要改变自己懒散的习惯，努力保持身姿挺拔。第二组被试人员则被要求在接下来的两周内，详细地记录自己所吃下的任何食物，用于验证“意志力之所以损耗是因为自我监控的行为需要消耗能量”的理念。第三组被试者则被用来检验情绪控制对意志力的作用，他们被要求在接下来的2周内，尽量保持好的情绪。研究人员认为第三组最有可能成功，因此第三组的样本量是其余两组样本量的两倍。然而最终的实验结果是在三组被试人员中，意志力唯一没有发生变化的正是第三组。<strong>事实上，情绪调节依靠的不是意志力，依靠的往往是一些各种各样的微妙技巧，如改变对眼前问题的看法，或者做其他事情转移注意力。因此，练习情绪控制并不会增强你的意志力。</strong>而且，通过对实验数据的认真分析可以发现，其余两组被试者的意志力测试分数均有提高，而效果最好的来自于被要求保持身姿的那组。通过抑制懒散的习惯，学生增强了意志力，且在与保持身姿毫无关系的任务上表现的更为出色。<strong>这项实验结果还表明，意志力训练能够提升意志力的”耐力“，但是对于意志力的”力量“帮助不大。</strong>在这项实验的第一阶段中，被试者需要先通过握力器测试，测试出基本意志力水平，然后经过经典的”不想白熊“实验损耗心智能量后，再进行一次握力器测试，看意志力损耗后他们能够坚持多久。两周后，回到实验室内，所有被试人员在第一次握力器测试中的成绩没有多少进步，这意味着他们意志力的力量没有发生多大改变，然而在经过”不想白熊“意志损耗实验后，身姿保持组和饮食记录组的第二次握力器测试得分明显提高，这说明他们的意志力”耐力“得到了提升，他们的意志力损耗没有那么快了。</p><p>  保持身姿的挺拔本身并没有任何神奇之处，研究者在后续的研究当中对于其他人群选择其他的策略也可以取得类似的结果，你既可以选择他们研究过的策略，也可以举一反三创造自己的策略，但不管怎样，<strong>你所选择的策略应当符合刻意练习的原则，即每次集中精力改变那么你伸伸手就能够得到的小习惯，同时还要不停地走出你的舒适区。</strong>你不能一上来就干票大的，从戒烟开始，这样十有八九撑不到两周，也不能只做一些太简单的，比如每天坚持喝八杯水。作者提供了一个小策略，那就是……换一只手刷牙，比如你惯用手是右手，那你就换左手刷牙。我个人认为这样的策略没什么意思，一方面，像换只手刷牙的策略，由于不能加码，所以一旦习惯了，就必须换新的习惯。另一方面，仅仅只是换只手刷牙，养成了这样的习惯又能给你的生活带来多大收益？查尔斯·杜希格 (Charles Duhigg)在《习惯的力量》一书中指出了”核心习惯“的概念。<strong>所谓”核心习惯“是指那些能够引起个人生活广泛变化的习惯，它包含着某些能够让其他习惯更容易形成的因素，比如运动、记账等习惯。</strong>从核心习惯(比如运动)去入手，一方面既能锻炼意志力，同时帮助我们培养更多良好的习惯，另一方面，一旦你适应了这种习惯，你甚至不需要找新的习惯就能轻易地走出你的舒适区，你所需要做的不过就是给自己的习惯加码。今天慢跑，那明天开始我就换变速跑；今天跑6公里，明天开始我就跑7公里。只要你愿意，你总能找到方法折腾你自己。</p><h3 id="自控方法论"><a href="#自控方法论" class="headerlink" title="自控方法论"></a><strong>自控方法论</strong></h3><div style="text-align:center"> <img src="/img/willpower/methodology.webp" width=665 height=390/></div><div class="image-caption" align="center">方法论</div> <p>  正如之前所提到的，真正自律的人是不会事事依赖意志力，他们会把宝贵的意志力资源存起来，用在该用的地方，他们靠的是科学的方法。<strong>要实现自控，迈出去的第一步就是设置切合实际的中短期目标。</strong>没有任何目标的自我控制不过是漫无目的改变。人们所烦恼的往往是目标太多，而不是没有目标。太多的目标会让人愁得多，做得少，还影响身心健康。因此，最好采用月计划而非日计划的方式规划你的目标。这是因为使用日计划，很容易塞进很多目标，从而失去重点，而且缺乏灵活性，难以长期坚持，一旦脱离原始轨道，很容易产生积累效应，使人丧失信息。<strong>利用月计划结合OKR(Objective and Key Result)的方法，可以更容易地推进你的计划。</strong>另外，在《习惯的力量》中提到了这样一个实验，被试人员是一群平均年龄在68岁以上，进行了髋关节或膝关节置换手术后，需要进行康复训练的人，他们文化教育程度低，意志力薄弱，且由于康复训练需要忍受异常的疼痛，因此大部分病人，尤其是上了年纪的病人，会违背医嘱，拒绝进行康复训练，最终导致病情的恶化。在这个实验中，实验人员将被试者分为对照组和实验组，对照组不做任何处理，实验组发放记录表，要求被试者按照自己指定的周目标以及详细计划，填写记录表。经过一段时间的康复训练后，测试他们的身体恢复情况。实验结果不出所料，实验组的身体恢复状况远胜于对照组，而且细心的研究者发现，虽然实验组的被试人员所填写的目标和计划五花八门，但有一个关键是所有实验组被试人员都有的，<em>那就是他们都详细地记录了如何应对在特殊时期内发生的疼痛</em>。<strong>这个实验给我们一个提示，要使自己的计划能够坚持下去，就要懂得未谋胜，先虑败，在你的计划中，将所有诱惑点都列举出来，提前想好如何应对，不要等到遇上再来考虑对策。</strong></p><p>   <strong>实现自控的第二步，是监控自我的行为。</strong>监控行为既可以由第三方代劳，也可以由自身来实现，<strong>但关键必须要能够激发你的自我意识。</strong>实验表明，即使在没有任何人监视的情况下，在房间当中放置一面镜子，使人们可以时刻看到自己的样子，也有助于帮助人们约束自身的行为。<strong>同时，好的行为监控还必须包含及时适当的奖惩措施。</strong>你可以将你的目标告诉你的朋友，因为相较于私人信息，公共信息能让人更好的约束自己。毕竟，当你犯错误时，找个借口糊弄自己总比找个借口糊弄大家来得容易。如果万一一不小心破戒了怎么办？《自控力》中提到了，不要因为一时没忍住而感到羞愧或自责，这只会让你更加失控。承认错误，坦然面对，接受惩罚，继续前行才是正确的态度。《自控力》还提到了一个小技巧：要善于把你讨厌但必须做的事情和能让你获得多巴胺奖励的事情放在一起做，比如买一堆刮刮乐彩票，堆在你讨厌的项目旁边。</p><p>  一旦你迈出了自控的前两步，你就会面临一个不可避免的问题：是应当关注已经走了多远？还是应当关注接下来还剩多远？对于这个问题没有简单答案，科学家们为此设计了一个实验来回答这个问题。他们选取了一家韩国广告公司，并将该公司的员工分为两组，一组反思自己自入职以来在自己的工作岗位上所做出的成就，另一组则反思自己在职位上计划取得但尚未取得的成就。最后通过统计他们对工作的满意度和进取心发现，相比之下，前者对公司当前任务和项目的满意度更高，但是后者则有更强的动机去实现目标并接受更具挑战性的任务。<strong>显而易见，要满意，看看走过的路，要上进，看看前方的路。</strong></p><p>  <strong>因此，任何希望通过自控来达成自己目标的人都应当掌握自控的公式：<code>自控=设置目标+监控行为+关注未来</code>。</strong></p><h3 id="身处逆境的哲学"><a href="#身处逆境的哲学" class="headerlink" title="身处逆境的哲学"></a>身处逆境的哲学</h3><div style="text-align:center"> <img src="/img/willpower/henry-morton-stanley.webp" width=406 height=580/></div><div class="image-caption" align="center">不错，我就是亨利·莫顿·斯坦利</div> <p>  上述所说的理论，适用于带有主观意愿个人希望通过提前规划的方式，尽可能的实现自我控制，这属于”怎么做“的理论。但现实中还有一种情况，那就是当我们身处逆境时，你往往需要强大的意志力来摆脱困难。由于飞来横祸往往毫无预兆，因此只知道怎么做还不够，你还得知道怎么熬！亨利·莫顿·斯坦利(Henry Morton Stanley)爵士是19世纪著名的探险家，曾经远征中非，寻找过失踪的英国传教士戴维·利文斯通(David Livingstone)。他于1869年出发，在1871年找到了利文斯通。后来他回国后，将其经历写成了一篇报道，并因此成名。在他的一生当中，曾三次探险非洲，你很难想象在人类历史上，会有哪个探险家会像他那样如此深入蛮荒，经受如此长期的恐惧与痛苦。在探险非洲的过程中，他们曾在茂密的热带雨林中挣扎数月，期间经历了狂风暴雨，穿过了齐腰深的泥沼，不停地同成群结队的苍蝇和蚂蚁斗争。他们常因吃不饱而虚弱不堪，因流脓溃疡而千疮百孔，因疟疾痢疾而无法行动。原住民使用毒箭和长矛攻击他们，使他们或死或残，或者沦为盘中餐。有一段时间，他们当中每天都有几个人会死于疾病和饥饿。最初同斯坦利一同出发，深入非洲最黑暗之地的人们，最后活着走出来的人不到原来的三分之一。然而，斯坦利却度过了所有难关，一次又一次地探险，欧洲和非洲的同伴都惊呼他的意志力，称呼他为”破石者“。<strong>而在现代的心理学研究中，斯坦利之所以能征服蛮荒，既不是靠美德，也不是靠意志，而是因为他十分清楚蛮荒之地的限制，同时运用了现代心理学家所研究的长期战略 —— 培养整洁的生活习惯+寻找信仰。</strong></p><p>  想象一下，如果你是斯坦利，你在一个特别不祥的早晨醒来。你走出了自己的帐篷，外面一片黑暗，仔细算下来，你已经有近4个月没有见到太阳了。在前面几次探险中，你的肚子早已被寄生虫搞坏，时不时出点问题以至于你不得不经常服用奎宁等药物。更糟糕的是，你和你的手下早就开始过着饥不择食的生活，你们吃完了找得到的所有浆果，草根，真菌，蛆，蚂蚁以及鼻涕虫。你开始回想上一次吃的最后一次大餐吃的是你的驴子，饥肠辘辘的人们分食驴子身上所有的部位，甚至为了争抢驴蹄而大打出手，疯狂舔食着地面上尚未干透的血迹。另外，你的队员当中有几十个人因为情况过糟而不得不留在森林中的某个据点等待救援，尽管你明白，将他们留下来最终只会是饿死在这里，而你所能做的不过就是从一个饿死营走到另一个饿死营。不过，在这个早晨，你刚刚醒来，营地里没有食物，但至少你还活着，那么伴随着一声早晨的召唤，你接下来应该干点什么？斯坦利给出了不一样的选择——认真地刮个胡子。不论是身处森林，战场还是饿死营，无论遭遇到多大的困难，斯坦利从未漏掉这件事。斯坦利相信外在的秩序与内在的自律之间存在着联系，而一项实验也证实了这一观点。实验人员将被试人员随机分为两组，让其中一组被试者坐在干净整洁的房间中回答问题，而让另一组坐在一间肮脏杂乱的房间中回答问题。最终实验结果表明，坐在杂乱房间内的被试者在很多自控力测试中得分都比较低，相比另外一组，他们更加关注眼下的利益而忽视未来的利益。因此，对于斯坦利而言，每天刮胡子就成为了一种整洁的线索，一旦养成了习惯，刮胡子甚至几乎不耗费任何意志力。在一项由来自荷兰的德里德和芬肯奥尔领导的实验中，他们通过搜集所有围绕在人格测验自制力得分高的人的行为表现的研究结果并进行了统计分析发现，常规的看法往往认为自律的人常常会将意志力用在控制行为上，但实验结果却表明恰恰相反，<strong>自制力高的人与普通人区别更多地集中在自动行为，也就是习惯上</strong>。<strong>这也就告诉了我们，越是身处逆境，就越不能抛弃生活</strong>，<strong>你需要一点仪式感。未必需要多懂得生活，但至少定期打扫一下卫生，整理一下房间，每天抽一点点时间整理一下自己，还是可以帮助我们度过寒冬的。</strong></p><p>  另外，当人们身处逆境时，往往也是诸多陋习复发的高峰期，而面对这个问题，斯坦利则从通过聚焦高尚的想法来解决这个问题。纽约大学的藤田健太郎和雅各布·特罗普的研究团队设计了一个实验来检验这个策略，他们通过对被试人员提问题来进行思维引导，比如让被试者反思自己为什么要做某件事情，或者反思自己如何做某件事情。“为什么”的问题更加关注长远目标，会诱导人们聚焦未来，而“如何做”的问题则更加关注当下，会诱导人们聚焦现在。不同的实验采用不同的引导方式，最终测试结果都是一致的，被引导到长远目标的被试人员在握力器测试中能够坚持更长的时间，他们也更加倾向于放弃当前的小奖励而选择未来的大奖励。<strong>《习惯的力量》一书中也提到一个有着类似结论的实验，研究人员在研究匿名戒酒会的超自然能量戒酒法时发现，能够将改造过的习惯变成永久性行为的关键要素并不是上帝，而是信仰本身。</strong></p><h3 id="自尊-VS-自制"><a href="#自尊-VS-自制" class="headerlink" title="自尊 VS 自制"></a>自尊 VS 自制</h3><div style="text-align:center"> <img src="/img/willpower/self-esteem.webp" width=480 height=428/></div><div class="image-caption" align="center">自尊</div> <p>  在大多数的学生当中，我们往往很容易发现，成绩好的学生拥有较高的自尊心，而成绩较差的学生则比较容易自卑，再加上心灵鸡汤往往乐意于宣扬，成绩差的学生在不断地受到父母或者良师的鼓励之后，重获自尊与自信，最终逆袭成功的故事，这就造成了我们的一种错觉，自尊是获得好成绩的原因。<strong>然而，通过统计数据发现，十年级的成绩可以预测十二年级的自尊，但是十年级的自尊却无法预测十二年级的成绩，这似乎说明了，成绩是因，而自尊是果。</strong>弗吉尼亚联邦大学的唐纳德·福赛思（Donald Forsyth）则设计了一个更加严谨的实验，他将期末成绩为C或更差的学生随机分成了两组，其中研究人员每周给实验组提供提高自尊的信息，给对照组提供中性信息，并在期末考试统计他们的考试成绩。按理说每周一次的鼓励应该可以提高实验组的自尊心，同时还应当提升他们的考试成绩。然而，实验结果让人大跌眼镜，对照组的平均成绩没有太大变化，而实验组的期末考试平均成绩从原来的59分讲到39分，从差点及格变成差得无可救药。换句话讲，实验组的学生自尊心倒是提高了，成绩反而下降了。他们期中考试时对原来的不及格感到不好意思，而面对期末考试更加糟糕的成绩，他们却自我感觉良好。根据评审小组的说法，高自尊并没有让人变得更有效率，唯一有确凿证据的好处只有两个：1、高自尊能提高人的主动性；2、它让人感觉良好，更加能从失败或抑郁等负面情绪中走出来。提高自尊对提高成绩没有任何帮助。<strong>而在另一项研究中，研究人员通过对大学生三十多项品质的统计发现，在众多品质当中，唯一能决定一个人学习成绩的品质是自控。</strong>换句话讲，要提高孩子的成绩，关键不是鼓励，而是教会孩子自控的能力，让他能管得住自己，该学习学习，该放松放松，少打游戏多看书比什么都强。</p><h3 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读"></a>推荐阅读</h3><p>《意志力》 ——[美]罗伊·鲍迈斯特 / 约翰·蒂尔尼</p><p>《习惯的力量：为什么我们会这样生活，那样工作？》 —— [美] 查尔斯·杜希格 </p><p>《自控力》——[美]凯利·麦格尼格尔</p><p>《精力管理》——[美]吉姆·洛尔/ 托尼·施瓦茨</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;   当我们谈论到成功的品质时，你最应该想到的是哪个？是中国励志成功学所提倡的好口才与好人脉，还是美国心灵鸡汤式的正面思维，比如特别强调自尊与自信？其实都不是，你最应当想到的是智力和意志力！心理学家通过研究发现，在众多人们所认为的与成功相关的品质当中，最能够预示一个人成功的品质是智力和意志力。虽然到目前为止，科学家们尚未找到能永久提高智力的方法，但幸运的是他们找到了提升意志力的方法，而佛罗里达州立大学的心理学家罗伊 · 鲍迈斯特(Roy Baumeister)和科学记者约翰 · 提尔内(John Tierney)所写的《意志力》正是对这一研究成果通俗有趣的一个介绍。该书借助于各种实验与统计，而非名人轶事，不仅告诉了我们关于意志力的科学，更告诉了我们什么是科学的意志力。&lt;/p&gt;
&lt;div style=&quot;text-align:center&quot;&gt;
 &lt;img src=&quot;/img/willpower/willpower-book.webp&quot; width=200 height=250/&gt;
&lt;/div&gt;
&lt;div class=&quot;image-caption&quot; align=&quot;center&quot;&gt;《意志力：关于自控、专注和效率的心理学》&lt;/div&gt;</summary>
    
    
    
    <category term="学而时嬉之" scheme="https://phoenix500526.github.io/categories/%E5%AD%A6%E8%80%8C%E6%97%B6%E5%AC%89%E4%B9%8B/"/>
    
    
    <category term="思维与认知" scheme="https://phoenix500526.github.io/tags/%E6%80%9D%E7%BB%B4%E4%B8%8E%E8%AE%A4%E7%9F%A5/"/>
    
    <category term="主题阅读" scheme="https://phoenix500526.github.io/tags/%E4%B8%BB%E9%A2%98%E9%98%85%E8%AF%BB/"/>
    
    <category term="读书笔记" scheme="https://phoenix500526.github.io/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    
    <category term="意志力科学" scheme="https://phoenix500526.github.io/tags/%E6%84%8F%E5%BF%97%E5%8A%9B%E7%A7%91%E5%AD%A6/"/>
    
  </entry>
  
</feed>
