<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Heary&#39;s Blog</title>
  
  <subtitle>渡口缀满灯花</subtitle>
  <link href="https://heary.cn/atom.xml" rel="self"/>
  
  <link href="https://heary.cn/"/>
  <updated>2022-08-07T04:02:09.584Z</updated>
  <id>https://heary.cn/</id>
  
  <author>
    <name>Heary</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>CentOS yum Error: Failed to download metadata for repo &#39;appstream&#39;问题解决</title>
    <link href="https://heary.cn/posts/CentOS-yum-Error-Failed-to-download-metadata-for-repo-appstream-%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"/>
    <id>https://heary.cn/posts/CentOS-yum-Error-Failed-to-download-metadata-for-repo-appstream-%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/</id>
    <published>2022-03-13T11:03:52.000Z</published>
    <updated>2022-08-07T04:02:09.584Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍CentOS 8在使用<code>yum update</code>更新时，遭遇报错：Error:Failed to download metadata for repo 'appstream': Cannot prepareinternal mirrorlist: No URLs in mirrorlist 的解决方法。</p><span id="more"></span><h1id="centos-yum-error-failed-to-download-metadata-for-repo-appstream问题解决">CentOSyum Error: Failed to download metadata for repo 'appstream'问题解决</h1><h2 id="问题描述">1 问题描述</h2><p>在CentOS8中，执行<code>yum update</code>更新时，遭遇报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Error: Failed to download metadata for repo &#x27;appstream&#x27;: Cannot prepare internal mirrorlist: No URLs in mirrorlist</span><br></pre></td></tr></table></figure><p>根据提示，显示mirror list中找不到可用的URL，导致无法获取appstreamrepo的元信息。</p><h2 id="解决方法">2 解决方法</h2><h3 id="相关资料中的解决方法">2.1 相关资料中的解决方法</h3><p>我查到的资料中，已有的解决方案是通过sed工具批量查询和替换<code>/etc/yum.repos.d/</code>中的软件仓库配置信息。</p><blockquote><p>引用自：https://www.cnblogs.com/EthanWong/p/15932675.html</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 进入yum.repos.d 目录下</span></span><br><span class="line"><span class="built_in">cd</span> /etc/yum.repos.d/</span><br><span class="line"><span class="comment"># 修改源链接</span></span><br><span class="line">sed -i <span class="string">&#x27;s/mirrorlist/#mirrorlist/g&#x27;</span> /etc/yum.repos.d/CentOS-*</span><br><span class="line"><span class="comment"># 要将之前的mirror.centos.org 改成 vault.centos.org</span></span><br><span class="line">sed -i <span class="string">&#x27;s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g&#x27;</span> /etc/yum.repos.d/CentOS-*</span><br></pre></td></tr></table></figure></blockquote><p>上述文章指出，因为CentOs Linux 8 从 2021.10.31号后已经停止维护，所以之后更新镜像需要通过<code>vault.centos.org</code>来获取更新。相应的，文中给出的方案就是将<code>/etc/yum.repos.d/</code>中，各个软件仓库配置文件中的<code>mirrorlist</code>字段都注释掉，不采用镜像列表，而是启用<code>baseurl</code>去连接源站，且原站修改为<code>http://vault.centos.org</code>。</p><h3 id="本文的解决方法">2.2 本文的解决方法</h3><p>我认为上述修改软件配置的方案也许可行，但问题的实质是因为：RHEL修改了CentOS的开源方案，将CentOS改为CentOSStream的形式进行后续迭代。过去的CentOS与RHEL共享核心代码，只是RHEL额外具备一些增值软件和服务。目前的CentOSStream将作为RHEL的“开发版”，即：开发的新代码先发布到CentOSStream上进行验证，再合入RHEL。通过Fedora Linux, CentOSStream这依次两道试验阶段，再合入RHEL，以保障RHEL的高可靠。</p><blockquote><p><strong>CentOS Stream</strong></p><p>Continuously delivered distro that tracks just ahead of Red HatEnterprise Linux (RHEL) development, positioned as a midstream betweenFedora Linux and RHEL. For anyone interested in participating andcollaborating in the RHEL ecosystem, CentOS Stream is your reliableplatform for innovation.</p></blockquote><p>CentOS Linux 8因为这项变更，EOL被改为2021年底，因此2022年开始，CentOSLinux 8的mirrorlist下架，代码停止维护。</p><p>因此，该问题解决方法的最佳实践应当是根据官方文档，将已经EOL的CentOSLinux 8迁移到CentOS Stream8的发行分支上。（或采用其他Linux，如：Debian）。</p><blockquote><p>https://www.centos.org/centos-stream/</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">dnf --disablerepo <span class="string">&#x27;*&#x27;</span> --enablerepo extras swap centos-linux-repos centos-stream-repos</span><br><span class="line">dnf distro-sync</span><br></pre></td></tr></table></figure></blockquote><p>dnf是CentOS8默认的包管理器。按照上述命令，通过dnf重新配置软件repo并同步数据，即可迁移到CentOSStream发行分支上。</p><blockquote><p>DNF stands for <strong>Dandified YUM</strong> is a software packagemanager for RPM-based Linux distributions. It is used to install, updateand remove packages in the CentOS operating system. It is the defaultpackage manager of CentOS8.</p></blockquote><p>迁移后的CentOS Stream 8的EOL为May 31st, 2024。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;本文介绍CentOS 8在使用&lt;code&gt;yum update&lt;/code&gt;更新时，遭遇报错：Error:
Failed to download metadata for repo &#39;appstream&#39;: Cannot prepare
internal mirrorlist: No URLs in mirrorlist 的解决方法。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Troubleshooting" scheme="https://heary.cn/tags/Troubleshooting/"/>
    
    <category term="CentOS" scheme="https://heary.cn/tags/CentOS/"/>
    
    <category term="Linux" scheme="https://heary.cn/tags/Linux/"/>
    
    <category term="yum" scheme="https://heary.cn/tags/yum/"/>
    
    <category term="dnf" scheme="https://heary.cn/tags/dnf/"/>
    
  </entry>
  
  <entry>
    <title>slice.go - 理解Go的切片容器</title>
    <link href="https://heary.cn/posts/slice-go-%E7%90%86%E8%A7%A3Go%E7%9A%84%E5%88%87%E7%89%87%E5%AE%B9%E5%99%A8/"/>
    <id>https://heary.cn/posts/slice-go-%E7%90%86%E8%A7%A3Go%E7%9A%84%E5%88%87%E7%89%87%E5%AE%B9%E5%99%A8/</id>
    <published>2021-11-01T14:44:43.000Z</published>
    <updated>2022-08-07T04:02:09.871Z</updated>
    
    <content type="html"><![CDATA[<p>阅读Go源码，理解内置切片（slice）容器的数据结构与算法原理。</p><span id="more"></span><h1 id="slice.go---理解go的切片容器">slice.go - 理解Go的切片容器</h1><p>Slice的实现位于go.go，总共仅318行。</p><p>本文以目前Go源码最新的1.17.2版本为例。</p><h2 id="数据结构">数据结构</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> slice <span class="keyword">struct</span> &#123;</span><br><span class="line">array unsafe.Pointer</span><br><span class="line"><span class="built_in">len</span>   <span class="type">int</span></span><br><span class="line"><span class="built_in">cap</span>   <span class="type">int</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>slice的数据结构并不复杂，本质上是对array的一层封装，类似Java中的ArrayList。</p><p>slice底层数据由array存储，由len标记当前实际存储的元素数量，cap标记当前array指针指向的内存对象的元素容量。</p><h2 id="算法">算法</h2><h3 id="构造makeslice">构造（makeslice）</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">makeslice</span><span class="params">(et *_type, <span class="built_in">len</span>, <span class="built_in">cap</span> <span class="type">int</span>)</span></span> unsafe.Pointer &#123;</span><br><span class="line">mem, overflow := math.MulUintptr(et.size, <span class="type">uintptr</span>(<span class="built_in">cap</span>))</span><br><span class="line"><span class="keyword">if</span> overflow || mem &gt; maxAlloc || <span class="built_in">len</span> &lt; <span class="number">0</span> || <span class="built_in">len</span> &gt; <span class="built_in">cap</span> &#123;</span><br><span class="line"><span class="comment">// <span class="doctag">NOTE:</span> Produce a &#x27;len out of range&#x27; error instead of a</span></span><br><span class="line"><span class="comment">// &#x27;cap out of range&#x27; error when someone does make([]T, bignumber).</span></span><br><span class="line"><span class="comment">// &#x27;cap out of range&#x27; is true too, but since the cap is only being</span></span><br><span class="line"><span class="comment">// supplied implicitly, saying len is clearer.</span></span><br><span class="line"><span class="comment">// See golang.org/issue/4085.</span></span><br><span class="line">mem, overflow := math.MulUintptr(et.size, <span class="type">uintptr</span>(<span class="built_in">len</span>))</span><br><span class="line"><span class="keyword">if</span> overflow || mem &gt; maxAlloc || <span class="built_in">len</span> &lt; <span class="number">0</span> &#123;</span><br><span class="line">panicmakeslicelen()</span><br><span class="line">&#125;</span><br><span class="line">panicmakeslicecap()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> mallocgc(mem, et, <span class="literal">true</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>构造过程输入et，即ElementType的缩写，用于记录slice中存储的元素类型、</p><p>首先，通过<code>math.MulUintptr</code>函数实现带溢出检测的uintptr类型乘法。</p><blockquote><p>https://pkg.go.dev/runtime/internal/math#MulUintptr</p><p>https://cs.opensource.google/go/go/+/go1.17.2:src/runtime/internal/math/math.go;l=13</p><p>math.MulUintptr函数的实现挺巧妙的，此处暂不深究</p></blockquote><p>随后，根据计算出的内存长度，通过mallocgc函数（位于go.go中，基于TCMalloc机制实现）分配相应的内存对象。</p><h3 id="扩容growslice">扩容（growslice）</h3><p>slice能够在append时自动扩容。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// growslice handles slice growth during append.</span></span><br><span class="line"><span class="comment">// It is passed the slice element type, the old slice, and the desired new minimum capacity,</span></span><br><span class="line"><span class="comment">// and it returns a new slice with at least that capacity, with the old data</span></span><br><span class="line"><span class="comment">// copied into it.</span></span><br><span class="line"><span class="comment">// The new slice&#x27;s length is set to the old slice&#x27;s length,</span></span><br><span class="line"><span class="comment">// NOT to the new requested capacity.</span></span><br><span class="line"><span class="comment">// This is for codegen convenience. The old slice&#x27;s length is used immediately</span></span><br><span class="line"><span class="comment">// to calculate where to write new values during an append.</span></span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> When the old backend is gone, reconsider this decision.</span></span><br><span class="line"><span class="comment">// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">growslice</span><span class="params">(et *_type, old slice, <span class="built_in">cap</span> <span class="type">int</span>)</span></span> slice &#123;</span><br><span class="line"><span class="keyword">if</span> raceenabled &#123;</span><br><span class="line">callerpc := getcallerpc()</span><br><span class="line">racereadrangepc(old.array, <span class="type">uintptr</span>(old.<span class="built_in">len</span>*<span class="type">int</span>(et.size)), callerpc, funcPC(growslice))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> msanenabled &#123;</span><br><span class="line">msanread(old.array, <span class="type">uintptr</span>(old.<span class="built_in">len</span>*<span class="type">int</span>(et.size)))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">cap</span> &lt; old.<span class="built_in">cap</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(errorString(<span class="string">&quot;growslice: cap out of range&quot;</span>))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> et.size == <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// append should not create a slice with nil pointer but non-zero len.</span></span><br><span class="line"><span class="comment">// We assume that append doesn&#x27;t need to preserve old.array in this case.</span></span><br><span class="line"><span class="keyword">return</span> slice&#123;unsafe.Pointer(&amp;zerobase), old.<span class="built_in">len</span>, <span class="built_in">cap</span>&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">newcap := old.<span class="built_in">cap</span></span><br><span class="line">doublecap := newcap + newcap</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">cap</span> &gt; doublecap &#123;</span><br><span class="line">newcap = <span class="built_in">cap</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">if</span> old.<span class="built_in">cap</span> &lt; <span class="number">1024</span> &#123;</span><br><span class="line">newcap = doublecap</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// Check 0 &lt; newcap to detect overflow</span></span><br><span class="line"><span class="comment">// and prevent an infinite loop.</span></span><br><span class="line"><span class="keyword">for</span> <span class="number">0</span> &lt; newcap &amp;&amp; newcap &lt; <span class="built_in">cap</span> &#123;</span><br><span class="line">newcap += newcap / <span class="number">4</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Set newcap to the requested cap when</span></span><br><span class="line"><span class="comment">// the newcap calculation overflowed.</span></span><br><span class="line"><span class="keyword">if</span> newcap &lt;= <span class="number">0</span> &#123;</span><br><span class="line">newcap = <span class="built_in">cap</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="keyword">var</span> overflow <span class="type">bool</span></span><br><span class="line"><span class="keyword">var</span> lenmem, newlenmem, capmem <span class="type">uintptr</span></span><br><span class="line"><span class="comment">// Specialize for common values of et.size.</span></span><br><span class="line"><span class="comment">// For 1 we don&#x27;t need any division/multiplication.</span></span><br><span class="line"><span class="comment">// For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant.</span></span><br><span class="line"><span class="comment">// For powers of 2, use a variable shift.</span></span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> et.size == <span class="number">1</span>:</span><br><span class="line">lenmem = <span class="type">uintptr</span>(old.<span class="built_in">len</span>)</span><br><span class="line">newlenmem = <span class="type">uintptr</span>(<span class="built_in">cap</span>)</span><br><span class="line">capmem = roundupsize(<span class="type">uintptr</span>(newcap))</span><br><span class="line">overflow = <span class="type">uintptr</span>(newcap) &gt; maxAlloc</span><br><span class="line">newcap = <span class="type">int</span>(capmem)</span><br><span class="line"><span class="keyword">case</span> et.size == sys.PtrSize:</span><br><span class="line">lenmem = <span class="type">uintptr</span>(old.<span class="built_in">len</span>) * sys.PtrSize</span><br><span class="line">newlenmem = <span class="type">uintptr</span>(<span class="built_in">cap</span>) * sys.PtrSize</span><br><span class="line">capmem = roundupsize(<span class="type">uintptr</span>(newcap) * sys.PtrSize)</span><br><span class="line">overflow = <span class="type">uintptr</span>(newcap) &gt; maxAlloc/sys.PtrSize</span><br><span class="line">newcap = <span class="type">int</span>(capmem / sys.PtrSize)</span><br><span class="line"><span class="keyword">case</span> isPowerOfTwo(et.size):</span><br><span class="line"><span class="keyword">var</span> shift <span class="type">uintptr</span></span><br><span class="line"><span class="keyword">if</span> sys.PtrSize == <span class="number">8</span> &#123;</span><br><span class="line"><span class="comment">// Mask shift for better code generation.</span></span><br><span class="line">shift = <span class="type">uintptr</span>(sys.Ctz64(<span class="type">uint64</span>(et.size))) &amp; <span class="number">63</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">shift = <span class="type">uintptr</span>(sys.Ctz32(<span class="type">uint32</span>(et.size))) &amp; <span class="number">31</span></span><br><span class="line">&#125;</span><br><span class="line">lenmem = <span class="type">uintptr</span>(old.<span class="built_in">len</span>) &lt;&lt; shift</span><br><span class="line">newlenmem = <span class="type">uintptr</span>(<span class="built_in">cap</span>) &lt;&lt; shift</span><br><span class="line">capmem = roundupsize(<span class="type">uintptr</span>(newcap) &lt;&lt; shift)</span><br><span class="line">overflow = <span class="type">uintptr</span>(newcap) &gt; (maxAlloc &gt;&gt; shift)</span><br><span class="line">newcap = <span class="type">int</span>(capmem &gt;&gt; shift)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">lenmem = <span class="type">uintptr</span>(old.<span class="built_in">len</span>) * et.size</span><br><span class="line">newlenmem = <span class="type">uintptr</span>(<span class="built_in">cap</span>) * et.size</span><br><span class="line">capmem, overflow = math.MulUintptr(et.size, <span class="type">uintptr</span>(newcap))</span><br><span class="line">capmem = roundupsize(capmem)</span><br><span class="line">newcap = <span class="type">int</span>(capmem / et.size)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// The check of overflow in addition to capmem &gt; maxAlloc is needed</span></span><br><span class="line"><span class="comment">// to prevent an overflow which can be used to trigger a segfault</span></span><br><span class="line"><span class="comment">// on 32bit architectures with this example program:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// type T [1&lt;&lt;27 + 1]int64</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// var d T</span></span><br><span class="line"><span class="comment">// var s []T</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// func main() &#123;</span></span><br><span class="line"><span class="comment">//   s = append(s, d, d, d, d)</span></span><br><span class="line"><span class="comment">//   print(len(s), &quot;\n&quot;)</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"><span class="keyword">if</span> overflow || capmem &gt; maxAlloc &#123;</span><br><span class="line"><span class="built_in">panic</span>(errorString(<span class="string">&quot;growslice: cap out of range&quot;</span>))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> p unsafe.Pointer</span><br><span class="line"><span class="keyword">if</span> et.ptrdata == <span class="number">0</span> &#123;</span><br><span class="line">p = mallocgc(capmem, <span class="literal">nil</span>, <span class="literal">false</span>)</span><br><span class="line"><span class="comment">// The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).</span></span><br><span class="line"><span class="comment">// Only clear the part that will not be overwritten.</span></span><br><span class="line">memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// Note: can&#x27;t use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.</span></span><br><span class="line">p = mallocgc(capmem, et, <span class="literal">true</span>)</span><br><span class="line"><span class="keyword">if</span> lenmem &gt; <span class="number">0</span> &amp;&amp; writeBarrier.enabled &#123;</span><br><span class="line"><span class="comment">// Only shade the pointers in old.array since we know the destination slice p</span></span><br><span class="line"><span class="comment">// only contains nil pointers because it has been cleared during alloc.</span></span><br><span class="line">bulkBarrierPreWriteSrcOnly(<span class="type">uintptr</span>(p), <span class="type">uintptr</span>(old.array), lenmem-et.size+et.ptrdata)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">memmove(p, old.array, lenmem)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> slice&#123;p, old.<span class="built_in">len</span>, newcap&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在扩容时，如果新容量已经超过现有容量的两倍，则以更大的新容量为准。</p><p>如果指定的新容量不足两倍，则分两种情况：</p><ol type="1"><li>如果现有容量较小（&lt;1024），那就直接容量翻倍2x（直接成倍增长的策略有助于避免频繁扩容，而容量较小时，即使有空间冗余浪费，也是比较少的）；</li><li>如果现有容量不小了（&gt;=1024），此时翻倍式扩容可能会浪费较多的内存，因此以1.25x渐进式增长至不低于目标容量，既满足目标容量，又避免浪费内存。</li></ol><blockquote><p><ahref="https://github.com/golang/go/commit/2dda92ff6f9f07eeb110ecbf0fc2d7a0ddd27f9d#diff-fc52a9434e8f6cb1b87de5e565399f0d3e5efb448408f2e2e0ea3ea12de60550">runtime:make slice growth formula a bit smoother</a></p><p>不过值得注意的是，这样的扩容算法未必是最优的，仍然存在改进的研究空间。从master分支上最新commit中可以看到，新的commit正在尝试更平滑的扩容函数（及参数）。高的增长倍率，一方面有助于避免频繁扩容（避免分配内存时潜在的系统调用代价），另一方面也更容易造成内存冗余。</p></blockquote><p>此后，计算新slice的array所需的内存容量capmen和相应的元素容量newcap。（该计算过程针对元素尺寸做了优化）</p><p>最后，通过mallocgc函数申请capmem尺寸的内存对象，并且用memmove函数将原slice数据拷贝到新slice的内存（指针p）中。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;阅读Go源码，理解内置切片（slice）容器的数据结构与算法原理。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Golang" scheme="https://heary.cn/tags/Golang/"/>
    
    <category term="Slice" scheme="https://heary.cn/tags/Slice/"/>
    
  </entry>
  
  <entry>
    <title>从源码理解Gin框架原理</title>
    <link href="https://heary.cn/posts/%E4%BB%8E%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Gin%E6%A1%86%E6%9E%B6%E5%8E%9F%E7%90%86/"/>
    <id>https://heary.cn/posts/%E4%BB%8E%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Gin%E6%A1%86%E6%9E%B6%E5%8E%9F%E7%90%86/</id>
    <published>2021-08-22T13:04:24.000Z</published>
    <updated>2022-08-07T04:02:09.877Z</updated>
    
    <content type="html"><![CDATA[<p><ahref="https://github.com/gin-gonic/gin">Gin</a>是一款高性能的Go语言Web框架，本文以一个小型示例项目为例，从源码解读Gin的服务启动过程、请求与响应过程的技术原理。</p><span id="more"></span><h1 id="从源码理解gin框架原理">从源码理解Gin框架原理</h1><h2 id="概述">1 概述</h2><blockquote><p><strong><a href="https://github.com/gin-gonic/gin">Gin WebFramework</a></strong></p><p>Gin is a web framework written in Go (Golang). It features amartini-like API with performance that is up to 40 times faster thanksto <a href="https://github.com/julienschmidt/httprouter">httprouter</a>.If you need performance and good productivity, you will love Gin.</p></blockquote><p><ahref="https://github.com/gin-gonic/gin">Gin</a>是一款高性能的Go语言Web框架。</p><blockquote><p><ahref="https://github.com/HearyShen/LearnGin"><strong>LearnGin</strong></a></p></blockquote><p>LearnGin仓库存储本文的示例代码。</p><p>本文所使用的软件版本是：</p><ul><li>Gin的版本是：<strong>gin@v1.7.4</strong>；</li><li>Go的版本是：1.17。</li></ul><h2 id="技术原理">2 技术原理</h2><h3 id="gin的启动过程">2.1 Gin的启动过程</h3><h4 id="项目的main函数">2.1.1 项目的main函数</h4><p>主函数位于项目根目录下的main.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;github.com/LearnGin/handler&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/LearnGin/middleware&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// init gin with default configs</span></span><br><span class="line">r := gin.Default()</span><br><span class="line"></span><br><span class="line"><span class="comment">// append custom middle-wares</span></span><br><span class="line">middleware.RegisterMiddleware(r)</span><br><span class="line"><span class="comment">// register custom routers</span></span><br><span class="line">handler.RegisterHandler(r)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run the engine</span></span><br><span class="line">r.Run()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>主要步骤：</p><ol type="1"><li><strong>初始化Gin</strong>：<code>gin.Default()</code>执行Gin的初始化过程，默认的初始化包含两个中间件，<ol type="1"><li><strong>Logger</strong>：日志中间件，将Gin的启动与响应日志输出到控制台；</li><li><strong>Recovery</strong>：恢复中间件，将Gin遇到的无法处理的请求按HTTP500状态码返回。</li></ol></li><li><strong>注册中间件</strong>：本例的<code>middleware.RegisterMiddleware(r)</code>用于将项目中开发的中间件注册到GinEngine上；</li><li><strong>注册事件处理</strong>：本例的<code>handler.RegisterHandler(r)</code>用于将项目中开发的对应于指定URL的事件处理函数注册到GinEngine上；</li><li><strong>启动Gin</strong>：<code>r.Run()</code>负责启动GinEngine，开始监听请求并提供HTTP服务。</li></ol><h4 id="初始化gin">2.1.2 初始化Gin</h4><h5 id="gin的default函数">gin的Default函数</h5><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Default returns an Engine instance with the Logger and Recovery middleware already attached.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Default</span><span class="params">()</span></span> *Engine &#123;</span><br><span class="line">debugPrintWARNINGDefault()</span><br><span class="line">engine := New()</span><br><span class="line">engine.Use(Logger(), Recovery())</span><br><span class="line"><span class="keyword">return</span> engine</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Gin的默认初始化主要是创建Engine和注册默认的两款中间件。</p><h4 id="注册中间件">2.1.3 注册中间件</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> middleware</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;github.com/LearnGin/middleware/debug&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RegisterMiddleware</span><span class="params">(r *gin.Engine)</span></span> &#123;</span><br><span class="line">r.Use(debug.DebugMiddleWare())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>gin.Engine的<code>r.Use</code>函数负责将gin.HandleFunc类型函数注册为中间件。此处的<code>debug.DebugMiddleWare()</code>是本例开发的一个简易的自定义中间件，用于在实际的事件处理前，输出详细的请求信息；在实际的事件处理后，输出结果状态码。</p><h5 id="engine.use函数">Engine.Use函数</h5><p>Engine.Use函数用于将中间件添加到当前的路由上，位于gin.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be</span></span><br><span class="line"><span class="comment">// included in the handlers chain for every single request. Even 404, 405, static files...</span></span><br><span class="line"><span class="comment">// For example, this is the right place for a logger or error management middleware.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(engine *Engine)</span></span> Use(middleware ...HandlerFunc) IRoutes &#123;</span><br><span class="line">engine.RouterGroup.Use(middleware...)</span><br><span class="line">engine.rebuild404Handlers()</span><br><span class="line">engine.rebuild405Handlers()</span><br><span class="line"><span class="keyword">return</span> engine</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="routergroup.use函数">RouterGroup.Use函数</h5><p>实际上，还需要进一步调用<code>engine.RouterGroup.Use(middleware...)</code>完成实际的中间件注册工作，该函数位于gin.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Use adds middleware to the group, see example code in GitHub.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(group *RouterGroup)</span></span> Use(middleware ...HandlerFunc) IRoutes &#123;</span><br><span class="line">group.Handlers = <span class="built_in">append</span>(group.Handlers, middleware...)</span><br><span class="line"><span class="keyword">return</span> group.returnObj()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该函数也很简短，实际上就是把中间件（本质是一个函数）添加到HandlersChain类型（实质上为数组<code>type HandlersChain []HandlerFunc</code>）的group.Handlers中。换句话说，实际上是以<strong>函数数组</strong>的形式收集了一个有序的函数序列。</p><p>此后会介绍中间件中每次都会出现的<code>c.Next()</code>函数如何基于该数组进行流程控制。</p><h4 id="注册事件处理">2.1.4 注册事件处理</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> handler</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;github.com/LearnGin/handler/person&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">RegisterHandler</span><span class="params">(r *gin.Engine)</span></span> &#123;</span><br><span class="line">r.Handle(<span class="string">&quot;GET&quot;</span>, <span class="string">&quot;/ping&quot;</span>, PingHandler())</span><br><span class="line">r.Handle(<span class="string">&quot;POST&quot;</span>, <span class="string">&quot;/person/create&quot;</span>, person.CreatePersonHandler())</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>gin.Engine的<code>r.Handle</code>函数用于将事件处理函数注册到指定的HTTP方法+相对路径上。</p><h5 id="routergroup.handle函数">RouterGroup.Handle函数</h5><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Handle registers a new request handle and middleware with the given path and method.</span></span><br><span class="line"><span class="comment">// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.</span></span><br><span class="line"><span class="comment">// See the example code in GitHub.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut</span></span><br><span class="line"><span class="comment">// functions can be used.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// This function is intended for bulk loading and to allow the usage of less</span></span><br><span class="line"><span class="comment">// frequently used, non-standardized or custom methods (e.g. for internal</span></span><br><span class="line"><span class="comment">// communication with a proxy).</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(group *RouterGroup)</span></span> Handle(httpMethod, relativePath <span class="type">string</span>, handlers ...HandlerFunc) IRoutes &#123;</span><br><span class="line"><span class="keyword">if</span> matches, err := regexp.MatchString(<span class="string">&quot;^[A-Z]+$&quot;</span>, httpMethod); !matches || err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">&quot;http method &quot;</span> + httpMethod + <span class="string">&quot; is not valid&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> group.handle(httpMethod, relativePath, handlers)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>GinEngine的Handle函数调用实际上调用的是内部匿名属性RouterGroup的Handle函数。该函数的逻辑由handle函数进一步处理，代码为：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(group *RouterGroup)</span></span> handle(httpMethod, relativePath <span class="type">string</span>, handlers HandlersChain) IRoutes &#123;</span><br><span class="line">absolutePath := group.calculateAbsolutePath(relativePath)</span><br><span class="line">handlers = group.combineHandlers(handlers)</span><br><span class="line">group.engine.addRoute(httpMethod, absolutePath, handlers)</span><br><span class="line"><span class="keyword">return</span> group.returnObj()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，实际上handler是由<code>group.engine.addRoute(httpMethod, absolutePath, handlers)</code>注册路由的。</p><h5 id="engine.addroute函数">Engine.addRoute函数</h5><p>如果持续追查下去，会发现addRoute函数实际上是将该方法添加到当前HTTP方法对应的那颗路由树中。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(engine *Engine)</span></span> addRoute(method, path <span class="type">string</span>, handlers HandlersChain) &#123;</span><br><span class="line">assert1(path[<span class="number">0</span>] == <span class="string">&#x27;/&#x27;</span>, <span class="string">&quot;path must begin with &#x27;/&#x27;&quot;</span>)</span><br><span class="line">assert1(method != <span class="string">&quot;&quot;</span>, <span class="string">&quot;HTTP method can not be empty&quot;</span>)</span><br><span class="line">assert1(<span class="built_in">len</span>(handlers) &gt; <span class="number">0</span>, <span class="string">&quot;there must be at least one handler&quot;</span>)</span><br><span class="line"></span><br><span class="line">debugPrintRoute(method, path, handlers)</span><br><span class="line"></span><br><span class="line">root := engine.trees.get(method)</span><br><span class="line"><span class="keyword">if</span> root == <span class="literal">nil</span> &#123;</span><br><span class="line">root = <span class="built_in">new</span>(node)</span><br><span class="line">root.fullPath = <span class="string">&quot;/&quot;</span></span><br><span class="line">engine.trees = <span class="built_in">append</span>(engine.trees, methodTree&#123;method: method, root: root&#125;)</span><br><span class="line">&#125;</span><br><span class="line">root.addRoute(path, handlers)</span><br><span class="line"></span><br><span class="line"><span class="comment">// Update maxParams</span></span><br><span class="line"><span class="keyword">if</span> paramsCount := countParams(path); paramsCount &gt; engine.maxParams &#123;</span><br><span class="line">engine.maxParams = paramsCount</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每个HTTP方法（如：GET，POST）的路由信息都各自由一个<strong>树结构</strong>来维护，该树结构的模型与函数实现位于gin/tree.go中，此处不再继续展开。</p><h4 id="启动gin">2.1.5 启动Gin</h4><h5 id="engine.run函数">Engine.Run函数</h5><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Run attaches the router to a http.Server and starts listening and serving HTTP requests.</span></span><br><span class="line"><span class="comment">// It is a shortcut for http.ListenAndServe(addr, router)</span></span><br><span class="line"><span class="comment">// Note: this method will block the calling goroutine indefinitely unless an error happens.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(engine *Engine)</span></span> Run(addr ...<span class="type">string</span>) (err <span class="type">error</span>) &#123;</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; debugPrintError(err) &#125;()</span><br><span class="line"></span><br><span class="line">trustedCIDRs, err := engine.prepareTrustedCIDRs()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">engine.trustedCIDRs = trustedCIDRs</span><br><span class="line">address := resolveAddress(addr)</span><br><span class="line">debugPrint(<span class="string">&quot;Listening and serving HTTP on %s\n&quot;</span>, address)</span><br><span class="line">err = http.ListenAndServe(address, engine)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，Engine.Run函数主要是：</p><ol type="1"><li>解析监听地址传参；</li><li>启动监听与服务。</li></ol><p>其中，最核心的监听与服务实质上是调用Go语言内置库net/http的<code>http.ListenAndServe</code>函数实现的。</p><h5 id="nethttp的listenandserve函数">net/http的ListenAndServe函数</h5><p>Gin框架网络编程的底层实际上是基于Go语言的内置net/http网络库实现的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ListenAndServe listens on the TCP network address addr and then calls</span></span><br><span class="line"><span class="comment">// Serve with handler to handle requests on incoming connections.</span></span><br><span class="line"><span class="comment">// Accepted connections are configured to enable TCP keep-alives.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// The handler is typically nil, in which case the DefaultServeMux is used.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// ListenAndServe always returns a non-nil error.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ListenAndServe</span><span class="params">(addr <span class="type">string</span>, handler Handler)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">server := &amp;Server&#123;Addr: addr, Handler: handler&#125;</span><br><span class="line"><span class="keyword">return</span> server.ListenAndServe()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该函数实例化Sever，并调用其<code>ListenAndServe</code>函数实现监听与服务功能。</p><p><strong>注意</strong>：此时，输入的GinEngine对象以<strong>Handler接口</strong>的对象的形式被传入给了net/http库的Server对象，作为后续Serve对象处理网络请求时调用的函数。</p><h5 id="nethttp的handler接口">net/http的Handler接口</h5><p>net/http的Server结构体类型中有一个Handler接口类型的Handler。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A Server defines parameters for running an HTTP server.</span></span><br><span class="line"><span class="comment">// The zero value for Server is a valid configuration.</span></span><br><span class="line"><span class="keyword">type</span> Server <span class="keyword">struct</span> &#123;</span><br><span class="line"><span class="comment">// Addr optionally specifies the TCP address for the server to listen on,</span></span><br><span class="line"><span class="comment">// in the form &quot;host:port&quot;. If empty, &quot;:http&quot; (port 80) is used.</span></span><br><span class="line"><span class="comment">// The service names are defined in RFC 6335 and assigned by IANA.</span></span><br><span class="line"><span class="comment">// See net.Dial for details of the address format.</span></span><br><span class="line">Addr <span class="type">string</span></span><br><span class="line"></span><br><span class="line">Handler Handler <span class="comment">// handler to invoke, http.DefaultServeMux if nil</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而该Handler接口的唯一特点就是有且仅有一个ServeHTTP函数声明，该接口定义代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A Handler responds to an HTTP request.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// ServeHTTP should write reply headers and data to the ResponseWriter</span></span><br><span class="line"><span class="comment">// and then return. Returning signals that the request is finished; it</span></span><br><span class="line"><span class="comment">// is not valid to use the ResponseWriter or read from the</span></span><br><span class="line"><span class="comment">// Request.Body after or concurrently with the completion of the</span></span><br><span class="line"><span class="comment">// ServeHTTP call.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Depending on the HTTP client software, HTTP protocol version, and</span></span><br><span class="line"><span class="comment">// any intermediaries between the client and the Go server, it may not</span></span><br><span class="line"><span class="comment">// be possible to read from the Request.Body after writing to the</span></span><br><span class="line"><span class="comment">// ResponseWriter. Cautious handlers should read the Request.Body</span></span><br><span class="line"><span class="comment">// first, and then reply.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Except for reading the body, handlers should not modify the</span></span><br><span class="line"><span class="comment">// provided Request.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes</span></span><br><span class="line"><span class="comment">// that the effect of the panic was isolated to the active request.</span></span><br><span class="line"><span class="comment">// It recovers the panic, logs a stack trace to the server error log,</span></span><br><span class="line"><span class="comment">// and either closes the network connection or sends an HTTP/2</span></span><br><span class="line"><span class="comment">// RST_STREAM, depending on the HTTP protocol. To abort a handler so</span></span><br><span class="line"><span class="comment">// the client sees an interrupted response but the server doesn&#x27;t log</span></span><br><span class="line"><span class="comment">// an error, panic with the value ErrAbortHandler.</span></span><br><span class="line"><span class="keyword">type</span> Handler <span class="keyword">interface</span> &#123;</span><br><span class="line">ServeHTTP(ResponseWriter, *Request)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Handler接口的意义就在于，任何类型，只需要实现了该ServeHTTP函数，就实现了Handler接口，就可以用作Server的Handler，供HTTP处理时调用。</p><p>显然，gin.Engine实现了net/http的Handler接口的ServeHTTP函数（gin/gin.go）。具体的实现原理在接下来介绍。</p><h3 id="请求与响应过程">2.2 请求与响应过程</h3><h4 id="监听与接受请求">2.2.1 监听与接受请求</h4><h5id="nethttp的server.listenandserve函数">net/http的Server.ListenAndServe函数</h5><p>上文介绍到，gin实际上调用了net/http的<code>ListenAndServe</code>函数实现网络监听与处理，具体由<code>Server.ListenAndServe</code>实现，位于net/http/server.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ListenAndServe listens on the TCP network address srv.Addr and then</span></span><br><span class="line"><span class="comment">// calls Serve to handle requests on incoming connections.</span></span><br><span class="line"><span class="comment">// Accepted connections are configured to enable TCP keep-alives.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// If srv.Addr is blank, &quot;:http&quot; is used.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// ListenAndServe always returns a non-nil error. After Shutdown or Close,</span></span><br><span class="line"><span class="comment">// the returned error is ErrServerClosed.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(srv *Server)</span></span> ListenAndServe() <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> srv.shuttingDown() &#123;</span><br><span class="line"><span class="keyword">return</span> ErrServerClosed</span><br><span class="line">&#125;</span><br><span class="line">addr := srv.Addr</span><br><span class="line"><span class="keyword">if</span> addr == <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">addr = <span class="string">&quot;:http&quot;</span></span><br><span class="line">&#125;</span><br><span class="line">ln, err := net.Listen(<span class="string">&quot;tcp&quot;</span>, addr)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> srv.Serve(ln)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，net/http的<code>Server.ListenAndServe</code>函数实际上主要完成两项工作：</p><ol type="1"><li><strong>设置监听</strong>：<code>net.Listen("tcp", addr)</code>负责设置监听地址；</li><li><strong>接受并处理网络请求</strong>：<code>srv.Serve(ln)</code>负责在监听位置上接受网络请求，建立连接并做出响应。</li></ol><h5 id="nethttp的server.serve函数">net/http的Server.Serve函数</h5><p><code>Server.Serve</code>函数用于监听、接受和处理网络请求，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Serve accepts incoming connections on the Listener l, creating a</span></span><br><span class="line"><span class="comment">// new service goroutine for each. The service goroutines read requests and</span></span><br><span class="line"><span class="comment">// then call srv.Handler to reply to them.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// HTTP/2 support is only enabled if the Listener returns *tls.Conn</span></span><br><span class="line"><span class="comment">// connections and they were configured with &quot;h2&quot; in the TLS</span></span><br><span class="line"><span class="comment">// Config.NextProtos.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Serve always returns a non-nil error and closes l.</span></span><br><span class="line"><span class="comment">// After Shutdown or Close, the returned error is ErrServerClosed.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(srv *Server)</span></span> Serve(l net.Listener) <span class="type">error</span> &#123;</span><br><span class="line"><span class="keyword">if</span> fn := testHookServerServe; fn != <span class="literal">nil</span> &#123;</span><br><span class="line">fn(srv, l) <span class="comment">// call hook with unwrapped listener</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">origListener := l</span><br><span class="line">l = &amp;onceCloseListener&#123;Listener: l&#125;</span><br><span class="line"><span class="keyword">defer</span> l.Close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> err := srv.setupHTTP2_Serve(); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> !srv.trackListener(&amp;l, <span class="literal">true</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> ErrServerClosed</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> srv.trackListener(&amp;l, <span class="literal">false</span>)</span><br><span class="line"></span><br><span class="line">baseCtx := context.Background()</span><br><span class="line"><span class="keyword">if</span> srv.BaseContext != <span class="literal">nil</span> &#123;</span><br><span class="line">baseCtx = srv.BaseContext(origListener)</span><br><span class="line"><span class="keyword">if</span> baseCtx == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">&quot;BaseContext returned a nil context&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="keyword">var</span> tempDelay time.Duration <span class="comment">// how long to sleep on accept failure</span></span><br><span class="line"></span><br><span class="line">ctx := context.WithValue(baseCtx, ServerContextKey, srv)</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">rw, err := l.Accept()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">select</span> &#123;</span><br><span class="line"><span class="keyword">case</span> &lt;-srv.getDoneChan():</span><br><span class="line"><span class="keyword">return</span> ErrServerClosed</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> ne, ok := err.(net.Error); ok &amp;&amp; ne.Temporary() &#123;</span><br><span class="line"><span class="keyword">if</span> tempDelay == <span class="number">0</span> &#123;</span><br><span class="line">tempDelay = <span class="number">5</span> * time.Millisecond</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">tempDelay *= <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> max := <span class="number">1</span> * time.Second; tempDelay &gt; max &#123;</span><br><span class="line">tempDelay = max</span><br><span class="line">&#125;</span><br><span class="line">srv.logf(<span class="string">&quot;http: Accept error: %v; retrying in %v&quot;</span>, err, tempDelay)</span><br><span class="line">time.Sleep(tempDelay)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">connCtx := ctx</span><br><span class="line"><span class="keyword">if</span> cc := srv.ConnContext; cc != <span class="literal">nil</span> &#123;</span><br><span class="line">connCtx = cc(connCtx, rw)</span><br><span class="line"><span class="keyword">if</span> connCtx == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="built_in">panic</span>(<span class="string">&quot;ConnContext returned nil&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">tempDelay = <span class="number">0</span></span><br><span class="line">c := srv.newConn(rw)</span><br><span class="line">c.setState(c.rwc, StateNew, runHooks) <span class="comment">// before Serve can return</span></span><br><span class="line"><span class="keyword">go</span> c.serve(connCtx)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>Server.Serve</code>函数的实现中，启动了一个无条件的for循环以便持续监听、接受和处理网络请求，主要流程为：</p><ol type="1"><li><strong>接受请求</strong>：<code>l.Accept()</code>调用在无请求时保持阻塞，直到接收到请求时，接受请求并返回建立的连接；</li><li><strong>处理请求</strong>：启动一个goroutine，使用conn的serve函数进行处理（<code>go c.serve(connCtx)</code>）；</li></ol><h5 id="nethttp的conn.serve函数">net/http的conn.serve函数</h5><p>已接受的请求会建立连接，对连接的后续处理由conn.serve函数实现，该函数实现较长，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Serve a new connection.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *conn)</span></span> serve(ctx context.Context) &#123;</span><br><span class="line">c.remoteAddr = c.rwc.RemoteAddr().String()</span><br><span class="line">ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())</span><br><span class="line"><span class="keyword">defer</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">if</span> err := <span class="built_in">recover</span>(); err != <span class="literal">nil</span> &amp;&amp; err != ErrAbortHandler &#123;</span><br><span class="line"><span class="keyword">const</span> size = <span class="number">64</span> &lt;&lt; <span class="number">10</span></span><br><span class="line">buf := <span class="built_in">make</span>([]<span class="type">byte</span>, size)</span><br><span class="line">buf = buf[:runtime.Stack(buf, <span class="literal">false</span>)]</span><br><span class="line">c.server.logf(<span class="string">&quot;http: panic serving %v: %v\n%s&quot;</span>, c.remoteAddr, err, buf)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> !c.hijacked() &#123;</span><br><span class="line">c.<span class="built_in">close</span>()</span><br><span class="line">c.setState(c.rwc, StateClosed, runHooks)</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">if</span> tlsConn, ok := c.rwc.(*tls.Conn); ok &#123;</span><br><span class="line"><span class="keyword">if</span> d := c.server.ReadTimeout; d &gt; <span class="number">0</span> &#123;</span><br><span class="line">c.rwc.SetReadDeadline(time.Now().Add(d))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> d := c.server.WriteTimeout; d &gt; <span class="number">0</span> &#123;</span><br><span class="line">c.rwc.SetWriteDeadline(time.Now().Add(d))</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err := tlsConn.HandshakeContext(ctx); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">// If the handshake failed due to the client not speaking</span></span><br><span class="line"><span class="comment">// TLS, assume they&#x27;re speaking plaintext HTTP and write a</span></span><br><span class="line"><span class="comment">// 400 response on the TLS conn&#x27;s underlying net.Conn.</span></span><br><span class="line"><span class="keyword">if</span> re, ok := err.(tls.RecordHeaderError); ok &amp;&amp; re.Conn != <span class="literal">nil</span> &amp;&amp; tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) &#123;</span><br><span class="line">io.WriteString(re.Conn, <span class="string">&quot;HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n&quot;</span>)</span><br><span class="line">re.Conn.Close()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.server.logf(<span class="string">&quot;http: TLS handshake error from %s: %v&quot;</span>, c.rwc.RemoteAddr(), err)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.tlsState = <span class="built_in">new</span>(tls.ConnectionState)</span><br><span class="line">*c.tlsState = tlsConn.ConnectionState()</span><br><span class="line"><span class="keyword">if</span> proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) &#123;</span><br><span class="line"><span class="keyword">if</span> fn := c.server.TLSNextProto[proto]; fn != <span class="literal">nil</span> &#123;</span><br><span class="line">h := initALPNRequest&#123;ctx, tlsConn, serverHandler&#123;c.server&#125;&#125;</span><br><span class="line"><span class="comment">// Mark freshly created HTTP/2 as active and prevent any server state hooks</span></span><br><span class="line"><span class="comment">// from being run on these connections. This prevents closeIdleConns from</span></span><br><span class="line"><span class="comment">// closing such connections. See issue https://golang.org/issue/39776.</span></span><br><span class="line">c.setState(c.rwc, StateActive, skipHooks)</span><br><span class="line">fn(c.server, tlsConn, h)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// HTTP/1.x from here on.</span></span><br><span class="line"></span><br><span class="line">ctx, cancelCtx := context.WithCancel(ctx)</span><br><span class="line">c.cancelCtx = cancelCtx</span><br><span class="line"><span class="keyword">defer</span> cancelCtx()</span><br><span class="line"></span><br><span class="line">c.r = &amp;connReader&#123;conn: c&#125;</span><br><span class="line">c.bufr = newBufioReader(c.r)</span><br><span class="line">c.bufw = newBufioWriterSize(checkConnErrorWriter&#123;c&#125;, <span class="number">4</span>&lt;&lt;<span class="number">10</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line">w, err := c.readRequest(ctx)</span><br><span class="line"><span class="keyword">if</span> c.r.remain != c.server.initialReadLimitSize() &#123;</span><br><span class="line"><span class="comment">// If we read any bytes off the wire, we&#x27;re active.</span></span><br><span class="line">c.setState(c.rwc, StateActive, runHooks)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">const</span> errorHeaders = <span class="string">&quot;\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> err == errTooLarge:</span><br><span class="line"><span class="comment">// Their HTTP client may or may not be</span></span><br><span class="line"><span class="comment">// able to read this if we&#x27;re</span></span><br><span class="line"><span class="comment">// responding to them and hanging up</span></span><br><span class="line"><span class="comment">// while they&#x27;re still writing their</span></span><br><span class="line"><span class="comment">// request. Undefined behavior.</span></span><br><span class="line"><span class="keyword">const</span> publicErr = <span class="string">&quot;431 Request Header Fields Too Large&quot;</span></span><br><span class="line">fmt.Fprintf(c.rwc, <span class="string">&quot;HTTP/1.1 &quot;</span>+publicErr+errorHeaders+publicErr)</span><br><span class="line">c.closeWriteAndWait()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> isUnsupportedTEError(err):</span><br><span class="line"><span class="comment">// Respond as per RFC 7230 Section 3.3.1 which says,</span></span><br><span class="line"><span class="comment">//      A server that receives a request message with a</span></span><br><span class="line"><span class="comment">//      transfer coding it does not understand SHOULD</span></span><br><span class="line"><span class="comment">//      respond with 501 (Unimplemented).</span></span><br><span class="line">code := StatusNotImplemented</span><br><span class="line"></span><br><span class="line"><span class="comment">// We purposefully aren&#x27;t echoing back the transfer-encoding&#x27;s value,</span></span><br><span class="line"><span class="comment">// so as to mitigate the risk of cross side scripting by an attacker.</span></span><br><span class="line">fmt.Fprintf(c.rwc, <span class="string">&quot;HTTP/1.1 %d %s%sUnsupported transfer encoding&quot;</span>, code, StatusText(code), errorHeaders)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> isCommonNetReadError(err):</span><br><span class="line"><span class="keyword">return</span> <span class="comment">// don&#x27;t reply</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line"><span class="keyword">if</span> v, ok := err.(statusError); ok &#123;</span><br><span class="line">fmt.Fprintf(c.rwc, <span class="string">&quot;HTTP/1.1 %d %s: %s%s%d %s: %s&quot;</span>, v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">publicErr := <span class="string">&quot;400 Bad Request&quot;</span></span><br><span class="line">fmt.Fprintf(c.rwc, <span class="string">&quot;HTTP/1.1 &quot;</span>+publicErr+errorHeaders+publicErr)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Expect 100 Continue support</span></span><br><span class="line">req := w.req</span><br><span class="line"><span class="keyword">if</span> req.expectsContinue() &#123;</span><br><span class="line"><span class="keyword">if</span> req.ProtoAtLeast(<span class="number">1</span>, <span class="number">1</span>) &amp;&amp; req.ContentLength != <span class="number">0</span> &#123;</span><br><span class="line"><span class="comment">// Wrap the Body reader with one that replies on the connection</span></span><br><span class="line">req.Body = &amp;expectContinueReader&#123;readCloser: req.Body, resp: w&#125;</span><br><span class="line">w.canWriteContinue.setTrue()</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> req.Header.get(<span class="string">&quot;Expect&quot;</span>) != <span class="string">&quot;&quot;</span> &#123;</span><br><span class="line">w.sendExpectationFailed()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">c.curReq.Store(w)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> requestBodyRemains(req.Body) &#123;</span><br><span class="line">registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">w.conn.r.startBackgroundRead()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// HTTP cannot have multiple simultaneous active requests.[*]</span></span><br><span class="line"><span class="comment">// Until the server replies to this request, it can&#x27;t read another,</span></span><br><span class="line"><span class="comment">// so we might as well run the handler in this goroutine.</span></span><br><span class="line"><span class="comment">// [*] Not strictly true: HTTP pipelining. We could let them all process</span></span><br><span class="line"><span class="comment">// in parallel even if their responses need to be serialized.</span></span><br><span class="line"><span class="comment">// But we&#x27;re not going to implement HTTP pipelining because it</span></span><br><span class="line"><span class="comment">// was never deployed in the wild and the answer is HTTP/2.</span></span><br><span class="line">serverHandler&#123;c.server&#125;.ServeHTTP(w, w.req)</span><br><span class="line">w.cancelCtx()</span><br><span class="line"><span class="keyword">if</span> c.hijacked() &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">w.finishRequest()</span><br><span class="line"><span class="keyword">if</span> !w.shouldReuseConnection() &#123;</span><br><span class="line"><span class="keyword">if</span> w.requestBodyLimitHit || w.closedRequestBodyEarly() &#123;</span><br><span class="line">c.closeWriteAndWait()</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">c.setState(c.rwc, StateIdle, runHooks)</span><br><span class="line">c.curReq.Store((*response)(<span class="literal">nil</span>))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> !w.conn.server.doKeepAlives() &#123;</span><br><span class="line"><span class="comment">// We&#x27;re in shutdown mode. We might&#x27;ve replied</span></span><br><span class="line"><span class="comment">// to the user without &quot;Connection: close&quot; and</span></span><br><span class="line"><span class="comment">// they might think they can send another</span></span><br><span class="line"><span class="comment">// request, but such is life with HTTP/1.1.</span></span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> d := c.server.idleTimeout(); d != <span class="number">0</span> &#123;</span><br><span class="line">c.rwc.SetReadDeadline(time.Now().Add(d))</span><br><span class="line"><span class="keyword">if</span> _, err := c.bufr.Peek(<span class="number">4</span>); err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">c.rwc.SetReadDeadline(time.Time&#123;&#125;)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不难发现，<code>conn.serve</code>函数的代码实现较长，其中，对连接的主要处理由<code>serverHandler&#123;c.server&#125;.ServeHTTP(w, w.req)</code>函数调用实现。</p><p>这一步调用实质上时首先实例化了一个Server实例，然后调用实例的<code>ServeHTTP</code>函数对连接的请求与响应进行具体的处理。上文讲到，实现了<code>ServeHTTP</code>函数就实现了Handler接口。Gin就是通过实现接口的方式，利用系统的net/http库执行自身的功能。</p><h5 id="gin的engine.servehttp函数">gin的Engine.ServeHTTP函数</h5><p>gin在gin.go中实现了<code>ServeHTTP</code>函数，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ServeHTTP conforms to the http.Handler interface.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(engine *Engine)</span></span> ServeHTTP(w http.ResponseWriter, req *http.Request) &#123;</span><br><span class="line">c := engine.pool.Get().(*Context)</span><br><span class="line">c.writermem.reset(w)</span><br><span class="line">c.Request = req</span><br><span class="line">c.reset()</span><br><span class="line"></span><br><span class="line">engine.handleHTTPRequest(c)</span><br><span class="line"></span><br><span class="line">engine.pool.Put(c)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>主要步骤为：</p><ol type="1"><li><strong>建立连接上下文</strong>：从缓存池中提取上下文对象，填入当前连接的<code>http.ResponseWriter</code>实例与<code>http.Request</code>实例；</li><li><strong>处理连接</strong>：以上下文对象的形式将连接交给函数处理，由<code>engine.handleHTTPRequest(c)</code>封装实现了；</li><li><strong>回收连接上下文</strong>：处理完毕后，将上下文对象回收进缓存池中。</li></ol><p>值得注意的是，Gin中对每个连接都需要的上下文对象进行缓存化存取，通过缓存池节省连接高并发时上下文对象频繁生灭造成内存频繁分配与释放的代价。</p><h5id="gin的engine.handlehttprequest函数">gin的Engine.handleHTTPRequest函数</h5><p><code>handleHTTPRequest</code>函数封装了对请求进行处理的具体过程，位于gin/gin.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(engine *Engine)</span></span> handleHTTPRequest(c *Context) &#123;</span><br><span class="line">httpMethod := c.Request.Method</span><br><span class="line">rPath := c.Request.URL.Path</span><br><span class="line">unescape := <span class="literal">false</span></span><br><span class="line"><span class="keyword">if</span> engine.UseRawPath &amp;&amp; <span class="built_in">len</span>(c.Request.URL.RawPath) &gt; <span class="number">0</span> &#123;</span><br><span class="line">rPath = c.Request.URL.RawPath</span><br><span class="line">unescape = engine.UnescapePathValues</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> engine.RemoveExtraSlash &#123;</span><br><span class="line">rPath = cleanPath(rPath)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Find root of the tree for the given HTTP method</span></span><br><span class="line">t := engine.trees</span><br><span class="line"><span class="keyword">for</span> i, tl := <span class="number">0</span>, <span class="built_in">len</span>(t); i &lt; tl; i++ &#123;</span><br><span class="line"><span class="keyword">if</span> t[i].method != httpMethod &#123;</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">root := t[i].root</span><br><span class="line"><span class="comment">// Find route in tree</span></span><br><span class="line">value := root.getValue(rPath, c.params, unescape)</span><br><span class="line"><span class="keyword">if</span> value.params != <span class="literal">nil</span> &#123;</span><br><span class="line">c.Params = *value.params</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> value.handlers != <span class="literal">nil</span> &#123;</span><br><span class="line">c.handlers = value.handlers</span><br><span class="line">c.fullPath = value.fullPath</span><br><span class="line">c.Next()</span><br><span class="line">c.writermem.WriteHeaderNow()</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> httpMethod != <span class="string">&quot;CONNECT&quot;</span> &amp;&amp; rPath != <span class="string">&quot;/&quot;</span> &#123;</span><br><span class="line"><span class="keyword">if</span> value.tsr &amp;&amp; engine.RedirectTrailingSlash &#123;</span><br><span class="line">redirectTrailingSlash(c)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> engine.RedirectFixedPath &amp;&amp; redirectFixedPath(c, root, engine.RedirectFixedPath) &#123;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> engine.HandleMethodNotAllowed &#123;</span><br><span class="line"><span class="keyword">for</span> _, tree := <span class="keyword">range</span> engine.trees &#123;</span><br><span class="line"><span class="keyword">if</span> tree.method == httpMethod &#123;</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> value := tree.root.getValue(rPath, <span class="literal">nil</span>, unescape); value.handlers != <span class="literal">nil</span> &#123;</span><br><span class="line">c.handlers = engine.allNoMethod</span><br><span class="line">serveError(c, http.StatusMethodNotAllowed, default405Body)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">c.handlers = engine.allNoRoute</span><br><span class="line">serveError(c, http.StatusNotFound, default404Body)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Engine.handleHTTPRequest</code>函数的主要处理位于中间的for循环中，主要为：</p><ol type="1"><li>遍历查找<code>engine.trees</code>以找出当前请求的HTTPMethod对应的处理树；</li><li>从该处理树中，根据当前请求的路径与参数查询出对应的处理函数<code>value</code>；</li><li>将查询出的处理函数链（<code>gin.HandlerChain</code>）写入当前连接上下文的<code>c.handlers</code>中；</li><li>执行<code>c.Next()</code>，调用handlers链上的下一个函数（中间件/业务处理函数），开始形成LIFO的函数调用栈；</li><li>待函数调用栈全部返回后，<code>c.writermem.WriteHeaderNow()</code>根据上下文信息，将HTTP状态码写入响应头。</li></ol><h4 id="中间件与handler">2.2.2 中间件与handler</h4><p>请求发来时，被中间件与业务逻辑的handler处理，Gin的中间件与业务逻辑函数实质上都是gin.HandlerFunc函数。</p><p>例如，为gin.Engine添加了两款中间件（MiddeWareA与MiddleWareB）并为GET方法的/hello路径注册了一个Hello函数作为路由处理函数，那么执行过程为：</p><ol type="1"><li>上述<code>handleHTTPRequest</code>函数执行到<code>c.Next()</code>，调用MiddleWareA；</li><li>MiddleWareA执行到<code>c.Next()</code>，调用MiddleWareB；</li><li>MiddleWareB执行到<code>c.Next()</code>，调用Hello；</li><li>Hello函数返回，MiddleWareB继续执行至函数返回；</li><li>MiddleWareA函数继续执行至函数返回。</li></ol><h5 id="gin的context.next函数">gin的Context.Next函数</h5><p>中间件中屡屡调用的<code>c.Next()</code>函数时gin提供的中间件流程控制函数之一，位于gin/context.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/************************************/</span></span><br><span class="line"><span class="comment">/*********** FLOW CONTROL ***********/</span></span><br><span class="line"><span class="comment">/************************************/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Next should be used only inside middleware.</span></span><br><span class="line"><span class="comment">// It executes the pending handlers in the chain inside the calling handler.</span></span><br><span class="line"><span class="comment">// See example in GitHub.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *Context)</span></span> Next() &#123;</span><br><span class="line">c.index++</span><br><span class="line"><span class="keyword">for</span> c.index &lt; <span class="type">int8</span>(<span class="built_in">len</span>(c.handlers)) &#123;</span><br><span class="line">c.handlers[c.index](c)</span><br><span class="line">c.index++</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不难理解，<code>Next</code>函数起到的作用是，在当前中间件函数中，调用下一个HandlerFunc。依序调用HandlerChain中的HandlerFunc的过程中，形成了一个函数调用栈，调用时函数依序入栈，至最后一个函数调用返回，此后按LIFO的顺序出栈，自然就形成了上述中间件的LIFO的执行顺序。</p><h4 id="请求处理与响应">2.2.3 请求处理与响应</h4><p>在本例中，我写了一个简易的创建Person的API，其涉及到模型定义与业务逻辑。</p><h5 id="模型定义">模型定义</h5><p>模型定义位于/model/person.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> model</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Person <span class="keyword">struct</span> &#123;</span><br><span class="line">Name  <span class="type">string</span> <span class="string">`json:&quot;name&quot;`</span></span><br><span class="line">Phone <span class="type">string</span> <span class="string">`json:&quot;phone&quot;`</span></span><br><span class="line">Age   <span class="type">uint64</span> <span class="string">`json:&quot;age&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CreatePersonRequest <span class="keyword">struct</span> &#123;</span><br><span class="line">Person Person <span class="string">`json:&quot;person&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CreatePersonResponse <span class="keyword">struct</span> &#123;</span><br><span class="line">Person   Person        <span class="string">`json:&quot;person&quot;`</span></span><br><span class="line">Elapse   time.Duration <span class="string">`json:&quot;elapse&quot;`</span> <span class="comment">// nano seconds</span></span><br><span class="line">BaseResp BaseResp      <span class="string">`json:&quot;baseresp&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中，BaseResp位于/model/base.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> model</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> BaseResp <span class="keyword">struct</span> &#123;</span><br><span class="line">Code    <span class="type">int64</span>  <span class="string">`json:&quot;code&quot;`</span></span><br><span class="line">Message <span class="type">string</span> <span class="string">`json:&quot;message&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h5 id="业务逻辑">业务逻辑</h5><p>业务逻辑函数位于handler/person/create_person.go中，代码如下：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> person</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="string">&quot;github.com/LearnGin/model&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin&quot;</span></span><br><span class="line"><span class="string">&quot;github.com/gin-gonic/gin/binding&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CreatePersonHandler</span><span class="params">()</span></span> gin.HandlerFunc &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(c *gin.Context)</span></span> &#123;</span><br><span class="line"><span class="comment">// parse request</span></span><br><span class="line">tic := time.Now()</span><br><span class="line">req := <span class="built_in">new</span>(model.CreatePersonRequest)</span><br><span class="line">err := c.ShouldBindWith(req, binding.JSON)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">fmt.Errorf(<span class="string">&quot;Can not bind with model.Person, err: %+v\n&quot;</span>, err)</span><br><span class="line">resp := <span class="built_in">new</span>(model.CreatePersonResponse)</span><br><span class="line">resp.Elapse = time.Since(tic)</span><br><span class="line">resp.BaseResp = model.BaseResp&#123;</span><br><span class="line">Code:    <span class="number">1</span>,</span><br><span class="line">Message: fmt.Sprintf(<span class="string">&quot;create person failed in binding json, err: %s&quot;</span>, err.Error()),</span><br><span class="line">&#125;</span><br><span class="line">c.JSON(<span class="number">200</span>, resp)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// process request</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;Creating Person: %+v\n&quot;</span>, req.Person)</span><br><span class="line"></span><br><span class="line"><span class="comment">// jsonify response</span></span><br><span class="line">resp := <span class="built_in">new</span>(model.CreatePersonResponse)</span><br><span class="line">resp.Person = req.Person</span><br><span class="line">resp.Elapse = time.Since(tic)</span><br><span class="line">resp.BaseResp = model.BaseResp&#123;</span><br><span class="line">Code:    <span class="number">0</span>,</span><br><span class="line">Message: <span class="string">&quot;success&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">c.JSON(<span class="number">200</span>, resp)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>处理上主要分三步：</p><ol type="1"><li><strong>解析请求</strong>：<code>c.ShouldBindWith(req, binding.JSON)</code>负责解析请求中发来的JSON数据，并将解析结果绑定到指定的结构体对象上；</li><li><strong>业务处理</strong>：此处只做print显示；</li><li><strong>发送响应</strong>：实例化响应结构体，并将其序列化为JSON作为响应。</li></ol><p>值得注意的是：此处的序列化与反序列化会参照结构体的类型tag（如有）。</p><h2 id="总结">3 总结</h2><p>结合对Gin框架主干代码以及其调用的部分Go源码的阅读，可以体会到：</p><ol type="1"><li>Gin框架实质上实现的网络通信层以上的框架搭建，而网络通信功能完全采用Go语言的net/http库实现；</li><li>Gin通过实现Go语言提供的接口快捷地接入Go的内置库功能，使得上层应用与底层实现之间互不依赖，充分体现了SOLID中的依赖倒置原则；</li><li>Gin在性能上针对HTTPWeb框架常见的高并发问题进行了优化，例如：通过上下文对象的缓存池节省连接高并发时内存频繁申请与释放的代价；</li><li>Gin在设计上将中间件与业务逻辑都抽象为gin.HandleFunc函数，中间件与业务逻辑的执行过程实际上就是函数序列依序调用形成的函数调用栈的执行过程。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a
href=&quot;https://github.com/gin-gonic/gin&quot;&gt;Gin&lt;/a&gt;是一款高性能的Go语言Web框架，本文以一个小型示例项目为例，从源码解读Gin的服务启动过程、请求与响应过程的技术原理。&lt;/p&gt;</summary>
    
    
    
    
    <category term="HTTP" scheme="https://heary.cn/tags/HTTP/"/>
    
    <category term="Network" scheme="https://heary.cn/tags/Network/"/>
    
    <category term="Golang" scheme="https://heary.cn/tags/Golang/"/>
    
    <category term="Gin" scheme="https://heary.cn/tags/Gin/"/>
    
  </entry>
  
  <entry>
    <title>TCMalloc - Go的内存分配原理</title>
    <link href="https://heary.cn/posts/TCMalloc-Go%E7%9A%84%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%8E%9F%E7%90%86/"/>
    <id>https://heary.cn/posts/TCMalloc-Go%E7%9A%84%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E5%8E%9F%E7%90%86/</id>
    <published>2021-08-10T15:31:15.000Z</published>
    <updated>2022-08-07T04:02:09.619Z</updated>
    
    <content type="html"><![CDATA[<p>Golang的内存分配机制主要基于TCMalloc机制，本文根据<ahref="http://goog-perftools.sourceforge.net/doc/tcmalloc.html">TCMalloc: Thread-Caching Malloc</a>一文了解原理并总结笔记。</p><span id="more"></span><h1 id="tcmalloc---go的内存分配原理">TCMalloc - Go的内存分配原理</h1><p>本文参考自：</p><blockquote><p><strong><ahref="http://goog-perftools.sourceforge.net/doc/tcmalloc.html">TCMalloc: Thread-Caching Malloc</a></strong></p><p><em>Sanjay Ghemawat, Paul Menage <ahref="mailto:opensource@google.com"class="email">opensource@google.com</a></em></p></blockquote><h2 id="简介">1 简介</h2><p>TCMalloc（Thread-CachingMalloc）是Google发布的一款线程缓存型内存分配机制。TCMalloc为每一个线程都缓存一些可分配内存，因此，在多线程场景下，TCMalloc能够尽可能规避多个线程同时分配/释放内存时的锁争用问题，这使得TCMalloc相较于其它内存分配机制，内存分配和回收速度更快。另外，TCMalloc还有内存分配利用率高的优势。</p><h2 id="原理">2 原理</h2><p>TCMalloc通过Thread Cache和Central Heap组成的双层结构分配内存。</p><figure><img src="http://goog-perftools.sourceforge.net/doc/overview.gif"alt="Thread Cache and Central Heap" /><figcaption aria-hidden="true">Thread Cache and CentralHeap</figcaption></figure><p>线程分配内存时，TCMalloc从该线程的线程缓存（ThreadCache）中取出恰当尺寸的内存块。而线程释放回线程缓存的内存，也会由垃圾回收机制收纳回中央堆区（CentralHeap）。具体地，TCMalloc的内存分配分两种情况：</p><ol type="1"><li>小对象分配（小于等于32KB）</li><li>大对象分配（大于32KB）</li></ol><h3 id="小对象分配">2.1 小对象分配</h3><p>当线程请求分配不超过32KB的小对象时，线程缓存为其分配恰当尺寸的内存块。</p><figure><img src="http://goog-perftools.sourceforge.net/doc/threadheap.gif"alt="Free Objects Linked List of Thread Cache" /><figcaption aria-hidden="true">Free Objects Linked List of ThreadCache</figcaption></figure><p>线程缓存（ThreadCache）维护着一个数组到单向链表的数据结构，数组中的每一个节点都从小到大依次代表一个可分配尺寸（共约170种尺寸），每个尺寸以单链表的形式维护该尺寸的可分配内存。</p><p>当一个线程请求分配内存时：</p><ol type="1"><li>首先根据内存需求，找到合适的尺寸（例如：申请961~1024字节，均分配1024字节的内存对象）；</li><li>在该尺寸的链表上，检查是否有可分配内存块：<ol type="1"><li>如果有该尺寸的内存对象，那么取出链表中第一个可分配内存块供线程使用即可；</li><li>如果没有该尺寸的内存对象，那就得从中央堆区去拿一些内存来用：<ol type="1"><li>如果中央堆区有该尺寸的内存，那么就取过来用就可以了。将取来的一些内存对象补充到该尺寸的链表里，并从补充后的链表中拿一个内存对象出来供线程使用；</li><li>如果连中央堆区也没有该尺寸的内存了，那就需要为其补充更多的内存：<ol type="1"><li>通过中央页分配器（central pageallocator）来分配内存页（page）；</li><li>将分配到的内存页分解为该尺寸的一系列对象；</li><li>把分解出的这些对象补充到中央堆区该尺寸的链表上；</li><li>既然中央堆区补充好该尺寸的内存了，那就照常拿一些补充到请求线程的线程缓存中，供其分配使用。</li></ol></li></ol></li></ol></li></ol><h3 id="大对象分配">2.2 大对象分配</h3><p>超过32K的大对象以4K的内存页为单位进行分配。直接由中央堆区负责维护空页，通过链表分别归纳维护长度为1~255页的空内存块，长度超过255页的内存块则由rest链表统一管理。</p><figure><img src="http://goog-perftools.sourceforge.net/doc/pageheap.gif"alt="List of Various Page Sizes" /><figcaption aria-hidden="true">List of Various Page Sizes</figcaption></figure><p>当请求内存时，根据需求的页面数量找到对应页面数的链表；</p><ol type="1"><li>如果链表内有内存，则分配；</li><li>如果链表内没有内存，则找下一个更大尺寸的链表，<ol type="1"><li>如果找到了，则分配内存，并将剩余页面插入对应尺寸的链表中；</li><li>如果整个中央堆区都找不到合适尺寸的内存，则向操作系统申请内存以补充到中央堆区中。</li></ol></li></ol><h3 id="spans">2.3 Spans</h3><p>TCMalloc通过span对象来组织内存页。一个span代表一些连续的内存页。</p><p>通过一个数据结构来维护从页号到span地址的映射：</p><figure><img src="http://goog-perftools.sourceforge.net/doc/spanmap.gif"alt="Central Array of Pages&#39; Span Addresses" /><figcaption aria-hidden="true">Central Array of Pages' SpanAddresses</figcaption></figure><p>在32位环境下，32位的地址能够寻址2<sup>32B的内存空间，如果按每个内存页4KB的尺寸进行分页，总共2</sup>20，即1M个页号。每个span地址为32位，即4B，那么通过4MB的数组就能够实现从页号到span地址的寻址。</p><p>在64位环境下，考虑到地址空间很大，因此通过一个3层的基数树（radixtree）来建立页号到span地址的映射。</p><h3 id="释放">2.4 释放</h3><p>当内存对象释放时，首先根据内存页号查出对应的span对象。通过span对象进行判断：</p><ol type="1"><li>如果是小对象，则将其插入其线程缓存中对应尺寸的空闲链表（freelist）中。<ol type="1"><li>如果线程缓存超出了预设尺寸（默认2MB），则需要运行垃圾回收，将线程缓存中不用的对象还给中央堆的空闲链表。</li></ol></li><li>如果是大对象，则将span管理的内存页与相邻内存页合并后，还给中央堆区的空闲链表。</li></ol><h3 id="线程缓存的垃圾回收">2.5 线程缓存的垃圾回收</h3><p>当线程缓存中的空余内存超过阈值（默认2MB）时，会触发垃圾回收，把线程缓存中的内存还给中央堆区的空闲链表。</p><p>当线程数量增加时，垃圾回收阈值会减小，以免线程数量很多时浪费内存空间。</p><p>垃圾回收机制会记录线程缓存中每一个空闲链表的低水位L。L指的是自上一次垃圾回收以来该链表的最短长度。TCMalloc每次将空闲链表中L/2个内存对象回收到中央堆区。每次回收L/2，这样的回收速度能够很快地将长期不用的空闲链表回收到中央堆区的空闲链表中，以便其他有需要的线程快速获取。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Golang的内存分配机制主要基于TCMalloc机制，本文根据&lt;a
href=&quot;http://goog-perftools.sourceforge.net/doc/tcmalloc.html&quot;&gt;TCMalloc
: Thread-Caching Malloc&lt;/a&gt;一文了解原理并总结笔记。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Golang" scheme="https://heary.cn/tags/Golang/"/>
    
    <category term="TCMalloc" scheme="https://heary.cn/tags/TCMalloc/"/>
    
  </entry>
  
  <entry>
    <title>Go Web开发笔记</title>
    <link href="https://heary.cn/posts/Go-Web%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"/>
    <id>https://heary.cn/posts/Go-Web%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/</id>
    <published>2021-05-06T04:01:29.000Z</published>
    <updated>2022-08-07T04:02:09.604Z</updated>
    
    <content type="html"><![CDATA[<p>整理Go Web开发的相关知识点。</p><span id="more"></span><h1 id="go-web开发笔记">Go Web开发笔记</h1><h2 id="gowikigo-web应用案例">1 GoWiki：Go Web应用案例</h2><p>GoWiki是一个极简的GoWeb应用，使用Go语言内置的<code>html/template</code>和<code>net/http</code>等库实现，实现基本的百科网站功能，包含词条创建、编辑、保存和浏览功能。</p><p>本节内容总结自官方教程<ahref="https://golang.org/doc/articles/wiki/">Writing WebApplications</a>。</p><h3 id="项目结构">1.1 项目结构</h3><p><code>gowiki</code></p><ul><li><code>wiki.go</code></li><li><code>edit.html</code></li><li><code>view.html</code></li></ul><h3 id="代码实现">1.2 代码实现</h3><h4 id="wiki.go">1.3.1 wiki.go</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Writing Web Applications</span></span><br><span class="line"><span class="comment">// Official Example from https://golang.org/doc/articles/wiki/</span></span><br><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;errors&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;html/template&quot;</span></span><br><span class="line"><span class="string">&quot;io/ioutil&quot;</span></span><br><span class="line"><span class="string">&quot;log&quot;</span></span><br><span class="line"><span class="string">&quot;net/http&quot;</span></span><br><span class="line"><span class="string">&quot;regexp&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Page <span class="keyword">struct</span> &#123;</span><br><span class="line">Title <span class="type">string</span></span><br><span class="line">Body  []<span class="type">byte</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> templates = template.Must(template.ParseFiles(<span class="string">&quot;edit.html&quot;</span>, <span class="string">&quot;view.html&quot;</span>))</span><br><span class="line"><span class="keyword">var</span> validPath = regexp.MustCompile(<span class="string">&quot;^/(edit|save|view)/([a-zA-Z0-9]+)$&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p *Page)</span></span> save() <span class="type">error</span> &#123;</span><br><span class="line">filename := p.Title + <span class="string">&quot;.txt&quot;</span></span><br><span class="line"><span class="keyword">return</span> ioutil.WriteFile(filename, p.Body, <span class="number">0600</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">loadPage</span><span class="params">(title <span class="type">string</span>)</span></span> (*Page, <span class="type">error</span>) &#123;</span><br><span class="line">filename := title + <span class="string">&quot;.txt&quot;</span></span><br><span class="line">body, err := ioutil.ReadFile(filename)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> &amp;Page&#123;Title: title, Body: body&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getTitle</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> (<span class="type">string</span>, <span class="type">error</span>) &#123;</span><br><span class="line">m := validPath.FindStringSubmatch(r.URL.Path)</span><br><span class="line"><span class="keyword">if</span> m == <span class="literal">nil</span> &#123;</span><br><span class="line">http.NotFound(w, r)</span><br><span class="line"><span class="keyword">return</span> <span class="string">&quot;&quot;</span>, errors.New(<span class="string">&quot;invalid Page Title&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> m[<span class="number">2</span>], <span class="literal">nil</span> <span class="comment">// The title is the second subexpression.</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">renderTemplate</span><span class="params">(w http.ResponseWriter, tmpl <span class="type">string</span>, p *Page)</span></span> &#123;</span><br><span class="line">err := templates.ExecuteTemplate(w, tmpl+<span class="string">&quot;.html&quot;</span>, p)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, err.Error(), http.StatusInternalServerError)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">viewHandler</span><span class="params">(w http.ResponseWriter, r *http.Request, title <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">p, err := loadPage(title)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Redirect(w, r, <span class="string">&quot;/edit/&quot;</span>+title, http.StatusFound)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">renderTemplate(w, <span class="string">&quot;view&quot;</span>, p)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">editHandler</span><span class="params">(w http.ResponseWriter, r *http.Request, title <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">p, err := loadPage(title)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">p = &amp;Page&#123;Title: title&#125;</span><br><span class="line">&#125;</span><br><span class="line">renderTemplate(w, <span class="string">&quot;edit&quot;</span>, p)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">saveHandler</span><span class="params">(w http.ResponseWriter, r *http.Request, title <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">body := r.FormValue(<span class="string">&quot;body&quot;</span>)</span><br><span class="line">p := &amp;Page&#123;Title: title, Body: []<span class="type">byte</span>(body)&#125;</span><br><span class="line">err := p.save()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">http.Error(w, err.Error(), http.StatusInternalServerError)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">http.Redirect(w, r, <span class="string">&quot;/view/&quot;</span>+title, http.StatusFound)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">makeHandler</span><span class="params">(fn <span class="keyword">func</span>(http.ResponseWriter, *http.Request, <span class="type">string</span>)</span></span>) http.HandlerFunc &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">m := validPath.FindStringSubmatch(r.URL.Path)</span><br><span class="line"><span class="keyword">if</span> m == <span class="literal">nil</span> &#123;</span><br><span class="line">http.NotFound(w, r)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">fn(w, r, m[<span class="number">2</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="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/view/&quot;</span>, makeHandler(viewHandler))</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/edit/&quot;</span>, makeHandler(editHandler))</span><br><span class="line">http.HandleFunc(<span class="string">&quot;/save/&quot;</span>, makeHandler(saveHandler))</span><br><span class="line">host := <span class="string">&quot;127.0.0.1&quot;</span></span><br><span class="line">port := <span class="number">8080</span></span><br><span class="line">addr := fmt.Sprintf(<span class="string">&quot;%s:%v&quot;</span>, host, port)</span><br><span class="line">fmt.Printf(<span class="string">&quot;goWiki start listening at http://%s\n&quot;</span>, addr)</span><br><span class="line">log.Fatal(http.ListenAndServe(addr, <span class="literal">nil</span>))</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="edit.html">1.3.2 edit.html</h4><figure class="highlight html"><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="tag">&lt;<span class="name">h1</span>&gt;</span>Editing &#123;&#123;.Title&#125;&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">form</span> <span class="attr">action</span>=<span class="string">&quot;/save/&#123;&#123;.Title&#125;&#125;&quot;</span> <span class="attr">method</span>=<span class="string">&quot;POST&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span><span class="tag">&lt;<span class="name">textarea</span> <span class="attr">name</span>=<span class="string">&quot;body&quot;</span> <span class="attr">rows</span>=<span class="string">&quot;20&quot;</span> <span class="attr">cols</span>=<span class="string">&quot;80&quot;</span>&gt;</span>&#123;&#123;printf &quot;%s&quot; .Body&#125;&#125;<span class="tag">&lt;/<span class="name">textarea</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span><span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">value</span>=<span class="string">&quot;Save&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">form</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="view.html">1.3.3 view.html</h4><figure class="highlight html"><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="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;&#123;.Title&#125;&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>[<span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/edit/&#123;&#123;.Title&#125;&#125;&quot;</span>&gt;</span>edit<span class="tag">&lt;/<span class="name">a</span>&gt;</span>]<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;&#123;printf &quot;%s&quot; .Body&#125;&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="运行说明">1.3 运行说明</h3><p>单文件go程序，通过以下命令即可运行：</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">go run wiki.go</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">go build wiki.go</span><br><span class="line">./wiki.go</span><br></pre></td></tr></table></figure><h2 id="gingo-web框架">2 Gin：Go Web框架</h2><p>从上节可以看到，Go语言的<code>net/http</code>和<code>html/template</code>已经足够实现基本的Web应用，但Go自带的路由<code>http.ServerMux</code>机制简单，只能实现从请求路径（string）到处理函数（handler）的映射，无法根据HTTP的方法（Method），请求头（header）进行路由。GoWeb框架实现了比内置库更丰富的功能，例如<ahref="https://github.com/gin-gonic/gin">Gin</a>。</p><blockquote><p><strong><a href="https://github.com/gin-gonic/gin">Gin WebFramework</a></strong></p><p>Gin is a web framework written in Go (Golang). It features amartini-like API with performance that is up to 40 times faster thanksto <a href="https://github.com/julienschmidt/httprouter">httprouter</a>.If you need performance and good productivity, you will love Gin.</p></blockquote><p>此外，还有其他Go Web框架，如：<ahref="https://github.com/gorilla/mux">gorilla/mux</a>、<ahref="https://github.com/labstack/echo">echo</a>。</p><h2 id="数据库存储">3 数据库存储</h2><h3 id="sql">3.1 SQL</h3><p>Go语言没有内置数据库驱动。</p><p>Go语言定义了<code>database/sql</code>接口，分离出接口实现与接口调用，使得调用方改换数据库时无需修改代码。</p><blockquote><p>参阅：<ahref="https://github.com/longjoy/micro-go-book/blob/master/ch5-web/mysql/mysql.go">longjoy/micro-go-book/ch5-web/mysql/mysql.go</a></p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">db, err = sql.Open(<span class="string">&quot;mysql&quot;</span>,</span><br><span class="line"><span class="string">&quot;root:a123456@tcp(47.96.140.41:3366)/user?charset=utf8&quot;</span>)</span><br><span class="line">checkErr(err)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">queryByName</span><span class="params">(name <span class="type">string</span>)</span></span> User &#123;</span><br><span class="line">user := User&#123;&#125;</span><br><span class="line">stmt, err := db.Prepare(<span class="string">&quot;select * from user where name=?&quot;</span>)</span><br><span class="line">checkErr(err)</span><br><span class="line"></span><br><span class="line">rows, _ := stmt.Query(name)</span><br><span class="line"></span><br><span class="line">fmt.Println(<span class="string">&quot;\nafter deleting records: &quot;</span>)</span><br><span class="line"><span class="keyword">for</span> rows.Next() &#123;</span><br><span class="line"><span class="keyword">var</span> id <span class="type">int</span></span><br><span class="line"><span class="keyword">var</span> name <span class="type">string</span></span><br><span class="line"><span class="keyword">var</span> habits <span class="type">string</span></span><br><span class="line"><span class="keyword">var</span> createdTime <span class="type">string</span></span><br><span class="line">err = rows.Scan(&amp;id, &amp;name, &amp;habits, &amp;createdTime)</span><br><span class="line">checkErr(err)</span><br><span class="line">fmt.Printf(<span class="string">&quot;[%d, %s, %s, %s]\n&quot;</span>, id, name, habits, createdTime)</span><br><span class="line">user = User&#123;id, name, habits, createdTime&#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">return</span> user</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">store</span><span class="params">(user User)</span></span> &#123;</span><br><span class="line"><span class="comment">//插入数据</span></span><br><span class="line">stmt, err := db.Prepare(<span class="string">&quot;INSERT INTO user SET name=?,habits=?,created_time=?&quot;</span>)</span><br><span class="line">t := time.Now().UTC().Format(<span class="string">&quot;2006-01-02&quot;</span>)</span><br><span class="line">res, err := stmt.Exec(user.Name, user.Habits, t)</span><br><span class="line">checkErr(err)</span><br><span class="line"></span><br><span class="line">id, err := res.LastInsertId()</span><br><span class="line">checkErr(err)</span><br><span class="line"></span><br><span class="line">fmt.Printf(<span class="string">&quot;last insert id is: %d\n&quot;</span>, id)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="nosql">3.2 NoSQL</h3><p>Go语言的结构体和NoSQL的JSON可以很好地直接对应起来，因此，Go语言中一般可以直接操作NoSQL，不依赖ORM。</p><blockquote><p>参阅：<ahref="https://github.com/longjoy/micro-go-book/blob/master/ch5-web/mongo/mongo.go">longjoy/micro-go-book/ch5-web/mongo/mongo.go</a></p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">connect</span><span class="params">(cName <span class="type">string</span>)</span></span> (*mgo.Session, *mgo.Collection) &#123;</span><br><span class="line">session, err := mgo.Dial(<span class="string">&quot;mongodb://47.96.140.41:27017/&quot;</span>) <span class="comment">//Mongodb&#x27;s connection</span></span><br><span class="line">checkErr(err)</span><br><span class="line">session.SetMode(mgo.Monotonic, <span class="literal">true</span>)</span><br><span class="line"><span class="comment">//return a instantiated collect</span></span><br><span class="line"><span class="keyword">return</span> session, session.DB(<span class="string">&quot;test&quot;</span>).C(cName)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">queryByName</span><span class="params">(name <span class="type">string</span>)</span></span> []User &#123;</span><br><span class="line"><span class="keyword">var</span> user []User</span><br><span class="line">s, c := connect(<span class="string">&quot;user&quot;</span>)</span><br><span class="line"><span class="keyword">defer</span> s.Close()</span><br><span class="line">err := c.Find(bson.M&#123;<span class="string">&quot;name&quot;</span>: name&#125;).All(&amp;user)</span><br><span class="line">checkErr(err)</span><br><span class="line"><span class="keyword">return</span> user</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">store</span><span class="params">(user User)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">s, c := connect(<span class="string">&quot;user&quot;</span>)</span><br><span class="line"><span class="keyword">defer</span> s.Close()</span><br><span class="line">user.Id = bson.NewObjectId().Hex()</span><br><span class="line"><span class="keyword">return</span> c.Insert(&amp;user)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="beegoormgo-orm框架">3.3 beego/orm：Go ORM框架</h3><blockquote><p><a href="https://github.com/beego/beego">Beego</a></p><p>Beego is used for rapid development of enterprise application in Go,including RESTful APIs, web apps and backend services.</p><p>It is inspired by Tornado, Sinatra and Flask. beego has someGo-specific features such as interfaces and struct embedding.</p></blockquote><p>Beego是一个简单易用的企业级Go应用开发框架，其中包含了ORM框架。</p><p>Beego的ORM的具体使用方法可以参阅其文档：</p><blockquote><p><a href="https://beego.me/docs/mvc/model/orm.md">ORM 使用方法</a></p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;整理Go Web开发的相关知识点。&lt;/p&gt;</summary>
    
    
    
    
    <category term="HTTP" scheme="https://heary.cn/tags/HTTP/"/>
    
    <category term="Web" scheme="https://heary.cn/tags/Web/"/>
    
    <category term="Golang" scheme="https://heary.cn/tags/Golang/"/>
    
    <category term="ORM" scheme="https://heary.cn/tags/ORM/"/>
    
  </entry>
  
  <entry>
    <title>matplotlib绘制图像注意力</title>
    <link href="https://heary.cn/posts/matplotlib%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F%E6%B3%A8%E6%84%8F%E5%8A%9B/"/>
    <id>https://heary.cn/posts/matplotlib%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F%E6%B3%A8%E6%84%8F%E5%8A%9B/</id>
    <published>2021-04-12T02:29:56.000Z</published>
    <updated>2022-08-07T04:02:09.862Z</updated>
    
    <content type="html"><![CDATA[<p>通过matplotlib可以在图像表层对图像注意力机制进行可视化绘制，即将图像注意力叠加在图像表层。</p><span id="more"></span><h1 id="matplotlib绘制图像注意力">matplotlib绘制图像注意力</h1><h2 id="效果展示">1 效果展示</h2><img src="/posts/matplotlib%E7%BB%98%E5%88%B6%E5%9B%BE%E5%83%8F%E6%B3%A8%E6%84%8F%E5%8A%9B/demo_img_att.png" class="" title="Demo image with attention masked on it"><h2 id="实现原理">2 实现原理</h2><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">import</span> matplotlib.pyplot <span class="keyword">as</span> plt</span><br><span class="line"><span class="keyword">import</span> cv2</span><br><span class="line"><span class="keyword">import</span> numpy <span class="keyword">as</span> np</span><br><span class="line"></span><br><span class="line"><span class="comment"># read target image</span></span><br><span class="line">demo_img_path = <span class="string">r&quot;res\img\36979.jpg&quot;</span></span><br><span class="line">demo_img = plt.imread(demo_img_path)</span><br><span class="line">demo_img_h, demo_img_w, demo_img_c = demo_img.shape</span><br><span class="line"></span><br><span class="line">demo_img_att = np.array([</span><br><span class="line">    [<span class="number">0.1</span>, <span class="number">0.4</span>, <span class="number">0.4</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">0</span>, <span class="number">0.4</span>, <span class="number">0.4</span>, <span class="number">0.4</span>, <span class="number">0</span>],</span><br><span class="line">    [<span class="number">0.4</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.3</span>, <span class="number">0.4</span>],</span><br><span class="line">    [<span class="number">0.2</span>, <span class="number">0.1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.4</span>],</span><br><span class="line">    [<span class="number">0.3</span>, <span class="number">0.4</span>, <span class="number">0.1</span>, <span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">])</span><br><span class="line"><span class="comment"># resize the image attention to the target image size with interpolation</span></span><br><span class="line">demo_img_att = cv2.resize(demo_img_att,</span><br><span class="line">                          dsize=(demo_img_w, demo_img_h),</span><br><span class="line">                          interpolation=cv2.INTER_CUBIC)</span><br><span class="line"></span><br><span class="line"><span class="comment"># plot with matplotlib</span></span><br><span class="line">plt.figure(figsize=(<span class="number">9</span>, <span class="number">5</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment"># plot target image</span></span><br><span class="line">plt.subplot(<span class="number">1</span>, <span class="number">2</span>, <span class="number">1</span>)</span><br><span class="line">plt.imshow(demo_img)</span><br><span class="line">plt.axis(<span class="string">&quot;off&quot;</span>)</span><br><span class="line">plt.title(<span class="string">&quot;image&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># plot image with attention masked on it</span></span><br><span class="line">plt.subplot(<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>)</span><br><span class="line">plt.imshow(demo_img)</span><br><span class="line">plt.imshow(demo_img_att, alpha=<span class="number">0.8</span>, cmap=<span class="string">&quot;gray&quot;</span>)</span><br><span class="line">plt.axis(<span class="string">&quot;off&quot;</span>)</span><br><span class="line">plt.title(<span class="string">&quot;image with attention&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># shrink padding etc. to a tight layout</span></span><br><span class="line">plt.tight_layout()</span><br><span class="line"></span><br><span class="line"><span class="comment"># save figure and show on display</span></span><br><span class="line">plt.savefig(<span class="string">&quot;demo_img_att.png&quot;</span>)     <span class="comment"># to disk</span></span><br><span class="line">plt.show()                          <span class="comment"># on display</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>主要的绘制要点在于：</p><ol type="1"><li>绘制前，需要对注意力层进行插值，调整到与原图相同的尺寸；</li><li>绘制时，先绘制原图，再绘制插值后的注意力层，且绘制时设置好colormap(<code>cmap</code>)。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;通过matplotlib可以在图像表层对图像注意力机制进行可视化绘制，即将图像注意力叠加在图像表层。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Python" scheme="https://heary.cn/tags/Python/"/>
    
    <category term="OpenCV" scheme="https://heary.cn/tags/OpenCV/"/>
    
    <category term="matplotlib" scheme="https://heary.cn/tags/matplotlib/"/>
    
    <category term="Attention" scheme="https://heary.cn/tags/Attention/"/>
    
  </entry>
  
  <entry>
    <title>投资学笔记</title>
    <link href="https://heary.cn/posts/%E6%8A%95%E8%B5%84%E5%AD%A6%E7%AC%94%E8%AE%B0/"/>
    <id>https://heary.cn/posts/%E6%8A%95%E8%B5%84%E5%AD%A6%E7%AC%94%E8%AE%B0/</id>
    <published>2021-03-08T15:19:12.000Z</published>
    <updated>2023-12-24T14:13:24.701Z</updated>
    
    <content type="html"><![CDATA[<p>通过中央财经大学的投资学课程，系统性地学习基本的投资学原理。</p><span id="more"></span><h1 id="投资学笔记">投资学笔记</h1><p><em>对投资做的好的大师，首先必须是控制风险的大师。</em></p><h2 id="投资的内涵及其与宏观经济的关系">1投资的内涵及其与宏观经济的关系</h2><h3 id="投资与投资主体">1.1 投资与投资主体</h3><h4 id="投资的定义">投资的定义</h4><p>为了（可能不确定的）将来的消费（价值）而牺牲现在一定的消费（价值）。</p><h4 id="投资主体">投资主体</h4><h5 id="家庭">家庭</h5><p>家庭收入</p><ul><li>投资<ul><li>直接投资：资金的使用者和所有者一致；</li><li>间接投资：资金的使用者和所有者不一致。</li></ul></li><li>消费<ul><li>购买商品或服务</li></ul></li></ul><h5 id="企业">企业</h5><p>企业收入用于</p><ul><li>投资<ul><li>研发投资、员工培训</li><li>建造厂房、购买设备</li><li>存货投资</li><li>金融投资</li></ul></li><li>消费<ul><li>工资福利</li><li>原材料</li></ul></li></ul><h5 id="企业与家庭">企业与家庭</h5><p>企业的工资福利流入家庭，家庭的投资和消费流入企业。</p><h5 id="政府">政府</h5><p>资金来源</p><ul><li>税收</li><li>政府融资</li></ul><p>政府收入用于</p><ul><li>政府投资<ul><li>直接投资</li><li>间接投资</li></ul></li><li>政府支出<ul><li>医疗等社会保障</li><li>公务员支出</li></ul></li></ul><h4 id="投资活动">投资活动</h4><h5 id="直接投资">直接投资</h5><ul><li>居民或企业建造房屋等不动产</li><li>企业研发费用、存货投资</li><li>政府投资基础建设</li></ul><h5 id="间接投资">间接投资</h5><ul><li>居民投资购买商品房或金融资产</li><li>企业购买金融资产</li><li>政府购买国内外金融资产</li></ul><h5 id="实物资产投资">实物资产投资</h5><ul><li>居民建造房屋、投资购买商品房</li><li>企业建设厂房、购买设备、存货投资</li><li>政府投资基础设施</li></ul><h5 id="金融资产投资">金融资产投资</h5><ul><li>居民投资股票、债券、基金或存款</li><li>企业购买股票、债券、衍生品等金融资产</li><li>政府购买国内外金融资产</li></ul><h3 id="投资与宏观经济运行的内部逻辑">1.2投资与宏观经济运行的内部逻辑</h3><h4 id="资产负债表">资产负债表</h4><p>企业部门的金融负债和居民部门的金融资产接近。</p><p>宏观经济上，企业部门的负债主要来源于居民部门的金融资产。</p><h4 id="金融泡沫">金融泡沫</h4><p>当股票等金融资产价格呈现上涨趋势时，</p><ul><li>家庭投入更多收入到金融资产</li><li>企业融资或变卖实物资产到金融资产</li><li>银行的货币创造进一步推高金融资产价格</li></ul><p>形成金融泡沫。</p><h4 id="虚拟经济">虚拟经济</h4><h5 id="虚拟经济与实体经济">虚拟经济与实体经济</h5><p>虚拟经济与实体经济是此消彼长，又相辅相成的。</p><h5 id="托宾q系数">托宾Q系数</h5><p>Q = 公司的市场价值/公司的重置成本</p><ul><li>Q&gt;1，投资者理性选择出售股票，然后重新建立同样的公司获得更多收益；</li><li>Q&lt;1，投资者倾向并购公司扩张，而非重建。</li></ul><p>实际情况Q一般大于1，因为存在专利、壁垒等……</p><h3 id="投资与短期经济增长">1.3 投资与短期经济增长</h3><h4 id="投资-储蓄恒等式">投资-储蓄恒等式</h4><p><strong>C+S=C+I</strong></p><p>居民消费C+居民储蓄S=企业所生产产品C+企业投资I</p><p>加入政府和国际经济体时：</p><p><strong>C+S+T+Kr=C+I</strong></p><h3 id="政府投资与短期经济增长案例">1.4 政府投资与短期经济增长案例</h3><p>维持经济稳定，应对金融危机的4万亿投资计划</p><ul><li>29.50%，11800亿，来自中央预算内投资、重要政府性基金、中央财政其他公共投资以及中央财政灾后恢复重建基金；</li><li>70.50%，28200亿，来自地方财政预算、中央财政代发地方政府债券、政策性贷款、企业债券和中期票据、银行贷款以及民间投资。</li></ul><p>影响：</p><ol type="1"><li>央行资产负债表扩表，货币规模增幅超两倍；</li><li>社会融资规模高速增长，增幅超过200%；</li><li>地方政府债务规模扩大，违约可能性大大提高。</li></ol><p>负面效应：</p><ol type="1"><li>各部门债务高企、违约概率大大提高；</li><li>产能过剩（投资挤压消费）、结构失衡、亟待供给侧改革；</li><li>民营企业倒闭潮、工人失业；</li><li>房价飙升（M2增速很快），催生一系列民生问题。</li></ol><h3 id="投资与长期经济增长和经济波动">1.5投资与长期经济增长和经济波动</h3><h4 id="投资与长期经济增长">投资与长期经济增长</h4><h5 id="索洛模型">索洛模型</h5><p>只有生产和消费。</p><p>储蓄总是等于投资。</p><p>生产三大要素：资本K、劳动L、知识A。</p><p>资本投入存在边际效应。</p><p>索洛模型表明：在具有相同生产函数、储蓄和折旧率情况下，经济体最终所达到的均衡状态与初始状态无关。</p><p>提高储蓄率，可以增加资本投入，继而提高人均产出。</p><p>但是，储蓄率过分提高，会牺牲一代人的消费。</p><h5 id="内生增长模型">内生增长模型</h5><p>将技术进步纳入内生变量。</p><p>纳入技术进步后，资本的边际报酬不再递减。</p><p>案例：供给侧改革</p><h4 id="投资与经济波动">投资与经济波动</h4><h5 id="萨穆尔森-凯恩斯经济周期理论">萨穆尔森-凯恩斯经济周期理论</h5><p>由于投资存在乘数效应且产出存在加速效应，因此到达一定峰值后，经济就会衰退，到达低谷后，一些投资需求被刺激，又进入上升通道。</p><p>认为投资和消费本身的特性产生了经济周期。</p><h5 id="实际经济周期理论">实际经济周期理论</h5><p>认为经济周期是外部因素引起的。</p><p>熊彼特的创新周期论。</p><p>卡莱斯基的政治周期论。</p><h3 id="投资规模与投资效率">1.6 投资规模与投资效率</h3><h4 id="投资规模">投资规模</h4><p>年度投资规模（短期、流量）</p><p>在建投资规模（长期、存量）</p><h4 id="投资结构">投资结构</h4><p>投资主体结构：</p><ul><li>企业<ul><li>利润最大化</li><li>自主投资、市场化运作</li><li>促进经济发展</li></ul></li><li>政府<ul><li>社会福利最大化</li><li>国企投资、政策影响</li><li>调整经济结构</li></ul></li></ul><h4 id="投资效率的衡量">投资效率的衡量</h4><h5 id="资本产出比">资本产出比</h5><p>Capital-Output Ratio, COR</p><h5 id="投资产出比">投资产出比</h5><p>Incremental Capital-Output Ratio, ICOR</p><h5 id="资本边际收益均一化原则">资本边际收益均一化原则</h5><p>当市场有效且达到均衡时，各个部门的资本边际收益率呈现均一化的特征。</p><p>（否则资本会从低收益部门流向高收益部门）</p><h5 id="衡量方式">衡量方式</h5><ul><li><p>调整推算法：对统计数据要求很高；</p></li><li><p>函数估计法：需要假定生产函数。</p></li></ul><h2 id="行业投资分析">2 行业投资分析</h2><h3 id="行业的涵义与分类">2.1 行业的涵义与分类</h3><h4 id="行业的涵义">2.1.1 行业的涵义</h4><p>一个企业群，群内各成员所生产的商品对消费者时可互相替代的。</p><h4 id="行业的分类">2.1.2 行业的分类</h4><p>道琼斯分类法</p><p>联合国国际标准行业分类法</p><p>我国国民经济行业分类法</p><p>我国上市公司行业分类法</p><ul><li>证监会标准</li><li>申银万国分类标准</li><li>Wind分类标准</li></ul><h3 id="行业的生命周期">2.2 行业的生命周期</h3><h4 id="行业生命周期的阶段">2.2.1 行业生命周期的阶段</h4><p>四个阶段：</p><ol type="1"><li>创业阶段：开始增长<ol type="1"><li>大众尚未认知该产品/服务，市场需求较小</li><li>投资规模小，风险很大</li></ol></li><li>成长阶段：快速增长<ol type="1"><li>大众认识到该产品/服务，市场需求迅速扩大</li><li>销售收入迅速扩大，开始盈利</li><li>产品需要完善，投资需求强烈，风险较大（新生事物政策风险大）</li></ol></li><li>成熟阶段：缓慢增长<ol type="1"><li>市场趋于饱和，市场竞争（相对）垄断，少数企业分享高额利润</li><li>产品成熟稳定，投资需求不大，风险较低，投资可获得稳定回报</li></ol></li><li>衰退阶段：缓慢下滑<ol type="1"><li>本产品更新跟不上大量出现的替代品</li><li>市场需求减少，销售利润下降，风险增大，不宜大量投资</li></ol></li></ol><h4 id="行业发展阶段分析">2.2.2 行业发展阶段分析</h4><table><thead><tr class="header"><th>特征</th><th>创业阶段</th><th>成长阶段</th><th>成熟阶段</th><th>衰退阶段</th></tr></thead><tbody><tr class="odd"><td>行业规模</td><td>较小</td><td>扩大</td><td>饱和</td><td>缩小</td></tr><tr class="even"><td>产出增长</td><td>较快</td><td>很快</td><td>较慢</td><td>很慢，甚至为负</td></tr><tr class="odd"><td>利润水平</td><td>低</td><td>高</td><td>低</td><td>亏损</td></tr><tr class="even"><td>技术创新</td><td>较快</td><td>逐渐稳定</td><td>稳定</td><td>淘汰或被替代</td></tr><tr class="odd"><td>竞争者数量</td><td>很多</td><td>增多</td><td>下降</td><td>降至不足</td></tr><tr class="even"><td>开工率</td><td>提高</td><td>满负荷</td><td>下降</td><td>降至不足</td></tr><tr class="odd"><td>资本进退</td><td>进大于出</td><td>进大于出</td><td>进出平衡</td><td>进小于出</td></tr></tbody></table><h4 id="影响行业兴衰的因素">2.2.3 影响行业兴衰的因素</h4><ol type="1"><li>技术进步</li><li>社会习惯</li><li>产业政策</li><li>经济全球化</li></ol><p>其他高壁垒行业、政府介入行业等，难以用行业生命周期解释。</p><h3 id="行业与经济周期">2.3 行业与经济周期</h3><h4 id="不同行业对经济周期的敏感度">2.3.1不同行业对经济周期的敏感度</h4><table><colgroup><col style="width: 13%" /><col style="width: 27%" /><col style="width: 28%" /><col style="width: 30%" /></colgroup><thead><tr class="header"><th></th><th>增长型</th><th>周期型</th><th>防御型</th></tr></thead><tbody><tr class="odd"><td>与经济周期的关系</td><td>受经济周期影响不大</td><td>与经济周期直接相关</td><td>产品需求相对稳定，受经济周期影响较小</td></tr><tr class="even"><td>增长的核心来源</td><td>技术进步等不受经济周期影响的因素</td><td>居民收入等受经济周期直接影响的因素</td><td>居民刚性需求</td></tr><tr class="odd"><td>案例</td><td>计算机相关行业</td><td>汽车等行业</td><td>医药、生活必需品等行业</td></tr><tr class="even"><td>经济繁荣时</td><td>增长</td><td>增长</td><td>相对稳定</td></tr><tr class="odd"><td>经济衰退时</td><td>增长</td><td>衰落</td><td>相对稳定</td></tr></tbody></table><h4 id="经济周期敏感度的决定因素">2.3.2 经济周期敏感度的决定因素</h4><ol type="1"><li>销售额对经济周期的敏感度</li><li>经营杠杆</li><li>财务杠杆</li></ol><h4 id="行业轮动美林的投资时钟理论">2.3.3行业轮动——美林的投资时钟理论</h4><p>行业轮动是根据商业周期状态预测业绩卓越的行业或部门，并将投资组合转向这些行业或部门。</p><p>宏观经济周期分为四个阶段：</p><ol type="1"><li>衰退<ol type="1"><li>低增长、低通胀</li><li>债券投资</li></ol></li><li>复苏<ol type="1"><li>高增长、低通胀</li><li>股票投资</li></ol></li><li>过热<ol type="1"><li>高增长、高通胀</li><li>大宗商品</li></ol></li><li>滞胀<ol type="1"><li>低增长、高通胀</li><li>现金</li></ol></li></ol><p>投资策略：</p><ol type="1"><li>周期性<ol type="1"><li>经济增长加快时，投资股票和大宗商品，选择周期型行业（如汽车、钢铁）；</li><li>经济增长减慢时，投资债券或现金，选择防御型行业（如医药、公共事业）；</li></ol></li><li>久期<ol type="1"><li>通胀率下降时，投资债券或股票，选择久期较长的债券或成长型股票；</li><li>通胀率上升时，折现率上升，投资大宗商品和现金，或估值波动小且久期短的价值型股票。</li></ol></li></ol><p>适用情况：</p><ol type="1"><li>在美国有效，1973.4-2004.7超过30年，美国经济周期可较明确分为四个阶段，每阶段平均20个月左右，一个经济周期约6年。</li><li>在中国不完全适用，因为：<ol type="1"><li>央行货币政策逆周期调整对金融市场影响巨大（2011-2015经济稳步下降，2013年收紧货币使流动性趋紧、股市大跌，2015年降准降息等改革政策推动短期大牛市），</li><li>经济转型的结构性调整也有很大影响（复苏阶段供给侧改革淘汰落后产能）。</li></ol></li></ol><h3 id="行业的结构及其分析">2.4 行业的结构及其分析</h3><h4 id="行业的结构分析">2.4.1 行业的结构分析</h4><table style="width:100%;"><colgroup><col style="width: 8%" /><col style="width: 17%" /><col style="width: 12%" /><col style="width: 26%" /><col style="width: 35%" /></colgroup><thead><tr class="header"><th>特征</th><th>完全竞争</th><th>垄断竞争</th><th>寡头垄断</th><th>完全垄断</th></tr></thead><tbody><tr class="odd"><td>企业数目</td><td>众多</td><td>很多</td><td>较少</td><td>单个企业</td></tr><tr class="even"><td>生产要素流动性</td><td>完全自由流动</td><td>自由流动</td><td>较难流动</td><td>不流动</td></tr><tr class="odd"><td>产品差异性</td><td>同质无差别</td><td>存在差别</td><td>同质或存在差别</td><td>无</td></tr><tr class="even"><td>企业定价能力</td><td>企业仅接受价格，无法制定价格</td><td>企业对价格有控制能力</td><td>企业对价格具有垄断能力</td><td>企业垄断定价，但受到法律管制</td></tr><tr class="odd"><td>典型行业</td><td>初级产品（例如：农产品）</td><td>家电、洗发水等消费品</td><td>资本、技术密集型行业，少数储量集中的矿产品</td><td>公共事业，资本、技术高度密集型行业，稀有金属矿藏开采行业</td></tr></tbody></table><h4 id="波特的五力模型">2.4.2 波特的五力模型</h4><ol type="1"><li>供应商：供应商议价能力<br /></li><li>购买者：买方议价能力</li><li>竞争对手：现有公司之间的竞争</li><li>潜在进入者：新进入者的威胁</li><li>替代品：替代品的威胁</li></ol><h4 id="其他行业分析工具">2.4.3 其他行业分析工具</h4><h5 id="pestle模型">PESTLE模型</h5><ol type="1"><li>政治P</li><li>经济因素E</li><li>社会文化因素S</li><li>科技T</li><li>法律L</li><li>环保E</li></ol><h5 id="行业集中度分析模型">行业集中度分析模型</h5><p>集中度体现在行业前K名的累计市场份额。</p><p>集中度曲线：</p><ul><li>上升，则行业竞争激烈，优势企业纷纷渠道扩张、降价等方式扩大市场。<ul><li>迅速上升蕴含发展机会，加大市场投入、加快渠道建设往往成效。</li></ul></li><li>稳定，则市场竞争结构稳定，领导企业优势地位业已建立。<ul><li>稳定中的行业机会不大，企业扩张会受到领导企业的集体抵制，需细分化、差异化发展。</li></ul></li></ul><p>散点市场-&gt;块状同质化市场-&gt;团状异质化市场</p><h5 id="行业关键成功要素模型">行业关键成功要素模型</h5><p>列表格，对要素打分。</p><h3 id="行业定量分析">2.5 行业定量分析</h3><h4 id="市盈率">2.5.1 市盈率</h4><p><strong>行业市盈率=行业（价格）指数 / 行业利润率</strong></p><ul><li><p>行业指数代表投资者对行业的估值；</p></li><li><p>行业利润率代表行业的盈利能力。</p></li></ul><p>投资策略：同等条件下，尽量选择行业指数和行业利润率较高，而市盈率较低的行业。</p><p>注意：行业之间的市盈率不具备可比性。</p><h4 id="回归分析法估计行业收益率">2.5.2 回归分析法估计行业收益率</h4><p>利用行业的历史数据回归估计行业收益率。</p><p>注意：有效市场中，未来股价不受过去股价影响，用现在收益率难以预测未来收益率。因此一般很少用定量分析预测行业未来，常用定性分析和经验。</p><h2 id="项目投资评估方法">3 项目投资评估方法</h2><p>略</p><h2 id="融资与创新">4 融资与创新</h2><p>金融工具一般按期限来分类：</p><ul><li>一年期以上，称为资本市场，高风险高收益；</li><li>一年期以下，称为货币市场，大多流动性好、信用安全。</li></ul><h3 id="债券市场">4.1 债券市场</h3><h4 id="债券市场概况">4.1.1 债券市场概况</h4><p>债券市场可追溯到1792年纽约股票交易所。</p><h5 id="美国债券市场">美国债券市场</h5><ol type="1"><li>美国政府债券：<ol type="1"><li>短期国库券（Treasury Bills）：90天~1年不等；</li><li>中期国库票据（Treasury Notes）：2~10年不等；</li><li>长期国债（Treasury Bonds）：10~30年不等。</li><li>以政府信用担保，还可免交州及地方税。</li></ol></li><li>市政债券：州和地方政府发行，类似我国的地方债<ol type="1"><li>一般责任债券：由发行者的信用（财政能力、收税能力）支撑；</li><li>收入债券：由地方基建、公共服务的收益支撑；</li><li>安全性：一般责任债券&gt;收入债券</li><li>也可免税</li></ol></li><li>政府机构债券<ol type="1"><li>通常是联邦政府级机构发行，用于资助和公共政策相关项目，如：农业、小企业、首次购房者贷款。</li></ol></li><li>公司债券<ol type="1"><li>即使大公司也有风险，会受到经济、管理及竞争等的影响</li><li>发行需经过评级认定，有：标准普尔、穆迪、惠誉</li></ol></li></ol><h4 id="债券分类及简介">4.1.2 债券分类及简介</h4><h5 id="国债">国债</h5><p>形式：</p><ol type="1"><li>凭证式国债：记名、可挂失、仅银行网点、财政部门国债服务部发行，不上市流通；</li><li>记账式国债：1994年开始发行，电脑系统账户，记名、可挂失、效率高、简便；</li><li>不记名（实物）国债：不记名、不挂失、可流通。</li></ol><p>风险：安全性非常好，近乎于现金等价物。</p><p>期限：</p><ol type="1"><li>短期国债</li><li>中长期国债</li></ol><p>目的：</p><ol type="1"><li>央行利用短期国债做公开市场运作</li><li>作为市场无风险利率基准</li><li>第二准备金</li><li>筹集财政资金</li></ol><p>发行：</p><ol type="1"><li>固定收益出售法</li><li>公募拍卖：竞价投标；<ol type="1"><li>美式拍卖：“加价拍卖”，以加权平均中标价格为当期国债发行价格<ol type="1"><li>竞争性报价中标者按各自高低不同的投标价购买国债，通常是大型机构投资者；</li><li>非竞争性报价中标者按加权平均中标价格购买，通常是中小型投资者。</li></ol></li><li>荷兰式拍卖：“减价拍卖”，按竞价递减直到第一个竞价人应价（或到达底价）时击槌成交，以最低成功出价金额作为成交价，作为当期国债发行价，中标者统一按发行价购买国债；<ol type="1"><li>价高者优先，相同价格先出价先得；</li></ol></li><li>混合式拍卖：两种混合，以加权平均中标价格作为债券发行价格；<ol type="1"><li>高于或等于发行价的中标，按发行价购买国债（荷兰式）；</li><li>低于发行价一定范围的中标，按各中标价格购买（美国式）；</li><li>低于发行价超出一定范围的中标，全部落标。</li></ol></li></ol></li><li>连续经销：柜台出卖；</li><li>承受发行：直接推销；</li></ol><h5 id="地方政府债券">地方政府债券</h5><p>地方政府募资发行。</p><p>目的：</p><ol type="1"><li>帮助国家实施财政或货币政策；</li><li>公共设施建设；</li><li>弥补财政赤字。</li></ol><p>规模：远远大于国债发行数量。</p><p>还本付息受财政、地方发展水平影响，信用比国债低些。</p><h5 id="央行票据">央行票据</h5><p>中央银行票据，调节商业银行超额准备金而发行的短期债务凭证，实质时中央银行债券。</p><p>目的：不是为了筹集资金，而是央行调节基础货币的货币政策工具，为了减少商业银行可贷资金量。</p><h5 id="大额可转让存单cds">大额可转让存单CDs</h5><p>因资金流向债券，商业银行需要吸引储蓄稳定存款。</p><p>存单注明存款期限和利率，可到期取本息，也可到期前转让，可在二级市场流通。</p><h5 id="同业存单">同业存单</h5><p>银行和基金公司间流通，同业拆借。</p><h5 id="债券回购">债券回购</h5><p>回购协议：先卖出债券，再回购。相当于以债券作为抵押品。</p><h5 id="金融债">金融债</h5><p>金融机构发行的债券。</p><h5 id="公司债">公司债</h5><p>企业发行的债券，受证监会管理。</p><p>风险和收益都高于国债。</p><h5 id="企业债">企业债</h5><p>在西方，企业债即公司债。</p><p>在我国，企业债券是中央政府部门所属机构、国有独资企业或国有控股企业发行的债券。</p><p>企业债的发行与政府部门的审批项目直接相关，发行由发改委审批。</p><h5 id="商业票据">商业票据</h5><p>金融公司或高信用企业开出的无担保短期票据。</p><p>期限在1个月~1年，通常滚动发行，为旧票据还本付息。</p><h5 id="短期融资券">短期融资券</h5><p>企业在银行间市场发行，由金融机构购买，不向社会发行，一年期内还本付息的有价证券，是短期贷款的替代品。</p><p>无担保、短期、需评级。</p><h5 id="中期票据">中期票据</h5><p>企业在银行间市场发行，是中期贷款的替代品。</p><h5 id="可转债">可转债</h5><p>可选是否将债券一定比例转为股权，赋予了债券一定程度的期权能力。</p><h5 id="国际债券">国际债券</h5><p>可以在海外发行的债券。</p><h5 id="私募债">私募债</h5><p>中小型企业的募资需求。</p><h3 id="资产证券化">4.2 资产证券化</h3><h4 id="基本理论">4.2.1 基本理论</h4><p>资产证券化（AssetSecuritization）是指以特定资产组合或特定现金流为支持，发行可交易债券的一种融资形式。</p><p>起源于1970年美国发行的以抵押贷款组合为基础资产（如：住房抵押贷款）的抵押支持债券（MBS,Mortgage-BackedSecurity），此后从抵押贷款发展到其他资产上（如：汽车贷款、消费贷款等），出现资产支持债券（ABS,‎Asset-Backed Security）。</p><p>本质特征：</p><ul><li>资产证券化以<strong>可预见的现金流为支持</strong>而发行证券在资本市场融资的一个过程。</li><li>可预见的现金流的资产可以是实物（如：高速公路的收费），也可以是非实物（如：住房抵押贷款、汽车消费带宽、信用卡等的偿还现金流）。</li><li>本质与精髓：表面上看起来是以资产为支持，实际上是<strong>以资产所产生的现金流</strong>为支持。</li></ul><p>影响：</p><ul><li>模糊了直接融资与间接融资之间的清晰界限，也显示出直接融资的发展前景。</li><li>资产证券化利用资本市场对资产的收益与风险进行分离与重组。</li><li>资产证券化是债券市场深化的助推器。</li></ul><h4 id="基本结构">4.2.2 基本结构</h4><img src="/posts/%E6%8A%95%E8%B5%84%E5%AD%A6%E7%AC%94%E8%AE%B0/ch4-structure_of_AS.jpg" class="" title="资产证券化的结构"><h5 id="原始债务人obligors">原始债务人（Obligors）</h5><p>承担债务，需还本付息。例如，抵押贷款中的借款方。</p><h5 id="原始债权人originators">原始债权人（Originators）</h5><p>享有债权。例如：抵押贷款中的放款银行。</p><p>作为资产证券化的发起人，原始债权人把需要证券化的资产出售给特别目的机构，实现资产风险与收益的充足。</p><h5 id="特别目的机构special-purpose-vehicle-spv">特别目的机构（SpecialPurpose Vehicle, SPV）</h5><p>从发起人处购买可证券化资产，并发行以此为支持的证券的特殊实体。</p><p>一般是不会破产的高信用等级实体。</p><p>因为原始债权人将资产真实销售（truesale）给SPV，所以证券化资产的风险与原始债权人的风险可以隔离开来，实现破产隔离（bankruptremote），即使原始债权人破产，也不会影响到投资人对证券化资产的权益，提高了证券化资产的资信评级，降低了融资成本。</p><h5 id="投资者investors">投资者（Investors）</h5><p>购买证券的机构或个人。</p><p>因为这类证券通常高收益低风险，因此一般是机构投资者购买，如保险公司、投资基金和银行机构。</p><h5 id="专门服务人servicer">专门服务人（Servicer）</h5><p>一般由发起人兼任。</p><p>负责按期收取证券化资产所产生的现金流，并转移给SPV或SPV指定的信托实体。</p><h5 id="信托机构trustee">信托机构（Trustee）</h5><p>由SPV指定的负责对专门服务人收取的现金流进行管理，并向证券投资者按时支付的机构。</p><h5 id="信用评级机构rating-agency">信用评级机构（Rating Agency）</h5><p>通过对资产证券化各个环节进行评估而给出信用等级的机构。</p><p>对证券进行信用增级，降低发行成本。</p><h5 id="担保机构guarantors">担保机构（Guarantors）</h5><p>为SPV发行证券提供担保的机构，为证券进行信用增级。可以是政府担保机构或私人担保公司。</p><h5 id="证券承销商underwriters">证券承销商（Underwriters）</h5><p>为SPV所发行证券进行承销的实体，确保证券销售成功。一般是投资银行，或组建的承销团。</p><h4 id="基本过程">4.2.3 基本过程</h4><ol type="1"><li>组建SPV</li><li>SPV筛选可证券化的资产组成资产池（asset pool）</li><li>SPV与资产相结合阶段（原始权益人真实销售资产给SPV）</li><li>SPV发行资产支持证券阶段</li><li>SPV清偿债券阶段</li></ol><h4 id="中美资产证券化产品">4.2.4 中美资产证券化产品</h4><h5 id="美国">美国</h5><ol type="1"><li>住房抵押贷款证券（MBS）；</li><li>资产支持证券（ABS）：汽车贷款证券、信用贷款证券、学生贷款等；</li><li>以MBS+ABS现金流为抵押品再证券化的抵押债券凭证（CDO）；</li></ol><h5 id="中国">中国</h5><ol type="1"><li>信贷资产证券化：央行和银监会主管，以信贷资产为基础资产。</li><li>券商专项资产证券化：证监会主管，以企业应收款、信贷资产、信托收益权、基础设施收益权等财产权利、商业票据、债券等衍生品、股票及衍生品、商业物业等不动产为基础资产。</li><li>资产支持票据：交易商协会主管，以公用事业未来收益权、政府回购应收款、企业其他应收款为基础资产。</li></ol><h3 id="股权市场">4.3 股权市场</h3><h4 id="优先股与普通股">4.3.1 优先股与普通股</h4><h5 id="优先股">优先股</h5><p>优先股（preferredstocks）具有权益和债务的双重特征，是再剩余索取权方面较普通股优先的股票。</p><p>股息：通常归结为固定收益工具，它与债券一样，都承诺支付定量的股息（事先固定）。</p><p>优先性：再分得公司利润时和破产清偿时顺序优于普通股，但都低于债权。</p><p>股东权利：优先股在剩余控制方面劣于普通股，不能参与公司的经营管理，没有选举董事会和监事会的权利。</p><h5 id="普通股">普通股</h5><p>普通股（commonstocks）是在优先股要求权得到满足之后才参与公司利润和资产分配的股票合同，股息收益上不封顶、下不保底，每一阶段的红利数额也不确定。</p><p>股东权利：有出席股东大会的会议权、表决权和选举权、被选举权等，通过投票（通常一股一票和简单多数）来行使剩余控制权。</p><p>案例：阿里巴巴的双层结构的普通股，分为：</p><ul><li>A股：无投票权-&gt;一股一票；</li><li>B股：一股一票-&gt;数倍于A股的投票权（10~150倍）；</li></ul><h4 id="股票种类">4.3.2 股票种类</h4><h5 id="a股">A股</h5><p>人民币普通股</p><h5 id="b股">B股</h5><p>人民币特种股票。以人民币标明面值，以港币或美元交易。</p><h5 id="h股">H股</h5><p>国企股，注册地在内地，上市地在香港的股票。</p><h5 id="n股">N股</h5><p>注册地在中国大陆，上市地在纽约证券交易所。</p><h5 id="l股">L股</h5><p>注册地在中国大陆，上市地在伦敦。</p><h5 id="蓝筹股">蓝筹股</h5><p>稳定盈利的大公司发行，定期分派股利，投资价值较高的股票。</p><p>起源于赌场中蓝色筹码最值钱。</p><h5 id="红筹股">红筹股</h5><p>在境外注册、香港上市的中国大陆概念的股票。</p><h5 id="st股">ST股</h5><p>SpecialTreatment，特别处理股，连续两个会计年度净利润为负，每股净资产低于股票面值（1元）。</p><h4 id="股票指数">4.3.3 股票指数</h4><h5 id="功能">功能</h5><ol type="1"><li>投资指南；</li><li>衍生工具的标的：股指期货、股指期权的标的；</li><li>宏观经济景气度的指示器。</li></ol><h5 id="种类">种类</h5><ol type="1"><li>综合指数：全样本指数（上证综合指数、纽交所综合指数）；</li><li>成分指数：部分样本指数（标普500、伦敦金融时报100、沪深300），一般市值大、交易量大、业绩良好、业务稳定；</li><li>分类指数：具有相同特征（相同行业）的股票指数（上证行业指数，分为：材料、公用、能源、金融、电信、工业、可选、信息、消费和医药行业）。</li></ol><h5 id="编制方法">编制方法</h5><ol type="1"><li>简单算术平均法</li><li>算术平均修正法（道氏修正法）：道琼斯公司1928年创始，修正股票拆细、增资等因素造成平均数变化，保持连续性和可比性。</li><li>市值加权平均法：目前主流，权重是公司的规模而非股票价格，可比性强。</li></ol><h5 id="中国大陆股指">中国大陆股指</h5><ol type="1"><li>中证股票指数体系：上证+深证共同成立<ol type="1"><li>中证流通指数</li><li>沪深300指数：沪市+深市按日均成交金额排序选取300只A股</li><li>中证规模指数</li><li>中证500指数：由全部A股中剔除沪深300指数成份股及总市值排名前300名的股票后，总市值排名靠前的500只股票组成，综合反映中国A股市场中一批中小市值公司的股票价格表现。</li></ol></li><li>上证指数体系<ol type="1"><li>上证综指：全样本</li><li>上证50：规模最大、流动性最好的50只</li><li>上证180：总市值和成交金额靠前的180只</li><li>上证380：成长性好、盈利能力强的新兴蓝筹股</li></ol></li><li>深证指数体系<ol type="1"><li>深圳成分指数：深圳成指</li><li>中小板综指：100家主要中小板股票</li><li>创业板指数；创业板中100只</li></ol></li><li>新华富时中国A50指数：新华财经与英国富时合资，包含A股市值最大的50家，满足国内/外（QFII）投资者实时可交易。</li></ol><h5 id="全球主要股指">全球主要股指</h5><ol type="1"><li>美国<ol type="1"><li>道琼斯指数</li><li>纳斯达克指数</li><li>标准普尔指数</li></ol></li><li>英国<ol type="1"><li>富时100</li></ol></li><li>法国<ol type="1"><li>法国CAC40</li></ol></li><li>德国<ol type="1"><li>德国DAX</li></ol></li><li>日本<ol type="1"><li>日经225指数</li></ol></li><li>中国香港<ol type="1"><li>恒生指数</li></ol></li><li>韩国<ol type="1"><li>韩国综合指数</li></ol></li><li>澳大利亚<ol type="1"><li>澳洲标普200</li></ol></li><li>印度<ol type="1"><li>孟买SENSE</li></ol></li><li>俄罗斯<ol type="1"><li>俄罗斯RTS</li></ol></li><li>巴西<ol type="1"><li>圣保罗IBOV</li></ol></li></ol><h3 id="项目融资">4.4 项目融资</h3><h4 id="概念">4.4.1 概念</h4><p>项目融资（ProjectFinance）：是贷款人向<strong>特定的工程项目</strong>提供贷款协议融资，对于该<strong>项目所产生的现金流</strong>享有偿债请求权，并以该<strong>项目资产作为附属担保</strong>的融资类型。</p><h4 id="种类-1">4.4.2 种类</h4><h5 id="无追索权的项目融资">无追索权的项目融资</h5><p>贷款的还本付息完全依靠经营效益。</p><h5 id="有限追索权的项目融资">有限追索权的项目融资</h5><p>要求与项目有利害关系的第三方当事人提供各种担保。</p><p>贷款银行有权向担保方追索，以担保金额为限。</p><h4 id="融资方式">4.4.3 融资方式</h4><h5 id="政府主导方式">政府主导方式</h5><p>传统方式，本质是依靠政府负债。</p><p>利：运作简单，速度快，政府信用好。</p><p>弊：财政压力大、建设运营责任不清、资金利用效率低下。</p><h5 id="bot方式">BOT方式</h5><p>Build Operate Transfer</p><p>政府特许授权投资公司去<strong>建设</strong>、<strong>运营</strong>，在一定期限（如：三十年）后，最终<strong>转让</strong>给政府。</p><p>以特许经营权为主。</p><p>利：市场竞争机制、减轻政府财政负担、提高项目运营效率、引入管理与技术；</p><p>弊：风险大，因为投资大、期限长、条件差异大、缺乏先例可循。</p><h5 id="bt方式">BT方式</h5><p>Build Transfer</p><p>政府通过招投标，交给投资者去融资和建设，最后移交给政府。政府按协议分期支付项目投资与回报。</p><p>以项目外包为主。</p><p>弊：建设费用大、监管难、分包严重、质量得不到保证。</p><h5 id="其他方式">其他方式</h5><ol type="1"><li><p>BOOT: Build Own Operate Transfer</p></li><li><p>BOO: Build Own Operate</p></li><li><p>BTO: Build Transfer Operate</p></li><li><p>TOT: Transfer Operate Transfer</p></li></ol><h4 id="ppp项目融资模式">4.4.4 PPP项目融资模式</h4><h5 id="概念-1">概念</h5><p>Public-Private Partnership</p><p>政府与私人部门组成特许经营公司，引入社会资本。</p><p>政府补贴PPP项目，社会投资者投资PPP项目。</p><p>政府与私人部门风险共担，利益共享。</p><h5 id="适用">适用</h5><p>投资规模达、需求长期稳定、价格调整机制灵活、市场化程度较高的基础设施及公共服务类项目。例如：地铁。</p><h5 id="本质">本质</h5><ol type="1"><li>债权上，将政府债务转化为企业债务；</li><li>运营上，引入市场竞争与激励机制，发挥各方优势，提高公共产品与服务的供应质量和效率。</li></ol><h5 id="ppp项目的资产证券化">PPP项目的资产证券化</h5><p>运营管理权与收费收益权分离，将收益权作为基础资产。</p><p>按规定，PPP项目资产证券化的基础资产必须追到项目本身，不能以地方政府为基础资产，不能随意承诺保底、安排回购、明股实债等方式担保融资，但可以以财政补贴作为PPP项目收入的来源。</p><p>PPP项目期限一般为10~30年，比资产证券化产品期限（多数在7年以内）要长得多。</p><p>利：</p><ol type="1"><li>盘活存量PPP项目资产：增强资金流动性与安全性；</li><li>吸引社会资本投入公共服务；</li><li>提升项目稳定运营能力：风险隔离。</li></ol><p>类型：</p><ol type="1"><li>使用者付费：经营性项目；</li><li>可行性缺口补助：准经营性项目，使用者付费不足以满足成本回收与合理回报时，政府提供缺口补助使项目可行；</li><li>政府付费：非经营性项目，如，垃圾处理、污水处理、市政道路。</li></ol><h2 id="证券的发行与交易">5 证券的发行与交易</h2><p>证券的发行市场成为一级市场，证券的交易市场称为二级市场。</p><h3 id="证券的发行">5.1 证券的发行</h3><h4 id="证券发行市场">5.1.1 证券发行市场</h4><h5 id="概念-2">概念</h5><p>又称“初级市场”、“一级市场”，是证券发行主体发行和推销新证券所形成的市场。</p><p>证券发行者-&gt;（中介机构）-&gt;投资者，期间受监管者（证监会）监管。</p><h5 id="公募和私募">公募和私募</h5><p>公募公开发行，经过严格审查，因此信用高；可公开交易，因此流动性好；但成本高；</p><p>私募不公开发行，不经严格审查，发行程序简单，因此成本低；不公开上市，因此流动性差。</p><h5 id="直接发行和间接发行">直接发行和间接发行</h5><p>直接发行：自营发行，发行者直接发售证券给投资者；</p><p>间接发行：承销发行，发行者委托承销商代为发售证券，承销商收取代理费，并承担发行责任与风险。</p><h5 id="担保发行和无担保发行">担保发行和无担保发行</h5><p>主要用于债券发行。</p><p>担保发行：发行人以信用或实物担保，承诺证券收益；</p><p>无担保发行：不提供任何担保，例如：国债、部分金融债因违约可能性极低，一般无担保。</p><h4 id="证券发行制度">5.1.2 证券发行制度</h4><h5 id="注册制">注册制</h5><p>以美国联邦证券法为代表，遵循公开原则，实质上是发行公司的财务公开制度。如果信息误导，投资者有权起诉。</p><p>证券主管机关对证券发行信息资料做审查，不禁止质量差、风险高的证券上市，由市场判断公司价值。</p><p>利：政府干预少；流程快；上市成本低。</p><p>弊：门槛高，只适用于发达成熟的市场（需要投资者理性，且发行者、承销商等机构恪守法律与职业道德）。</p><h5 id="核准制">核准制</h5><p>以欧洲各国公司法为代表，实行实质管理原则，发行者必须符合公司法规定的实质条件（经营性质、管理人员资格、资本结构、偿债能力等）。</p><p>适用于证券市场历史短、投资者素质不高的地区和国家（欧洲大陆、发展中国家）。</p><h5 id="中国的证券发行制度历程">中国的证券发行制度历程</h5><ol type="1"><li>2001年前：发行审批制：地方与中央双重审批，获取配额，证监会复审；</li><li>2001年后：发行核准制；<ol type="1"><li>2001~2004：通道制，承销商（证券公司）推荐公司发行股票；</li><li>2004年后：保荐制，承销商推荐并一定程度担保公司质量，保荐责任必须落实到个人。</li></ol></li><li>2013年后：注册制提出；</li></ol><h5 id="发行制度对比">发行制度对比</h5><table><colgroup><col style="width: 15%" /><col style="width: 40%" /><col style="width: 27%" /><col style="width: 17%" /></colgroup><thead><tr class="header"><th></th><th>中国大陆</th><th>中国香港</th><th>美国</th></tr></thead><tbody><tr class="odd"><td>发行上市制度</td><td>发审制转向核准制</td><td>高度市场化的核准制</td><td>注册制</td></tr><tr class="even"><td>审核时间</td><td>6个月</td><td>4个月</td><td>3-4个月</td></tr><tr class="odd"><td>审核内容</td><td>实质审核</td><td>实质审核</td><td>形式审核</td></tr><tr class="even"><td>审核标准</td><td>监管部门严格审核资本结构、性质等</td><td>按《上市规则》规定指标</td><td>信息披露真实性</td></tr></tbody></table><h4 id="证券承销制度">5.1.3 证券承销制度</h4><p>承销商收承销费帮发行人销售股票和债券。</p><h5 id="包销">包销</h5><ul><li><p>全额包销：证券承销商全部购入，然后再转售给投资人；</p></li><li><p>余额包销：证券承销商按发行额，在发行期限内向投资人发售证券，到期未售出的证券由承销商负责认购。</p></li></ul><h5 id="代销">代销</h5><p>承销期结束时，将未售出的证券全部退还给发行人。</p><h4 id="股票的发行">5.1.4 股票的发行</h4><h5 id="初次发行">初次发行</h5><h6 id="三种情况">三种情况</h6><ol type="1"><li>新建股份公司时发行股票；（设立发行）</li><li>原非股份制公司改制为股份制时发行股票；（设立发行）</li><li>原私人持股公司转为公众持股公司时发行股票；（首次公开发行，IPO）</li></ol><h6 id="ipo">IPO</h6><p>Initial Public Offerings</p><p>IPO包含几个阶段，各阶段可按需并行：</p><ol type="1"><li>计划筹备阶段<ol type="1"><li>寻求政府支持</li><li>引入战略投资</li><li>公司内部治理结构调整</li><li>承销机构和其他中介早日入场</li></ol></li><li>申报材料阶段<ol type="1"><li>审计报告和核准时间</li><li>相关政府批文</li><li>法律问题</li><li>招股说明</li></ol></li><li>发行审核阶段<ol type="1"><li>申报材料</li><li>综合处收材料并分送预审员</li><li>预审员审核并向企业提问</li><li>形成反馈意见</li><li>回复反馈意见</li><li>通过预审会</li><li>上发审会</li><li>准备材料或退回材料</li></ol></li><li>路演与询价阶段<ol type="1"><li>路演准备工作</li><li>预路演：确定价格区间</li><li>网下路演：面向网下机构投资者（调整价格）</li><li>信息披露</li><li>网上路演：面向散户投资者</li></ol></li><li>上市阶段<ol type="1"><li>向交易所递交上市申请</li><li>通过上市委员会审核</li><li>刊登上市公告书</li><li>上市仪式</li><li>上市后市场维护</li><li>持续督导</li></ol></li></ol><h5 id="增资发行">增资发行</h5><p>SEO, Seasoned Equity Offerings</p><h6 id="有偿增资发行">有偿增资发行</h6><ol type="1"><li>向原股东配股：准许老股东按一定的配股价格优先认购新股票；</li><li>向第三者配股：向股东以外的第三者（公司职工、公司往来客户、社会大众等）以新股认购权的方式配发新股。</li></ol><h6 id="无偿增资发行">无偿增资发行</h6><p>原股东无需缴付股款即可获得新股。</p><p>通常目的是调整资本结构或将积累资本化。</p><p>形式有：</p><ol type="1"><li>无偿交付：盈余公积转为股份；</li><li>红利增资：将分红改为股份；</li><li>股份分割：一股拆多股；</li><li>债券股票化：债券转为股票。</li></ol><h6 id="有偿无偿混合增资发行">有偿无偿混合增资发行</h6><p>按比例同时进行有偿和无偿增资。</p><h5 id="股票发行的价格">股票发行的价格</h5><p>影响因素：</p><ol type="1"><li>企业自身状况<ol type="1"><li>经营业绩和发展前景</li><li>净资产</li><li>发行数量</li></ol></li><li>宏观环境因素<ol type="1"><li>宏观政策</li><li>所处行业</li><li>股票流通市场</li></ol></li></ol><h4 id="债券的发行">5.1.5 债券的发行</h4><h5 id="债券的评级">债券的评级</h5><p>债券的发行需要评级</p><h5 id="发行类型">发行类型</h5><ol type="1"><li>定向发行：私募、面向特定投资者；</li><li>承购包销：商行、券商组成承销团；</li><li>招标发行：招标竞价确定发行价格；</li><li>直接发售：券商或银行柜台直接销售。</li></ol><h3 id="证券的交易">5.2 证券的交易</h3><h4 id="证券交易市场">5.2.1 证券交易市场</h4><h5 id="概况">概况</h5><p>也称“二级市场”，是已发行的证券在证券市场上买卖的活动。</p><p>证券交易包含：</p><ol type="1"><li>股票交易</li><li>债券交易</li><li>基金交易</li><li>金融衍生工具交易</li></ol><h5 id="交易所市场">交易所市场</h5><p>证券交易所，会员资格才可交易，信息及时披露。二级市场中的第一市场。</p><h6 id="会员制交易所">会员制交易所</h6><p>券商自愿组织的社会团体，会费共担，不以营利为目的。</p><p>会员既有交易权，也有交易所的所有权。</p><p>案例：上海证券交易所、深圳证券交易所</p><p>利：</p><ol type="1"><li>不以营利为目的，费用低；</li><li>会员制避免违法行为；</li><li>损失由买卖双方自负，规范双方行为；</li><li>有政府支持，无破产可能；</li></ol><p>弊：</p><ol type="1"><li>缺乏第三方担保；</li><li>管理者同时也是交易者，有悖公平原则；</li><li>非会员不能进，容易垄断。</li></ol><h6 id="公司制交易所">公司制交易所</h6><p>商行、券商、信托等企业共同出资建立，以盈利为目的的公司法人。</p><p>案例：西方发达国家、中国香港都是公司制，20世纪90年代，为全球主要交易所采用。</p><p>交易权与所有权分离，会员无需拥有交易所所有权，也可拥有交易权。只有经过注册的券商才能进入交易大厅直接参加买卖。</p><p>利：</p><ol type="1"><li>第三方担保，若会员违约造成损失，交易所负责赔偿；</li><li>管理权与所有权分离，交易者、管理者、所有者三方分离，交易所不偏袒任何一方；</li><li>服务优质：为盈利，不得不提供良好服务。</li></ol><p>弊：</p><ol type="1"><li>利益驱使，利润取决于交易额，滋长过度投机；</li><li>交易所是有限公司，不排除倒闭可能。</li></ol><h5 id="场外交易市场">场外交易市场</h5><h6 id="定义">定义</h6><p>OTC, Over the Counter</p><p>在证券交易所以外，由证券买卖双方直接议价成交的市场。</p><h6 id="特点">特点</h6><ol type="1"><li>非集中：无交易场所、交易时间、交易规则限制；</li><li>开放式：无会员制；</li><li>种类多：上市或未上市证券都可交易；</li><li>议价方式不同：做市商和买卖价差的报价。</li></ol><h6 id="第二市场柜台交易市场">第二市场（柜台交易市场）</h6><p>最早形成，公开但未上市发行的证券，如：地方债、市政债、公司债。</p><h6 id="第三市场大宗交易市场">第三市场（大宗交易市场）</h6><p>已上市证券在交易所以外进行交易的市场，因75年后允许交易所会员自行决定佣金，第三市场发展放缓。</p><p>节约交易所内大宗交易的高昂佣金。</p><h6 id="第四市场场外网络市场">第四市场（场外网络市场）</h6><p>买卖双方不经过经纪人，而是通过网络直接大宗交易。</p><p>利：成本低；速度快；保密；不冲击证券市场。</p><p>弊：给金融监管带来挑战。</p><h5 id="全球主要交易市场">全球主要交易市场</h5><h6 id="美国证券市场">美国证券市场</h6><p>起源于政府债券。</p><ol type="1"><li>纽约证券交易所</li></ol><h6 id="英国证券市场">英国证券市场</h6><p>随股份公司涌现和信用活动开展而发展。</p><ol type="1"><li>伦敦证券交易所（前身自1773年）</li><li>利物浦证券交易所（1827年成立）</li><li>曼彻斯特证券交易所（1830年成立）</li></ol><p>特点：</p><ol type="1"><li>发行业务专业化，由各种证券金融业分担；</li><li>中小企业比重较大；</li><li>外国证券比重较大；</li><li>政府证券比重较大（伦敦证交所是最大的“金边债券”市场（早期英国政府公债带有黄边，且可靠性高，因此称为金边债券））。</li></ol><h6 id="中国证券市场">中国证券市场</h6><p>我国资本市场结构：</p><ol type="1"><li>主板：也称一板市场，上市要求最高；</li><li>创业板：也称二板市场，深交所，上市要求适合中小企业；</li><li>新三板：全国中小企业股份转让系统；</li><li>区域性股权交易市场：省市级，仅用于地区内；</li><li>柜台市场：2012年证监会“限定私募、先行起步”，开展柜台试点。</li></ol><h4 id="证券交易机制">5.2.2 证券交易机制</h4><p>报价制度，做市商制度，主要用于柜台市场；</p><p>指令驱动制度，竞价方式，主要用于交易所。</p><h5 id="做市商制度">做市商制度</h5><p>做市商（market maker）制定买价（bid price）/卖价（askprice），在买卖双方中间赚取差价。</p><h6 id="垄断型做市商">垄断型做市商</h6><p>案例：纽约证券交易所</p><p>信息综合能力强，价格竞争性差，高额利润，易于监管</p><h6 id="竞争型做市商">竞争型做市商</h6><p>多元做市商制度，案例：纳斯达克交易所</p><p>竞争使市场活跃，交易量增加。做市商信息分散，无垄断地位，交易利润少。</p><h5 id="指令驱动制度">指令驱动制度</h5><p>竞价市场，买方订单和卖方订单通过经纪商进入市场，交易中心以买卖双向价格为基准进行撮合。</p><h6 id="集合竞价">集合竞价</h6><p>在一定时段内累积订单，到一定时刻再撮合定价。（通常是开市前10分钟）</p><h6 id="连续竞价">连续竞价</h6><p>在交易日各个时刻连续进行，只要存在匹配订单，交易即发生。</p><h6 id="指令驱动交易过程">指令驱动交易过程</h6><ol type="1"><li>开户：投资者在经纪商处开户<ol type="1"><li>证券账户</li><li>资金账户</li></ol></li><li>委托：投资者委托经纪商下达买入卖出指令<ol type="1"><li>市价委托：按实施申报价格买卖证券；</li><li>限价委托：设定买进价格上限或卖出价格下限；</li><li>止损委托：市价低于卖方止损价即转为市价指令（卖方止损），市价高于买方止损价即转为市价指令（买方止损，通常期货市场买方避免过高价格）；</li><li>止损限价委托：止损+限价。</li></ol></li><li>竞价与成交：交易制度的核心，确定价格<ol type="1"><li>集合竞价：买单按价格降序排序，卖单按价格升序排序。撮合取得基准价。</li><li>连续竞价：报一笔撮合一笔，不能成交则按“价格优先、同价则时间优先”原则排队。</li></ol></li><li>结算：证券成交后，核定结算买卖双方应收应付的证券和价款<ol type="1"><li>逐笔交收：逐笔结算交易成本高，适合成交数少的大宗交易；</li><li>净额交收：在买卖双方约定的交收期限内，以交易净额进行交收，如：上交所、深交所。<ol type="1"><li>清算：<ol type="1"><li>一级清算：券商之间以中央登记清算公司为中介做清算；</li><li>二级清算：券商与投资者之间的清算。</li></ol></li><li>交割：买房付出现金取得证券，卖方交出证券获得价款。<ol type="1"><li>T+0：当日交割；</li><li>T+1：次日交割（我国主要为T+1）；</li><li>T+n：n日交割等。</li></ol></li></ol></li></ol></li><li>过户：对股票和记名债券，还需要过户<ol type="1"><li>我国实现无纸化交易，无需再到发行公司办理过户手续。</li></ol></li></ol><h5 id="混合交易制度">混合交易制度</h5><ol type="1"><li><p>在做市商制度中引入竞价交易制度，如：1997年后的纳斯达克；</p></li><li><p>在竞价交易制度中引入做市商制度，如：1986年后的伦敦交易所。</p></li></ol><h5 id="交易机制对比">交易机制对比</h5><table><colgroup><col style="width: 6%" /><col style="width: 46%" /><col style="width: 46%" /></colgroup><thead><tr class="header"><th>交易机制</th><th>做市商市场</th><th>竞价市场</th></tr></thead><tbody><tr class="odd"><td>竞争方式</td><td>报价驱动</td><td>指令驱动</td></tr><tr class="even"><td>价格发现</td><td>无正式程序</td><td>正式的市场开盘</td></tr><tr class="odd"><td>监管</td><td>直接监管少，靠竞争改进缺陷</td><td>直接监管</td></tr><tr class="even"><td>竞争</td><td>做市商之间</td><td>客户之间</td></tr><tr class="odd"><td>优点</td><td>成交及时；价格稳定；存货机制纠正买卖不均衡；做市商持仓抑制股价操纵</td><td>透明度高；信息传递快；运行费用低</td></tr><tr class="even"><td>缺点</td><td>因买卖集中在做市商手中而缺乏透明度；交易成本高；监管成本增加，难度大；</td><td>难以处理大宗交易；冷门股票成交持续萎缩；价格波动剧烈；价格难以维护，容易被操纵</td></tr></tbody></table><h3 id="量化投资">5.3 量化投资</h3><p>量化投资是利用计算机技术，采用数学模型实现投资理念、投资策略的过程。</p><h4 id="算法交易">5.3.1 算法交易</h4><p>数学建模+计算机自动化（半自动化）交易。</p><h5 id="目的">目的</h5><ol type="1"><li>将大额交易分割为许多小额交易来应对市场风险和冲击。（避免大额交易被发现，使行情向不利于自己的方向发展）</li><li>因为大型交易者不会一次性暴露自己的所有交易指令，因此实际的交易机会很多，可以通过算法发现交易机会。</li></ol><h5 id="分类">分类</h5><ol type="1"><li>被动型算法交易：结构型、时间表型，利用历史数据估计交易模型参数，按既定方针交易，不主动选择交易时机与交易数量，核心在减少滑价。<ol type="1"><li>成交量加权平均价格（VWAP）：让自己的交易量提交比例与市场成交量比例匹配；</li><li>时间加权平均价格（TWAP）</li></ol></li><li>主动型算法交易：根据市场情况，判断是否交易、交易数量、交易价格，核心在价格趋势预测。</li><li>综合型算法交易</li></ol><h5 id="发展">发展</h5><p>2006年，欧美有三分之一的股票交易量由算法交易完成。</p><h5 id="常用算法交易策略">常用算法交易策略</h5><p>算法交易核心问题：平衡冲击成本与等待风险。</p><ul><li>交易太快，可以快速完成交易目标，减小等待风险，但会冲击市场，影响价格走势；</li><li>交易太慢，可以避免冲击市场，但无法快速完成交易，存在等待风险。</li></ul><p>代表性的被动型算法交易策略：</p><h6 id="vwap">VWAP</h6><p>预测当天交易时间内各时间片的交易比例分布，最小化冲击成本。</p><p>标准VWAP：静态预测当天交易分布；</p><p>改进VWAP：根据市场价格走势调整交易量。</p><h6 id="twap">TWAP</h6><p>不预测交易期内成交量的分布，按交易时段的长度加权。</p><h6 id="peg">PEG</h6><p>盯住盘口策略，买入按当前最高买价，卖出按当前最低卖价发出限价交易。若交易未完成且成交价远离限价指令，则撤销，并重新循环。</p><h6 id="is策略">IS策略</h6><p>减小实际成交价与目标价的价差，分激进、中性和保守策略。</p><h6 id="sor策略">SOR策略</h6><p>下单路径选优策略，从做市商、交易所、暗池等路径择优交易。</p><h4 id="高频交易">5.3.2 高频交易</h4><p>利用高速计算机，在极短时间内判断有价值信息，先于其他投资者进行交易。例如：利用交易所之间的微小价差，大量地不停地买卖。</p><h3 id="暗池交易">5.4 暗池交易</h3><h4 id="概念-3">5.4.1 概念</h4><p>买卖双方匿名配对进行大宗股票交易，主要为机构投资者，运作不透明。</p><p>机构投资者不希望公开寻找交易对手，而是希望避免市场冲击并保持信息保密（例如：防止被高频交易套利）。不借助公开交易市场，又存在搜寻成本高的问题。</p><h4 id="分类-1">5.4.2 分类</h4><h5 id="独立暗池">独立暗池</h5><p>经纪公司组织，收取手续费，为机构投资者提供交易平台。</p><h5 id="内部撮合池">内部撮合池</h5><p>证券经纪商组织，在内部对自营业务的订单与客户订单之间进行撮合，避免交易所交易的费用。</p><h5 id="监听目标池">监听目标池</h5><p>对冲基金和电子做市商组织，只接受/取消订单，根据发来的订单，决定是否交易。</p><h5 id="联合暗池">联合暗池</h5><p>多家金融机构共同组织，作为二级暗池，处理各家机构内部撮合池未能完成的订单余额的撮合。</p><h4 id="特点-1">5.4.3 特点</h4><ol type="1"><li>保密</li><li>撮合方式类似订单驱动的电子竞价市场</li></ol><h4 id="利弊">5.4.4 利弊</h4><ol type="1"><li>为客户保密，一方面保护知情交易者和大宗交易者的利益，另一方面妨碍外部投资者知情和市场定价效率。</li><li>流动性分离，一方面在暗池中为客户提供更多流动性，另一方面造成市场分割，与交易所争夺流动性。</li></ol><h2 id="投资公司">6 投资公司</h2><h3 id="投资公司的类型">6.1 投资公司的类型</h3><p>金融中介，本质特征是资产集合，汇集资金并投资证券。</p><h4 id="单位投资信托">6.1.1 单位投资信托</h4><p>成立后，资产组合固定不变，是无需管理的基金；</p><p>主要投资固定收益资产组合。不需要主动管理，因此费率低。</p><h4 id="投资管理公司">6.1.2 投资管理公司</h4><h5 id="两类投资管理公司">两类投资管理公司</h5><ol type="1"><li>开放型基金，即共同基金（mutualfund），随时可以赎回或发行股份；我国正式名称为“证券投资信托基金”。</li><li>封闭型基金，不能赎回或发行股份。</li></ol><h5 id="共同基金的运作">共同基金的运作</h5><p>汇集大量投资者形成<strong>集合投资</strong>，基金的资金存于<strong>基金托管人</strong>，由<strong>基金管理人</strong>指令管理被托管资金，<strong>组合投资</strong>于一系列的证券。</p><h3 id="基金的分类">6.2 基金的分类</h3><h4 id="按申赎方式开放式封闭式基金">6.2.1按申赎方式：开放式/封闭式基金</h4><table><colgroup><col style="width: 10%" /><col style="width: 35%" /><col style="width: 53%" /></colgroup><thead><tr class="header"><th></th><th>封闭式基金</th><th>开放式基金</th></tr></thead><tbody><tr class="odd"><td>期限</td><td>5年以上，多数15年</td><td>无固定存续期</td></tr><tr class="even"><td>规模</td><td>不变</td><td>可变</td></tr><tr class="odd"><td>价格</td><td>供求关系</td><td>净值</td></tr><tr class="even"><td>策略</td><td>无赎回、无准备金、可长期投资</td><td>有赎回、有准备金、无法全额用于长期投资</td></tr><tr class="odd"><td>激励机制</td><td>缺乏</td><td>按总额收取管理费，若业绩差，则资金赎回流失</td></tr></tbody></table><h4 id="按组织形式契约型公司型基金">6.2.2按组织形式：契约型/公司型基金</h4><table><colgroup><col style="width: 17%" /><col style="width: 22%" /><col style="width: 60%" /></colgroup><thead><tr class="header"><th></th><th>契约型基金</th><th>公司型基金</th></tr></thead><tbody><tr class="odd"><td>投资者地位</td><td>受益人，无发言权</td><td>既是受益人，也是股东（有发言权等股东权利）</td></tr><tr class="even"><td>资产运用依据</td><td>按契约</td><td>按公司章程</td></tr><tr class="odd"><td>融资渠道</td><td>不能向银行借款</td><td>公司有法人资格，可以向银行借款</td></tr><tr class="even"><td>运营方式</td><td>按契约期运作</td><td>按公司法运作，除非破产清算，否则有永久性</td></tr><tr class="odd"><td>资金性质</td><td>收益凭证</td><td>股票</td></tr></tbody></table><p>中国基金以契约型为主，美国则以公司型居多。</p><h4 id="按投资对象货币股权固定收益混合指数">6.2.3按投资对象：货币/股权/固定收益/混合/指数</h4><h4 id="按投资目标成长型平衡型收入型">6.2.4按投资目标：成长型/平衡型/收入型</h4><h4 id="按募集方式公募私募">6.2.5 按募集方式：公募/私募</h4><p>私募基金：无需披露信息，监管不严，隐蔽。如美国的对冲基金，采取合伙制度。</p><h3 id="基金的募集和交易">6.3 基金的募集和交易</h3><h4 id="渠道">6.3.1 渠道</h4><p>银行</p><p>基金公司官网</p><p>第三方理财平台</p><p>证券公司代销</p><h4 id="交易原则">6.3.2 交易原则</h4><ol type="1"><li>未知价：申赎时不知道资产成交价格；</li><li>按金额申购，按份额赎回</li></ol><h4 id="基金申赎">6.3.3 基金申赎</h4><h5 id="申购价格">申购价格</h5><p>申购价格=基金单位净值+前端费用</p><ul><li>基金单位净值（net assetvalue）：（基金资产-基金负债）/已售出的基金单位</li><li>前端费用（front-end load）：申购费率</li></ul><h5 id="赎回金额">赎回金额</h5><p>赎回金额=赎回总额-赎回费用</p><ul><li>赎回数量×赎回日基金单位净值</li><li>后端费用（back-end value）=赎回总额×赎回费率</li></ul><h5 id="估值与费用">估值与费用</h5><h6 id="估值对象">估值对象</h6><p>资产：股票、债券、存款、应收利息；</p><p>负债：应收管理费、应付税收。</p><h6 id="运作费用">运作费用</h6><ol type="1"><li><p>管理费</p></li><li><p>托管费</p></li><li><p>交易费</p></li><li><p>其他：审计费、律师费、信息披露费</p></li></ol><h6 id="收益率">收益率</h6><p>收益率=(净值增长+收入+资本利得)/初净值</p><h4 id="基金的评级">6.3.4 基金的评级</h4><p>晨星于1984年成立于美国芝加哥。晨星中国2003年在深圳成立。</p><p>分为定性与定量评价。</p><p>五个关键因素：投研团队、投资方法、基金公司、业绩、费用。</p><p>对基金只做同类比较。</p><p>每个月进行一次评级，只对三年及以上的基金进行星级评价。</p><h3 id="代表性基金分类">6.4 代表性基金分类</h3><h4 id="指数型基金">6.4.1 指数型基金</h4><p>按当期指数的成分股比例购买的基金，追踪指数的变化幅度。</p><p>1971年，世界上第一个指数基金出现在美国。</p><p>1994~1996年，市场上91%的股票基金收益增长率低于标普500指数，指数基金优势开始显现。</p><p><em>当市场越有效时，被动化管理越有优势。</em></p><p>特点：</p><ol type="1"><li>费用低廉：被动管理，投研少、调仓少，因此成本低。</li><li>分散风险：单一股票涨跌不会冲击指数基金整体表现。</li><li>延迟纳税：在发达国家，资本利得税很少。</li><li>监控较少：管理人不需要监控基金表现。</li></ol><h4 id="etf基金">6.4.2 ETF基金</h4><p>交易型开放式指数证券投资基金（Exchange Traded Fund）</p><p>跟踪标的指数的变化，且在证交所上市交易的基金。</p><h5 id="实物申赎">实物申赎</h5><p>ETF对应的是一揽子股票，采用实物申赎，而不是一般开放基金的份额申赎。</p><p>实物申赎ETF必须以一篮子股票换取基金份额，或者以基金份额换取一篮子股票。</p><p>最小申赎单位都是100万基金份额，通常门槛高，由机构投资者直接申赎。</p><p>因为实物申赎可以当天换取为股票，而股票可以当天买卖，因此，ETF基金实物申赎可以做到<strong>T+0</strong>交割。</p><h4 id="lof基金">6.4.3 LOF基金</h4><p>我国本土创新，上市型开放式基金（Listed Open-ended Fund）。</p><p>LOF基金是开放式与封闭式基金功能的结合体：</p><ol type="1"><li>因为是上市的，所以可以在二级市场进行交易（类似封闭式基金）；</li><li>因为是开放的，所以可以按份额申赎（类似开放式基金）。</li></ol><h4 id="qdii基金">6.4.4 QDII基金</h4><p>合格境内机构投资者（Qualified Domestic Institutional Investor）。</p><p>在国境内设立，经国内有关部门批准从事境外证券市场的股票、债券等有价证券业务的投资基金。</p><h4 id="保本基金避险策略基金">6.4.5 保本基金（避险策略基金）</h4><p>在基金的保本周期内，投资者可以拿回认购时原始本金。</p><p>不表示周期内可以保本，也不保证周期内申购可以保本。保本周期在中国一般为3年，在国外可达7~12年。</p><p>一般使用利息或是极小比例的资产从事高风险投资，大多数资金投资于固定资产。</p><h4 id="专户理财">6.4.6 专户理财</h4><p>类似私募基金，由基金公司对多个客户提供投资管理服务。</p><h4 id="私募投资基金">6.4.7 私募投资基金</h4><p>Private placement fund</p><p>向特定的对象募集基金份额。</p><p>私募基金无需披露信息，监管要求不严，比较隐蔽。</p><p>我国规定所有基金必须公募，因此信托成为私募基金的合法化主要渠道。国内的阳光私募基金一般是私募信托证券基金，主要投资二级证券市场，与重点投资一级股权市场的私募股权基金（PE,Private Equity）在投资对象上有所区别。</p><p>私募基金一般为“2+20”收费模型，收取2%管理费和20%盈利部分提成。</p><h4 id="对冲基金">6.4.8 对冲基金</h4><p>Hedge Fund</p><p>借助复杂资产组合与风险管理手段，投资多种资产，广泛运用杠杆、卖空以及衍生品等交易策略。</p><p>美国的对冲基金即一般意义上的私募基金。</p><p>对冲基金以合伙人制度为主，仅提供给有限的合格投资者。</p><h4 id="互联网宝类基金">6.4.9 互联网宝类基金</h4><p>本质上是货币基金。</p><p>现逐步向各类非标的资产投资发展，包括：土地质押、ABS债券、P2P小额信贷等。</p><h2 id="投资收益与风险">7 投资收益与风险</h2><h3 id="利率水平的决定因素">7.1 利率水平的决定因素</h3><p><strong>名义利率</strong> <spanclass="math inline">\(R\)</span>，是资金量增长率；</p><p><strong>真实利率</strong> <spanclass="math inline">\(r\)</span>，是购买力增长率。</p><p>记<strong>通货膨胀率</strong>为 <spanclass="math inline">\(i\)</span>，则有以下关系公式： <spanclass="math display">\[1+r = \frac{1+R}{1+i}\]</span> <strong>真实利率均衡（The Equilibrium Real Rate ofInterest）</strong>是指货币流通中，货币供给（居民储蓄）与货币需求（实体经济与投资）一致时的利率。影响货币供给和需求的因素（财政政策：财政盈余和财政赤字；货币政策：中央银行操作）就影响利率均衡。</p><p><strong>名义利率均衡（The Equilibrium Nominal Rate ofInterest）</strong>是指当通货膨胀率增加时，投资者会对其投资提出更高的名义利率要求。</p><p><strong>费雪公式</strong>代表预期通货膨胀率为 <spanclass="math inline">\(E(i)\)</span> 时，投资者的名义利率要求： <spanclass="math display">\[R = r + E(i)\]</span> 税收对真实利率的影响：税赋是基于名义收入的支出。假设税率为<span class="math inline">\(t\)</span>，则税后真实利率为： <spanclass="math display">\[R(1-t)-i\]</span></p><h3 id="收益率的衡量">7.2 收益率的衡量</h3><h4 id="持有期收益率">7.2.1 持有期收益率</h4><p>持有期收益由两部分组成：</p><ol type="1"><li><p>资本利得：投资买卖差价；</p></li><li><p>股息或红利：如，股票的股息、分红。</p></li></ol><p><strong>持有期收益率（Holding-periodReturn）</strong>是给定期限内的收益率。 <span class="math display">\[r = HPR = \frac{p_t - p_0 + d}{p_0}\]</span> 其中，<spanclass="math inline">\(p_0\)</span>表示持有期起始时的价格，<spanclass="math inline">\(p_t\)</span>表示持有期结束时的价格，<spanclass="math inline">\(d\)</span>表示股息收入。</p><h4 id="有效年利率">7.2.2 有效年利率</h4><p><strong>有效年利率（Effective AnnualRate）</strong>是一年期投资价值增长的百分比。</p><p>一年期总收入（<spanclass="math inline">\(1+EAR\)</span>）是每一元投资的最终价值： <spanclass="math display">\[1 + EAR = [1 + r_f(T)]^{1/T}\]</span> 有效年利率<span class="math inline">\(EAR\)</span>： <spanclass="math display">\[EAR = (1 + r(T))^{\frac{1}{T}} - 1\]</span> T年的总收益率<span class="math inline">\(r(T)\)</span>： <spanclass="math display">\[r(T) = (EAR + 1)^T - 1\]</span> 投资期内总收益率<spanclass="math inline">\(r(T)\)</span>与有效年利率<spanclass="math inline">\(EAR\)</span>之间的关系： <spanclass="math display">\[(EAR + 1)^T = r(T) + 1\]</span></p><h4 id="年化百分比利率">7.2.3 年化百分比利率</h4><p><strong>年化百分比利率（Annual PercentageRate）</strong>是对期限小于一年的投资项目，将该投资的总收益率按照单利的形式转化为年收益率形式的利率。</p><p>年化百分比利率是年度化的简单利率： <span class="math display">\[r(T) = T \times APR\]</span> 例如：半年期国债总收益率<span class="math inline">\(r(T) =1.63 \%\)</span>，其中<span class="math inline">\(T =0.5\)</span>，则转化为年化百分比利率为<span class="math inline">\(APR =1.63\% \times 2\)</span>。</p><p>总收益率、有效年利率、年化百分比利率之间的关系： <spanclass="math display">\[EAR + 1 = [1+r(T)]^{\frac{1}{T}}=(1+T*APR)^{\frac{1}{T}}\]</span> 有效年利率可以被年化百分比利率表达： <spanclass="math display">\[EAR = (1 + T*APR)^{\frac{1}{T}} - 1\]</span></p><h4 id="连续复利">7.2.4 连续复利</h4><p><strong>连续复利（Continuously CompoundingInterest）</strong>是指在期数趋于无限大的极限情况下对应的利率，此时不同期之间的间隔很短，可以看作是无穷小量。</p><p><strong>连续复利收益率</strong>可以简单理解为当投资期限为无穷小时的<strong>年化百分比收益率的值</strong>。</p><p>连续复利收益率又称为对数收益率（Log Return）。</p><h3 id="风险与风险的衡量">7.3 风险与风险的衡量</h3><h4 id="风险的一般分类">7.3.1 风险的一般分类</h4><p>风险一般可分为：</p><ol type="1"><li>自然风险、政治风险、运输风险……</li><li>商界：财产权、生产、交易……</li><li>金融：<ol type="1"><li>投资风险<ol type="1"><li>市场风险（Market risk）：Interest rate, currency, equity,commodity</li><li>信用风险（Credit risk）：sovereign, corporate, personal</li><li>流动性风险（Liquidity risk）：market, funding</li><li>运营风险（Operational risk）：system &amp; control, managementfailure, human error</li><li>事件风险（Event risk）</li></ol></li><li>货币购买力风险：inflation, currency, liquidity</li></ol></li></ol><h4 id="风险的衡量">7.3.2 风险的衡量</h4><p>期望收益是对收益的数学期望： <span class="math display">\[E(r) = \sum_s p(s) r(s)\]</span> 其中，<spanclass="math inline">\(p(s)\)</span>是情境概率，<spanclass="math inline">\(r(s)\)</span>是情境下的持有期收益率，<spanclass="math inline">\(s\)</span>是情境。情境，例如：经济形势，好、差的经济形势，对应不同的收益率。</p><p><strong>超额收益（ExcessReturn）</strong>是在任意一个特定的阶段，风险资产的实际收益率与实际无风险收益率的差值。</p><blockquote><p>美国政府短期国库券（T-Bill）的收益率可被作为无风险收益率。相比之下，更长期的国债，尽管几乎不存在信用风险，但仍然存在货币购买力风险，如：通货膨胀风险。</p></blockquote><p><strong>风险溢价（RiskPremium）</strong>是风险资产预期持有期收益与无风险收益的差值。</p><p><strong>收益波动性比率（The Reward-to-VolatilityRatio）</strong>有如<strong>夏普比率（Sharpe Ratio）</strong>： <spanclass="math display">\[S = \frac{Risk Premium}{SD of Excess Return}\]</span> 夏普比率计算的是风险溢价与超额收益标准差的比例。</p><h2 id="收益率的时间序列分析">7.4 收益率的时间序列分析</h2><h3 id="收益率-1">7.4.1 收益率</h3><p>算术平均值收益率： <span class="math display">\[E(r) = \sum_{s-1}^{n}p(s)r(s) = \frac{1}{n}\sum_{s-1}^{n}r(s)\]</span></p><p>几何平均值收益率： <span class="math display">\[TV_n = (1+r_1)(1+r_2)...(1+r_n)\]</span></p><p><span class="math display">\[g = TV^{1/n} - 1\]</span></p><p>可以作为预期收益的估计工具。</p><p>实际上历史越久远对现有的预测影响越小，当以过去的时间序列估时计，需要考虑系统发生的变化。</p><p>理想情况下，拟合正态分布进行估计，只需依据过往的时间序列求解收益率的均值与方差。</p><p>现实中，小概率事件带来风险，收益可能偏离正态分布，此时标准差不再是衡量风险的完美度量工具，夏普比率也不再是评价证券表现的完美度量工具。对正态分布进行修正，需要考虑偏度（skewness）和峰度（kurtosis）。</p><blockquote><p>Yahoo Finance可获取国内外的股票时序数据。</p></blockquote><h3 id="在险价值">7.4.2 在险价值</h3>]]></content>
    
    
    <summary type="html">&lt;p&gt;通过中央财经大学的投资学课程，系统性地学习基本的投资学原理。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Investment" scheme="https://heary.cn/tags/Investment/"/>
    
  </entry>
  
  <entry>
    <title>spaCy无法下载预训练模型问题解决</title>
    <link href="https://heary.cn/posts/spaCy%E6%97%A0%E6%B3%95%E4%B8%8B%E8%BD%BD%E9%A2%84%E8%AE%AD%E7%BB%83%E6%A8%A1%E5%9E%8B%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"/>
    <id>https://heary.cn/posts/spaCy%E6%97%A0%E6%B3%95%E4%B8%8B%E8%BD%BD%E9%A2%84%E8%AE%AD%E7%BB%83%E6%A8%A1%E5%9E%8B%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/</id>
    <published>2021-01-14T07:48:00.000Z</published>
    <updated>2022-08-07T04:02:09.871Z</updated>
    
    <content type="html"><![CDATA[<p><ahref="https://spacy.io/">spaCy</a>是一款非常好用的自然语言处理工具，不过也许是因为一些原因，无法正常下载spaCy官方的预训练模型了，网络连接被重置，报错<code>ProtocolError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer'))</code>。为了解决该问题，可以尝试手动选择链接下载模型资源。</p><span id="more"></span><h1id="spacy无法下载预训练模型问题解决">spaCy无法下载预训练模型问题解决</h1><h2 id="问题描述">1 问题描述</h2><p>在开展涉及自然语言处理的研究中，需要对自然语言数据进行一系列处理，因此需要使用spaCy。</p><p>不过近期发现，在使用命令下载spaCy的预训练模型时，会遭遇网络连接重置的问题，导致无法正常使用该工具。</p><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><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></pre></td><td class="code"><pre><span class="line">(pytorch) shenjiayun@server3 ~/Dev/VisualEntailment $ python -m spacy download en_core_web_sm</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 706, <span class="keyword">in</span> urlopen</span><br><span class="line">    chunked=chunked,</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 382, <span class="keyword">in</span> _make_request</span><br><span class="line">    self._validate_conn(conn)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 1010, <span class="keyword">in</span> _validate_conn</span><br><span class="line">    conn.connect()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connection.py&quot;</span>, line 421, <span class="keyword">in</span> connect</span><br><span class="line">    tls_in_tls=tls_in_tls,</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/util/ssl_.py&quot;</span>, line 429, <span class="keyword">in</span> ssl_wrap_socket</span><br><span class="line">    sock, context, tls_in_tls, server_hostname=server_hostname</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/util/ssl_.py&quot;</span>, line 472, <span class="keyword">in</span> _ssl_wrap_socket_impl</span><br><span class="line">    <span class="built_in">return</span> ssl_context.wrap_socket(sock, server_hostname=server_hostname)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/ssl.py&quot;</span>, line 423, <span class="keyword">in</span> wrap_socket</span><br><span class="line">    session=session</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/ssl.py&quot;</span>, line 870, <span class="keyword">in</span> _create</span><br><span class="line">    self.do_handshake()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/ssl.py&quot;</span>, line 1139, <span class="keyword">in</span> do_handshake</span><br><span class="line">    self._sslobj.do_handshake()</span><br><span class="line">ConnectionResetError: [Errno 104] Connection reset by peer</span><br><span class="line"></span><br><span class="line">During handling of the above exception, another exception occurred:</span><br><span class="line"></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/requests/adapters.py&quot;</span>, line 449, <span class="keyword">in</span> send</span><br><span class="line">    <span class="built_in">timeout</span>=<span class="built_in">timeout</span></span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 756, <span class="keyword">in</span> urlopen</span><br><span class="line">    method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/util/retry.py&quot;</span>, line 531, <span class="keyword">in</span> increment</span><br><span class="line">    raise six.reraise(<span class="built_in">type</span>(error), error, _stacktrace)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/packages/six.py&quot;</span>, line 734, <span class="keyword">in</span> reraise</span><br><span class="line">    raise value.with_traceback(tb)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 706, <span class="keyword">in</span> urlopen</span><br><span class="line">    chunked=chunked,</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 382, <span class="keyword">in</span> _make_request</span><br><span class="line">    self._validate_conn(conn)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connectionpool.py&quot;</span>, line 1010, <span class="keyword">in</span> _validate_conn</span><br><span class="line">    conn.connect()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/connection.py&quot;</span>, line 421, <span class="keyword">in</span> connect</span><br><span class="line">    tls_in_tls=tls_in_tls,</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/util/ssl_.py&quot;</span>, line 429, <span class="keyword">in</span> ssl_wrap_socket</span><br><span class="line">    sock, context, tls_in_tls, server_hostname=server_hostname</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/urllib3/util/ssl_.py&quot;</span>, line 472, <span class="keyword">in</span> _ssl_wrap_socket_impl</span><br><span class="line">    <span class="built_in">return</span> ssl_context.wrap_socket(sock, server_hostname=server_hostname)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/ssl.py&quot;</span>, line 423, <span class="keyword">in</span> wrap_socket</span><br><span class="line">    session=session</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/ssl.py&quot;</span>, line 870, <span class="keyword">in</span> _create</span><br><span class="line">    self.do_handshake()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/ssl.py&quot;</span>, line 1139, <span class="keyword">in</span> do_handshake</span><br><span class="line">    self._sslobj.do_handshake()</span><br><span class="line">urllib3.exceptions.ProtocolError: (<span class="string">&#x27;Connection aborted.&#x27;</span>, ConnectionResetError(104, <span class="string">&#x27;Connection reset by peer&#x27;</span>))</span><br><span class="line"></span><br><span class="line">During handling of the above exception, another exception occurred:</span><br><span class="line"></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/runpy.py&quot;</span>, line 193, <span class="keyword">in</span> _run_module_as_main</span><br><span class="line">    <span class="string">&quot;__main__&quot;</span>, mod_spec)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/runpy.py&quot;</span>, line 85, <span class="keyword">in</span> _run_code</span><br><span class="line">    <span class="built_in">exec</span>(code, run_globals)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/spacy/__main__.py&quot;</span>, line 33, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    plac.call(commands[<span class="built_in">command</span>], sys.argv[1:])</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/plac_core.py&quot;</span>, line 348, <span class="keyword">in</span> call</span><br><span class="line">    cmd, result = parser.consume(arglist)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/plac_core.py&quot;</span>, line 217, <span class="keyword">in</span> consume</span><br><span class="line">    <span class="built_in">return</span> cmd, self.func(*(args + varargs + extraopts), **kwargs)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/spacy/cli/download.py&quot;</span>, line 44, <span class="keyword">in</span> download</span><br><span class="line">    shortcuts = get_json(about.__shortcuts__, <span class="string">&quot;available shortcuts&quot;</span>)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/spacy/cli/download.py&quot;</span>, line 95, <span class="keyword">in</span> get_json</span><br><span class="line">    r = requests.get(url)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/requests/api.py&quot;</span>, line 76, <span class="keyword">in</span> get</span><br><span class="line">    <span class="built_in">return</span> request(<span class="string">&#x27;get&#x27;</span>, url, params=params, **kwargs)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/requests/api.py&quot;</span>, line 61, <span class="keyword">in</span> request</span><br><span class="line">    <span class="built_in">return</span> session.request(method=method, url=url, **kwargs)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/requests/sessions.py&quot;</span>, line 542, <span class="keyword">in</span> request</span><br><span class="line">    resp = self.send(prep, **send_kwargs)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/requests/sessions.py&quot;</span>, line 655, <span class="keyword">in</span> send</span><br><span class="line">    r = adapter.send(request, **kwargs)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/requests/adapters.py&quot;</span>, line 498, <span class="keyword">in</span> send</span><br><span class="line">    raise ConnectionError(err, request=request)</span><br><span class="line">requests.exceptions.ConnectionError: (<span class="string">&#x27;Connection aborted.&#x27;</span>, ConnectionResetError(104, <span class="string">&#x27;Connection reset by peer&#x27;</span>))</span><br></pre></td></tr></table></figure><h2 id="问题分析">2 问题分析</h2><p>不便分析。</p><p>相关issue:</p><blockquote><p><a href="https://github.com/explosion/spaCy/issues/1510">ConnectionError while installing nlp models #1510</a></p></blockquote><h2 id="问题解决">3 问题解决</h2><p>正常情况下，应该使用官方的命令来安装spaCy工具和最匹配的预训练模型。</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">pip install spacy</span><br><span class="line">python -m spacy download en_core_web_sm</span><br></pre></td></tr></table></figure><p>但现在网络连接被重置，因此只能通过手动处理了。</p><p>spaCy在GitHub上同步存放了可下载的模型。</p><blockquote><p><strong><a href="https://github.com/explosion/spacy-models">spaCymodels</a></strong></p><p>This repository contains <ahref="https://github.com/explosion/spacy-models/releases">releases</a>of models for the <a href="https://github.com/explosion/spaCy">spaCy</a>NLP library. For more info on how to download, install and use themodels, see the <a href="https://spacy.io/usage/models">modelsdocumentation</a>.</p></blockquote><p>在无法自动安装的情况下，可以手动选择安装指定的<code>.tar.gz</code>包，例如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># pip install .tar.gz archive from path or URL</span></span><br><span class="line">pip install /Users/you/en_core_web_sm-2.1.0.tar.gz</span><br><span class="line">pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.1.0/en_core_web_sm-2.1.0.tar.gz</span><br></pre></td></tr></table></figure><p>采用此方法，意味着需要手动翻阅<code>release</code>中的资源，找出合适的预训练模型。例如，当前最新的<code>en_core_web_sm</code>模型是<ahref="https://github.com/explosion/spacy-models/releases/tag/en_core_web_sm-2.3.1">en_core_web_sm-2.3.1</a>。</p><p>也可通过spaCy官方的链接确定合适的预训练模型版本，以<code>en_core_web_sm</code>为例：</p><blockquote><p><a href="https://spacy.io/models/en#en_core_web_sm">English</a></p><p>Available pretrained statistical models for English</p></blockquote><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><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">(pytorch) shenjiayun@server3 ~/Dev/VisualEntailment $ pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz</span><br><span class="line">Collecting https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz</span><br><span class="line">  Downloading https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.3.1/en_core_web_sm-2.3.1.tar.gz (12.0 MB)</span><br><span class="line">     |████████████████████████████████| 12.0 MB 427 kB/s </span><br><span class="line">Requirement already satisfied: spacy&lt;2.4.0,&gt;=2.3.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from en-core-web-sm==2.3.1) (2.3.5)</span><br><span class="line">Requirement already satisfied: thinc&lt;7.5.0,&gt;=7.4.1 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (7.4.5)</span><br><span class="line">Requirement already satisfied: tqdm&lt;5.0.0,&gt;=4.38.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (4.55.0)</span><br><span class="line">Requirement already satisfied: requests&lt;3.0.0,&gt;=2.13.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (2.25.1)</span><br><span class="line">Requirement already satisfied: setuptools <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (51.0.0.post20201207)</span><br><span class="line">Requirement already satisfied: cymem&lt;2.1.0,&gt;=2.0.2 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (2.0.4)</span><br><span class="line">Requirement already satisfied: preshed&lt;3.1.0,&gt;=3.0.2 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (3.0.2)</span><br><span class="line">Requirement already satisfied: srsly&lt;1.1.0,&gt;=1.0.2 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (1.0.5)</span><br><span class="line">Requirement already satisfied: plac&lt;1.2.0,&gt;=0.9.6 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (1.1.0)</span><br><span class="line">Requirement already satisfied: numpy&gt;=1.15.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (1.19.2)</span><br><span class="line">Requirement already satisfied: catalogue&lt;1.1.0,&gt;=0.0.7 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (1.0.0)</span><br><span class="line">Requirement already satisfied: blis&lt;0.8.0,&gt;=0.4.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (0.4.1)</span><br><span class="line">Requirement already satisfied: wasabi&lt;1.1.0,&gt;=0.4.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (0.8.0)</span><br><span class="line">Requirement already satisfied: murmurhash&lt;1.1.0,&gt;=0.28.0 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (1.0.5)</span><br><span class="line">Requirement already satisfied: importlib-metadata&gt;=0.20 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from catalogue&lt;1.1.0,&gt;=0.0.7-&gt;spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (2.0.0)</span><br><span class="line">Requirement already satisfied: zipp&gt;=0.5 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from importlib-metadata&gt;=0.20-&gt;catalogue&lt;1.1.0,&gt;=0.0.7-&gt;spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (3.4.0)</span><br><span class="line">Requirement already satisfied: urllib3&lt;1.27,&gt;=1.21.1 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from requests&lt;3.0.0,&gt;=2.13.0-&gt;spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (1.26.2)</span><br><span class="line">Requirement already satisfied: chardet&lt;5,&gt;=3.0.2 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from requests&lt;3.0.0,&gt;=2.13.0-&gt;spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (4.0.0)</span><br><span class="line">Requirement already satisfied: certifi&gt;=2017.4.17 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from requests&lt;3.0.0,&gt;=2.13.0-&gt;spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (2020.12.5)</span><br><span class="line">Requirement already satisfied: idna&lt;3,&gt;=2.5 <span class="keyword">in</span> /home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages (from requests&lt;3.0.0,&gt;=2.13.0-&gt;spacy&lt;2.4.0,&gt;=2.3.0-&gt;en-core-web-sm==2.3.1) (2.10)</span><br><span class="line">Building wheels <span class="keyword">for</span> collected packages: en-core-web-sm</span><br><span class="line">  Building wheel <span class="keyword">for</span> en-core-web-sm (setup.py) ... <span class="keyword">done</span></span><br><span class="line">  Created wheel <span class="keyword">for</span> en-core-web-sm: filename=en_core_web_sm-2.3.1-py3-none-any.whl size=12047106 sha256=dd9f847a5f35d1760f70b07ab8e8a663ae2a10364a8d43ee93d2b0eab246de3d</span><br><span class="line">  Stored <span class="keyword">in</span> directory: /home/shenjiayun/.cache/pip/wheels/b7/0d/f0/7ecae8427c515065d75410989e15e5785dd3975fe06e795cd9</span><br><span class="line">Successfully built en-core-web-sm</span><br><span class="line">Installing collected packages: en-core-web-sm</span><br><span class="line">Successfully installed en-core-web-sm-2.3.1</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a
href=&quot;https://spacy.io/&quot;&gt;spaCy&lt;/a&gt;是一款非常好用的自然语言处理工具，不过也许是因为一些原因，无法正常下载spaCy官方的预训练模型了，网络连接被重置，报错&lt;code&gt;ProtocolError: (&#39;Connection aborted.&#39;, ConnectionResetError(104, &#39;Connection reset by peer&#39;))&lt;/code&gt;。为了解决该问题，可以尝试手动选择链接下载模型资源。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Troubleshooting" scheme="https://heary.cn/tags/Troubleshooting/"/>
    
    <category term="Python" scheme="https://heary.cn/tags/Python/"/>
    
    <category term="Network" scheme="https://heary.cn/tags/Network/"/>
    
    <category term="NLP" scheme="https://heary.cn/tags/NLP/"/>
    
    <category term="spaCy" scheme="https://heary.cn/tags/spaCy/"/>
    
  </entry>
  
  <entry>
    <title>IPython无法自动补全且因jedi报TypeError而退出问题解决</title>
    <link href="https://heary.cn/posts/IPython%E6%97%A0%E6%B3%95%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E4%B8%94%E5%9B%A0jedi%E6%8A%A5TypeError%E8%80%8C%E9%80%80%E5%87%BA%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"/>
    <id>https://heary.cn/posts/IPython%E6%97%A0%E6%B3%95%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E4%B8%94%E5%9B%A0jedi%E6%8A%A5TypeError%E8%80%8C%E9%80%80%E5%87%BA%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/</id>
    <published>2020-12-31T12:13:11.000Z</published>
    <updated>2022-08-07T04:02:09.608Z</updated>
    
    <content type="html"><![CDATA[<p>ipython7.19.0的自动补全失效，且回车后出现大段报错，提示jedi中<code>TypeError: __init__() got an unexpected keyword argument 'column'</code>。本文对问题进行排查并给出解决方案。</p><span id="more"></span><h1id="ipython无法自动补全且因jedi报typeerror而退出问题解决">IPython无法自动补全且因jedi报TypeError而退出问题解决</h1><h2 id="问题描述">1 问题描述</h2><p>使用最新版的IPython7.19.0时，发现无法Tab自动补全，且回车后会出现报错，具体情境例如：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line">(pytorch) shenjiayun@server3 ~/Dev/VisualEntailment $ ipython</span><br><span class="line">Python <span class="number">3.7</span><span class="number">.9</span> (default, Aug <span class="number">31</span> <span class="number">2020</span>, <span class="number">12</span>:<span class="number">42</span>:<span class="number">55</span>) </span><br><span class="line"><span class="type">Type</span> <span class="string">&#x27;copyright&#x27;</span>, <span class="string">&#x27;credits&#x27;</span> <span class="keyword">or</span> <span class="string">&#x27;license&#x27;</span> <span class="keyword">for</span> more information</span><br><span class="line">IPython <span class="number">7.19</span><span class="number">.0</span> -- An enhanced Interactive Python. <span class="type">Type</span> <span class="string">&#x27;?&#x27;</span> <span class="keyword">for</span> <span class="built_in">help</span>.</span><br><span class="line"></span><br><span class="line">In [<span class="number">1</span>]: <span class="keyword">import</span> torch</span><br><span class="line"></span><br><span class="line">In [<span class="number">2</span>]: x = torch.rand([<span class="number">4</span>, <span class="number">36</span>, <span class="number">2048</span>])</span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/ptutils.py&quot;</span>, line <span class="number">113</span>, <span class="keyword">in</span> get_completions</span><br><span class="line">    <span class="keyword">yield</span> <span class="keyword">from</span> self._get_completions(body, offset, cursor_position, self.ipy_completer)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/ptutils.py&quot;</span>, line <span class="number">129</span>, <span class="keyword">in</span> _get_completions</span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> completions:</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/core/completer.py&quot;</span>, line <span class="number">438</span>, <span class="keyword">in</span> _deduplicate_completions</span><br><span class="line">    completions = <span class="built_in">list</span>(completions)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/core/completer.py&quot;</span>, line <span class="number">1818</span>, <span class="keyword">in</span> completions</span><br><span class="line">    <span class="keyword">for</span> c <span class="keyword">in</span> self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/<span class="number">1000</span>):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/core/completer.py&quot;</span>, line <span class="number">1862</span>, <span class="keyword">in</span> _completions</span><br><span class="line">    full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/core/completer.py&quot;</span>, line <span class="number">2030</span>, <span class="keyword">in</span> _complete</span><br><span class="line">    cursor_pos, cursor_line, full_text)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/core/completer.py&quot;</span>, line <span class="number">1374</span>, <span class="keyword">in</span> _jedi_matches</span><br><span class="line">    text[:offset], namespaces, column=cursor_column, line=cursor_line + <span class="number">1</span>)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/jedi/api/__init__.py&quot;</span>, line <span class="number">726</span>, <span class="keyword">in</span> __init__</span><br><span class="line">    project=Project(Path.cwd()), **kwds)</span><br><span class="line">TypeError: __init__() got an unexpected keyword argument <span class="string">&#x27;column&#x27;</span></span><br><span class="line"></span><br><span class="line">During handling of the above exception, another exception occurred:</span><br><span class="line"></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/bin/ipython&quot;</span>, line <span class="number">11</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    sys.exit(start_ipython())</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/__init__.py&quot;</span>, line <span class="number">126</span>, <span class="keyword">in</span> start_ipython</span><br><span class="line">    <span class="keyword">return</span> launch_new_instance(argv=argv, **kwargs)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/traitlets/config/application.py&quot;</span>, line <span class="number">845</span>, <span class="keyword">in</span> launch_instance</span><br><span class="line">    app.start()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/ipapp.py&quot;</span>, line <span class="number">356</span>, <span class="keyword">in</span> start</span><br><span class="line">    self.shell.mainloop()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/interactiveshell.py&quot;</span>, line <span class="number">564</span>, <span class="keyword">in</span> mainloop</span><br><span class="line">    self.interact()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/interactiveshell.py&quot;</span>, line <span class="number">547</span>, <span class="keyword">in</span> interact</span><br><span class="line">    code = self.prompt_for_code()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/interactiveshell.py&quot;</span>, line <span class="number">475</span>, <span class="keyword">in</span> prompt_for_code</span><br><span class="line">    **self._extra_prompt_options())</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/shortcuts/prompt.py&quot;</span>, line <span class="number">1013</span>, <span class="keyword">in</span> prompt</span><br><span class="line">    <span class="keyword">return</span> self.app.run(set_exception_handler=set_exception_handler)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/application/application.py&quot;</span>, line <span class="number">817</span>, <span class="keyword">in</span> run</span><br><span class="line">    self.run_async(pre_run=pre_run, set_exception_handler=set_exception_handler)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/asyncio/base_events.py&quot;</span>, line <span class="number">587</span>, <span class="keyword">in</span> run_until_complete</span><br><span class="line">    <span class="keyword">return</span> future.result()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/application/application.py&quot;</span>, line <span class="number">783</span>, <span class="keyword">in</span> run_async</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> _run_async2()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/application/application.py&quot;</span>, line <span class="number">771</span>, <span class="keyword">in</span> _run_async2</span><br><span class="line">    <span class="keyword">await</span> self.cancel_and_wait_for_background_tasks()</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/application/application.py&quot;</span>, line <span class="number">872</span>, <span class="keyword">in</span> cancel_and_wait_for_background_tasks</span><br><span class="line">    <span class="keyword">await</span> task</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/buffer.py&quot;</span>, line <span class="number">1854</span>, <span class="keyword">in</span> new_coroutine</span><br><span class="line">    <span class="keyword">await</span> coroutine(*a, **kw)</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/buffer.py&quot;</span>, line <span class="number">1684</span>, <span class="keyword">in</span> async_completer</span><br><span class="line">    document, complete_event</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/completion/base.py&quot;</span>, line <span class="number">270</span>, <span class="keyword">in</span> get_completions_async</span><br><span class="line">    document, complete_event</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/prompt_toolkit/completion/base.py&quot;</span>, line <span class="number">196</span>, <span class="keyword">in</span> get_completions_async</span><br><span class="line">    <span class="keyword">for</span> item <span class="keyword">in</span> self.get_completions(document, complete_event):</span><br><span class="line">  File <span class="string">&quot;/home/shenjiayun/miniconda3/envs/pytorch/lib/python3.7/site-packages/IPython/terminal/ptutils.py&quot;</span>, line <span class="number">116</span>, <span class="keyword">in</span> get_completions</span><br><span class="line">    exc_type, exc_value, exc_tb = sys.exc_info()</span><br><span class="line">NameError: name <span class="string">&#x27;sys&#x27;</span> <span class="keyword">is</span> <span class="keyword">not</span> defined</span><br><span class="line"></span><br><span class="line">If you suspect this <span class="keyword">is</span> an IPython <span class="number">7.19</span><span class="number">.0</span> bug, please report it at:</span><br><span class="line">    https://github.com/ipython/ipython/issues</span><br><span class="line"><span class="keyword">or</span> send an email to the mailing <span class="built_in">list</span> at ipython-dev@python.org</span><br><span class="line"></span><br><span class="line">You can <span class="built_in">print</span> a more detailed traceback right now <span class="keyword">with</span> <span class="string">&quot;%tb&quot;</span>, <span class="keyword">or</span> use <span class="string">&quot;%debug&quot;</span></span><br><span class="line">to interactively debug it.</span><br><span class="line"></span><br><span class="line">Extra-detailed tracebacks <span class="keyword">for</span> bug-reporting purposes can be enabled via:</span><br><span class="line">    %config Application.verbose_crash=<span class="literal">True</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="问题排查">2 问题排查</h2><p>根据常识推断，ipython使用的是jedi作为languagesever实现代码自动补全。当自动补全失灵的时候，那应该和jedi有关。</p><p>从长段的报错中，能看到错误定位于<code>jedi/api/__init__.py", line 726, in __init__</code>，提示<code>TypeError: __init__() got an unexpected keyword argument 'column'</code>。确实是调用jedi时出现了错误。</p><p>由此查jedi的开源项目，发现issue：</p><blockquote><p><a href="https://github.com/davidhalter/jedi/issues/1714">IPython(&lt;=7.19) incompatible with jedi 0.18.0 #1714</a></p><p>Relevant traceback reads as follows:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">  File &quot;../venv/lib/python3.8/site-packages/IPython/core/completer.py&quot;, line 2029, in _complete</span><br><span class="line">    completions = self._jedi_matches(</span><br><span class="line">  File &quot;../venv/lib/python3.8/site-packages/IPython/core/completer.py&quot;, line 1373, in _jedi_matches</span><br><span class="line">    interpreter = jedi.Interpreter(</span><br><span class="line">  File &quot;../venv/lib/python3.8/site-packages/jedi/api/__init__.py&quot;, line 725, in __init__</span><br><span class="line">    super().__init__(code, environment=environment,</span><br><span class="line">TypeError: __init__() got an unexpected keyword argument &#x27;column&#x27;</span><br></pre></td></tr></table></figure></blockquote><p>经过确认，jedi所有者表示该问题系ipython作为下游应用的调用问题，待下游应用更新解决。</p><blockquote><p><strong><ahref="https://github.com/davidhalter">davidhalter</a></strong> commented<ahref="https://github.com/davidhalter/jedi/issues/1714#issuecomment-751273878">6days ago</a></p><p>I think we should continue the discussion in <ahref="https://github.com/ipython/ipython/issues/12740">ipython/ipython#12740</a>.IMOthis is an downstream issue and they should just do a new release.</p></blockquote><h2 id="解决方案">3 解决方案</h2><p>既然ipython目前最新的7.19.0版本无法正确调用最新的jedi0.18.0版本，那就把jedi版本降级到0.17即可。</p><p>通过conda检索可用的jedi版本：</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">conda search jedi</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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><span class="line">(pytorch) C:\Users\jyshen&gt;conda search jedi</span><br><span class="line">Loading channels: <span class="keyword">done</span></span><br><span class="line"><span class="comment"># Name                       Version           Build  Channel</span></span><br><span class="line">jedi                           0.8.1          py26_0  pkgs/free</span><br><span class="line">jedi                           0.8.1          py27_0  pkgs/free</span><br><span class="line">jedi                           0.8.1          py33_0  pkgs/free</span><br><span class="line">jedi                           0.8.1          py34_0  pkgs/free</span><br><span class="line">jedi                           0.9.0          py26_0  pkgs/free</span><br><span class="line">jedi                           0.9.0          py27_0  pkgs/free</span><br><span class="line">jedi                           0.9.0          py27_1  pkgs/free</span><br><span class="line">jedi                           0.9.0          py33_0  pkgs/free</span><br><span class="line">jedi                           0.9.0          py34_0  pkgs/free</span><br><span class="line">jedi                           0.9.0          py34_1  pkgs/free</span><br><span class="line">jedi                           0.9.0          py35_0  pkgs/free</span><br><span class="line">jedi                           0.9.0          py35_1  pkgs/free</span><br><span class="line">jedi                           0.9.0          py36_1  pkgs/free</span><br><span class="line">jedi                          0.10.2          py27_0  pkgs/free</span><br><span class="line">jedi                          0.10.2          py27_2  pkgs/free</span><br><span class="line">jedi                          0.10.2  py27h4f12af3_0  pkgs/main</span><br><span class="line">jedi                          0.10.2          py35_0  pkgs/free</span><br><span class="line">jedi                          0.10.2          py35_2  pkgs/free</span><br><span class="line">jedi                          0.10.2  py35h3350e2d_0  pkgs/main</span><br><span class="line">jedi                          0.10.2          py36_0  pkgs/free</span><br><span class="line">jedi                          0.10.2          py36_2  pkgs/free</span><br><span class="line">jedi                          0.10.2  py36hed927a0_0  pkgs/main</span><br><span class="line">jedi                          0.11.0          py27_1  pkgs/main</span><br><span class="line">jedi                          0.11.0          py27_2  pkgs/main</span><br><span class="line">jedi                          0.11.0  py27h53c0d9b_0  pkgs/main</span><br><span class="line">jedi                          0.11.0          py35_1  pkgs/main</span><br><span class="line">jedi                          0.11.0          py35_2  pkgs/main</span><br><span class="line">jedi                          0.11.0  py35hc856aec_0  pkgs/main</span><br><span class="line">jedi                          0.11.0          py36_1  pkgs/main</span><br><span class="line">jedi                          0.11.0          py36_2  pkgs/main</span><br><span class="line">jedi                          0.11.0  py36hc338079_0  pkgs/main</span><br><span class="line">jedi                          0.11.1          py27_0  pkgs/main</span><br><span class="line">jedi                          0.11.1          py27_1  pkgs/main</span><br><span class="line">jedi                          0.11.1          py35_0  pkgs/main</span><br><span class="line">jedi                          0.11.1          py35_1  pkgs/main</span><br><span class="line">jedi                          0.11.1          py36_0  pkgs/main</span><br><span class="line">jedi                          0.11.1          py36_1  pkgs/main</span><br><span class="line">jedi                          0.12.0          py27_0  pkgs/main</span><br><span class="line">jedi                          0.12.0          py27_1  pkgs/main</span><br><span class="line">jedi                          0.12.0          py35_0  pkgs/main</span><br><span class="line">jedi                          0.12.0          py35_1  pkgs/main</span><br><span class="line">jedi                          0.12.0          py36_0  pkgs/main</span><br><span class="line">jedi                          0.12.0          py36_1  pkgs/main</span><br><span class="line">jedi                          0.12.0          py37_1  pkgs/main</span><br><span class="line">jedi                          0.12.1          py27_0  pkgs/main</span><br><span class="line">jedi                          0.12.1          py35_0  pkgs/main</span><br><span class="line">jedi                          0.12.1          py36_0  pkgs/main</span><br><span class="line">jedi                          0.12.1          py37_0  pkgs/main</span><br><span class="line">jedi                          0.13.1          py27_0  pkgs/main</span><br><span class="line">jedi                          0.13.1          py36_0  pkgs/main</span><br><span class="line">jedi                          0.13.1          py37_0  pkgs/main</span><br><span class="line">jedi                          0.13.2          py27_0  pkgs/main</span><br><span class="line">jedi                          0.13.2          py36_0  pkgs/main</span><br><span class="line">jedi                          0.13.2          py37_0  pkgs/main</span><br><span class="line">jedi                          0.13.3          py27_0  pkgs/main</span><br><span class="line">jedi                          0.13.3          py36_0  pkgs/main</span><br><span class="line">jedi                          0.13.3          py37_0  pkgs/main</span><br><span class="line">jedi                          0.14.1          py27_0  pkgs/main</span><br><span class="line">jedi                          0.14.1          py36_0  pkgs/main</span><br><span class="line">jedi                          0.14.1          py37_0  pkgs/main</span><br><span class="line">jedi                          0.14.1          py38_0  pkgs/main</span><br><span class="line">jedi                          0.15.1          py27_0  pkgs/main</span><br><span class="line">jedi                          0.15.1          py36_0  pkgs/main</span><br><span class="line">jedi                          0.15.1          py37_0  pkgs/main</span><br><span class="line">jedi                          0.15.1          py38_0  pkgs/main</span><br><span class="line">jedi                          0.15.2          py27_0  pkgs/main</span><br><span class="line">jedi                          0.15.2          py36_0  pkgs/main</span><br><span class="line">jedi                          0.15.2          py37_0  pkgs/main</span><br><span class="line">jedi                          0.15.2          py38_0  pkgs/main</span><br><span class="line">jedi                          0.16.0          py36_0  pkgs/main</span><br><span class="line">jedi                          0.16.0          py36_1  pkgs/main</span><br><span class="line">jedi                          0.16.0          py37_0  pkgs/main</span><br><span class="line">jedi                          0.16.0          py37_1  pkgs/main</span><br><span class="line">jedi                          0.16.0          py38_0  pkgs/main</span><br><span class="line">jedi                          0.16.0          py38_1  pkgs/main</span><br><span class="line">jedi                          0.17.0          py36_0  pkgs/main</span><br><span class="line">jedi                          0.17.0          py37_0  pkgs/main</span><br><span class="line">jedi                          0.17.0          py38_0  pkgs/main</span><br><span class="line">jedi                          0.17.1          py36_0  pkgs/main</span><br><span class="line">jedi                          0.17.1          py37_0  pkgs/main</span><br><span class="line">jedi                          0.17.1          py38_0  pkgs/main</span><br><span class="line">jedi                          0.17.2          py36_0  pkgs/main</span><br><span class="line">jedi                          0.17.2  py36haa95532_1  pkgs/main</span><br><span class="line">jedi                          0.17.2          py37_0  pkgs/main</span><br><span class="line">jedi                          0.17.2  py37haa95532_1  pkgs/main</span><br><span class="line">jedi                          0.17.2          py38_0  pkgs/main</span><br><span class="line">jedi                          0.17.2  py38haa95532_1  pkgs/main</span><br><span class="line">jedi                          0.17.2  py39haa95532_1  pkgs/main</span><br><span class="line">jedi                          0.18.0  py36haa95532_0  pkgs/main</span><br><span class="line">jedi                          0.18.0  py37haa95532_0  pkgs/main</span><br><span class="line">jedi                          0.18.0  py38haa95532_0  pkgs/main</span><br><span class="line">jedi                          0.18.0  py39haa95532_0  pkgs/main</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过conda安装指定版本的jedi：</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">conda install jedi=0.17</span><br></pre></td></tr></table></figure><p>再次测试ipython，不再出现该问题。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;ipython
7.19.0的自动补全失效，且回车后出现大段报错，提示jedi中&lt;code&gt;TypeError: __init__() got an unexpected keyword argument &#39;column&#39;&lt;/code&gt;。本文对问题进行排查并给出解决方案。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Troubleshooting" scheme="https://heary.cn/tags/Troubleshooting/"/>
    
    <category term="Python" scheme="https://heary.cn/tags/Python/"/>
    
    <category term="Ipython" scheme="https://heary.cn/tags/Ipython/"/>
    
    <category term="jedi" scheme="https://heary.cn/tags/jedi/"/>
    
    <category term="conda" scheme="https://heary.cn/tags/conda/"/>
    
  </entry>
  
  <entry>
    <title>云原生与微服务概念笔记</title>
    <link href="https://heary.cn/posts/%E4%BA%91%E5%8E%9F%E7%94%9F%E4%B8%8E%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%A6%82%E5%BF%B5%E7%AC%94%E8%AE%B0/"/>
    <id>https://heary.cn/posts/%E4%BA%91%E5%8E%9F%E7%94%9F%E4%B8%8E%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%A6%82%E5%BF%B5%E7%AC%94%E8%AE%B0/</id>
    <published>2020-12-28T09:16:14.000Z</published>
    <updated>2022-08-07T04:02:09.876Z</updated>
    
    <content type="html"><![CDATA[<p>云原生与微服务概念入门。</p><span id="more"></span><h1 id="云原生与微服务概念笔记">云原生与微服务概念笔记</h1><p>参考书籍：</p><blockquote><p>朱荣鑫，黄迪璇，张天. Go语言高并发与微服务实战[M].北京：中国铁道出版社有限公司，2020.</p></blockquote><h2 id="云原生架构">1 云原生架构</h2><h3 id="云计算的历史">1.1 云计算的历史</h3><h4 id="云计算的基础虚拟化技术">1.1.1 云计算的基础：虚拟化技术</h4><p>虚拟化是云计算的基石。</p><ul><li>1955年，MIT的JohnMcCarthy提出<strong>time-sharing</strong>技术。</li><li>1959年6月，Christopher Strachey在国际信息处理大会发表《Time Sharingin Large Fast Computer》论文，提出<strong>虚拟化</strong>概念。</li><li>1965年8月，IBM推出TSS(Time Sharing System)和VMM(Virtual MachineMonitor)，是最原始的虚拟机技术。</li><li>20世纪60年代中期，美科学家HCR Licklider提出计算机互联系统，BobTaylor和Larry Robert开发<strong>ARPANET</strong>。</li><li>1978年，IBM获得<strong>RAID</strong>专利，融物理设备为LUN(LogicalUnit Number)，首次将虚拟化引入存储。</li><li>1990，Unity Computing概念复苏，亦称GridComputing，旨在实现公共计算服务给全世界用户使用。</li><li>1998年，VMware成立并首次引入x86虚拟化技术。</li><li>2000年，IEEE颁布<strong>VPN(Virtual PrivateNetwork)</strong>标准草案。</li><li>2002年，Amazon上线<strong>AWS(Amazon.com WebService)</strong>，旨在商品目录以SOAP接口开放。</li><li>2005年，开源虚拟机Xen 3.0发布，支持Intel VT和IA64。</li><li>2006年10月，以色列创业公司Qumranet宣布<strong>KVM</strong>诞生，且KVM模块的源码成为Linux内核源码的一部分。</li><li>2009年4月，VMware推出首款云操作系统VMware vSphere。</li></ul><h4 id="基于虚拟机的云计算">1.1.2 基于虚拟机的云计算</h4><p>虚拟化技术成熟，云计算市场出现。</p><ul><li>2006年，AWS推出S3(Simple Storage Service)和EC2(Elastic ComputeCloud)。</li><li>2007年，IBM发布云计算商业解决方案，推出Blue Cloud计划。</li><li>2008年，Google App Engine发布，用于Web开发和托管。</li><li>2009年，Heroku推出首款公有云PaaS(Platform as a Service)。</li><li>2010年，微软推出Azure。</li><li>2010年，Rackspace Hosting和NASA推出OpenStack开源云软件计划。</li><li>2011年，Pivotal推出开源PaaS——Cloud Foundry。</li><li>2013年，Docker发布，其使用LXC并封装一些新功能。</li><li>2014年，AWS推出Lambda，在AWS中直接运行代码而无需考虑服务器配置和管理，即FaaS(Functionas a Service)、Serverless。</li></ul><p>云计算模式：</p><ol type="1"><li>IaaS：Infrastructure as a Service，提供基础资源。</li><li>SaaS：Software as aService，提供搭建、实施、维护等一系列的软件服务。拿来即用。</li><li>PaaS：Platform as aService，是SaaS的延申，抽象硬件和操作系统，对外提供运行时环境作为部署平台，便于扩展。</li></ol><table><thead><tr class="header"><th>类型</th><th>传统IT</th><th>IaaS</th><th>PaaS</th><th>SaaS</th></tr></thead><tbody><tr class="odd"><td>应用程序</td><td>×</td><td>×</td><td>×</td><td>√</td></tr><tr class="even"><td>数据</td><td>×</td><td>×</td><td>×</td><td>√</td></tr><tr class="odd"><td>运行时</td><td>×</td><td>×</td><td>√</td><td>√</td></tr><tr class="even"><td>中间件</td><td>×</td><td>×</td><td>√</td><td>√</td></tr><tr class="odd"><td>操作系统</td><td>×</td><td>×</td><td>√</td><td>√</td></tr><tr class="even"><td>虚拟化</td><td>×</td><td>√</td><td>√</td><td>√</td></tr><tr class="odd"><td>服务器</td><td>×</td><td>√</td><td>√</td><td>√</td></tr><tr class="even"><td>存储</td><td>×</td><td>√</td><td>√</td><td>√</td></tr><tr class="odd"><td>网络</td><td>×</td><td>√</td><td>√</td><td>√</td></tr></tbody></table><ul><li>×表示云计算厂商不负责，√表示云计算厂商负责。</li></ul><h4 id="容器化和容器编排">1.1.3 容器化和容器编排</h4><p>容器化本质上是虚拟化的改进。</p><p>虚拟化通过Hypervisor分离操作系统，容器化共享操作系统。</p><p>LXC(LinuxContainer)侧重容器运行环境的资源隔离和限制，类似进程沙箱，而没有容器镜像打包技术，所以没有普及。</p><p>Docker在LXC的基础之上，建立了一套镜像打包和运行机制，将应用程序和依赖项打包成镜像文件，换别的Docker中也能运行，实现Build,Ship and Run。</p><p>容器编排技术经过Mesos、Swarm和Kubernetes三家竞争，最后随着Kubernetes的成熟及其与Docker的融合，PaaS技术的主流路线过渡到了KubernetesDocker。2018年，Kubernetes占据统治地位。</p><h4 id="云计算演进总结">1.1.4 云计算演进总结</h4><p>企业降低对IT基础设施的直接投入，而是通过上云来获取计算和存储能力，按时按需计费。</p><p>云计算降低了IT支出，降低了行业技术壁垒。</p><h3 id="云原生是什么">1.2 云原生是什么</h3><h4 id="云原生出现的背景">1.2.1 云原生出现的背景</h4><p>移动互联网，业务高速发展，快速迭代。</p><h4 id="云原生的定义">1.2.2 云原生的定义</h4><p>Pivotal（云原生应用提出者）：</p><ol type="1"><li>DevOps</li><li>持续集成</li><li>微服务架构</li><li>容器化</li></ol><p>CNCF(Cloud Native Computing Foundation)：</p><ol type="1"><li>应用容器化</li><li>面向微服务架构</li><li>应用支持容器的编排调度</li></ol><blockquote><p><ahref="https://github.com/cncf/foundation/blob/master/charter.md#1-mission-of-the-cloud-native-computing-foundation">Missionof the Cloud Native Computing Foundation</a></p><p>The Foundation’s mission is to make cloud native computingubiquitous. The CNCF Cloud Native Definition v1.0 says:</p><p><strong>Cloud native technologies empower organizations to build andrun scalable applications in modern, dynamic environments such aspublic, private, and hybrid clouds. Containers, service meshes,microservices, immutable infrastructure, and declarative APIs exemplifythis approach.</strong></p><p>These techniques enable loosely coupled systems that are resilient,manageable, and observable. Combined with robust automation, they allowengineers to make high-impact changes frequently and predictably withminimal toil.</p><p>The Cloud Native Computing Foundation seeks to drive adoption of thisparadigm by fostering and sustaining an ecosystem of open source,vendor-neutral projects. We democratize state-of-the-art patterns tomake these innovations accessible for everyone.</p></blockquote><h4 id="云原生与12因素">1.2.3 云原生与12因素</h4><p>2012年，Heroku提出12-Factors云应用设计理念。</p><ol type="1"><li>Codebase：基准代码。用一个代码库做版本控制和多次部署。</li><li>Dependencies：依赖。显式声明依赖关系，通过工具（Maven, Bundler,NPM等）隔离依赖，目的是不依赖于部署环境。</li><li>Config：配置。通过操作系统级的环境变量将配置信息应用到各个部署环境。</li><li>Backing services：后端服务。视后端服务为附加资源。</li><li>Build, release, run：严格分离构建和运行。</li><li>Process：进程。应用程序作为一个或多个无状态进程执行。任何持久化数据都存储于后端服务。</li><li>Portbinding：端口绑定。完全自我加载，不依赖网络服务器即可提供网络服务。通过监听端口来服务发来的请求。</li><li>Concurrency：并发。通过进程模型进行扩展。水平向外扩展应用进程。</li><li>Disposability：易处理。快速启动、优雅终止可最大化健壮性。包括，快速而有弹性的扩展、对变更的部署和宕机恢复能力。</li><li>Dev/prodparity：开发环境与线上环境等价。尽可能保持开发、预发布和线上环境的相似，实现持续交付与部署。</li><li>Logs：日志。视日志为事件流，通过集中式服务收集、聚合、检索和分析日志。</li><li>Adminprocesses：管理进程。后台管理任务当作一次性进程执行，如：数据库迁移任务。</li></ol><p>核心思想：</p><ol type="1"><li>使用声明式格式来搭建自动化。（学习成本低）</li><li>和底层操作系统保持简洁的契约。（可移植性强）</li><li>适合在现代的云平台上部署。（避免额外的管理需求）</li><li>最小化开发与生产的分歧。（持续部署、灵活性强）</li><li>在工具、架构和开发实践不产生重大变化的前提下实现扩展。</li></ol><h3 id="云原生的基础架构">1.3 云原生的基础架构</h3><p>云原生应用利用微服务、服务网络、容器、DevOps和声明式API等代表性技术，来构建容错性好、易于管理和便于观察的松耦合系统。</p><h4 id="微服务">1.3.1 微服务</h4><p>将明确定义的功能分成更小的服务，服务之间是松耦合的，每个服务可以独立迭代。</p><p>优点：降低系统复杂度、独立部署、独立扩展、跨语言编程。</p><p>缺点：需要构建、测试、部署、运行数十个独立的服务，支持多种语言和环境，还引入了分布式系统的复杂性，如：网络延迟、容错性、消息序列化、不可靠网络、异步机制、版本化和差异化。</p><h4 id="容器">1.3.2 容器</h4><p>将微服务和所需的所有配置、依赖关系和环境变量打包成容器镜像，轻松移植到新的服务器节点。</p><p>人力运维部署成本太大，在Docker基础之上，引入Kubernetes可以实现容器集群的自动化部署、自动扩缩容和维护等功能。</p><p>Kubernetes不仅支持Docker，还支持Rocket等其他容器技术。</p><h4 id="服务网络">1.3.3 服务网络</h4><p>微服务技术架构有：</p><ol type="1"><li>侵入式架构：服务框架嵌入程序代码，开发者组合各种组件（如：RPC、负载均衡、熔断等）。</li><li>非侵入式架构：以代理的形式，与应用程序部署在一起，接管应用程序网络并对其透明，开发者只关心自身业务。</li></ol><p>服务网络（Service Mesh）对运行于其上的云原生应用是透明的。</p><p>服务网格是处理服务间通信的基础设施层。它负责构成现代云原生应用程序的复杂服务拓扑来可靠地交付请求。在实践中，ServiceMesh通常以轻量级网络代理阵列的形式实现，这些代理与应用程序代码部署在一起，对应用程序来说无需感知代理的存在。</p><p>开源的服务网络软件：Istio、Linkerd、Envoy、Dubbo Mesh等。ServiceMesh可以运行在Kubernetes上。</p><h4 id="devops">1.3.4 DevOps</h4><p>DevOps包含三个部分：</p><ol type="1"><li>开发</li><li>测试</li><li>运维</li></ol><p>DevOps</p><ol type="1"><li>Dev<ol type="1"><li>Plan</li><li>Create</li><li>Verify</li><li>Package</li></ol></li><li>Ops<ol type="1"><li>Release</li><li>Configure</li><li>Monitor</li></ol></li></ol><h3 id="小结">1.4 小结</h3><p>云原生将云目标从节约IT成本转向推动业务增长。</p><h2 id="微服务概述">2 微服务概述</h2><h3 id="系统架构的演进">2.1 系统架构的演进</h3><h4 id="单体架构">2.1.1 单体架构</h4><p>巨石（Monolith）应用，易于测试、部署，但编译慢、局部改动就要重新部署、技术难扩展。</p><h4 id="垂直分层架构">2.1.2 垂直分层架构</h4><p>对单体架构垂直拆封，例如：用户界面层、业务逻辑层、数据访问层。</p><h4 id="面向服务架构soaservice-oriented-architecture">2.1.3面向服务架构SOA(Service-Oriented Architecture)</h4><p>每个服务登记到服务登记中心上。</p><p>服务消费者从服务登记中心寻找，通过发送消息由企业服务总线（EnterpriseService Bus）转换后发送给相应的服务来调用服务。</p><p>SOA是中心化架构，关注系统集成。</p><h4 id="微服务架构">2.1.4 微服务架构</h4><p>大型复杂软件有一个或多个微服务组成。微服务可独立部署、松耦合、仅关注完成单一职责。每个职责代表一个高内聚的业务能力。</p><p>微服务是去中心化架构，关注分散管理、代码重用、快速扩展。</p><p>微服务架构的特点：</p><ol type="1"><li>系统服务曾分离为一个个的微服务。</li><li>微服务遵循单一原则。</li><li>微服务之间采用RESTful等轻量级协议通信。</li><li>微服务采用容器技术部署，运行在自己的独立进程中。</li><li>每个微服务都有独立的业务开发活动和周期。</li></ol><p>如果拆分的服务过多，服务治理成本会极大升高，开发调试成本高。服务之间相互依赖，还可能形成复杂依赖链，异常时出现雪崩效应。</p><h4 id="云原生架构-1">2.1.5 云原生架构</h4><p>代表技术：</p><ol type="1"><li>容器</li><li>服务网络</li><li>微服务</li><li>不可变基础设施</li><li>声明式API</li></ol><p>四要素：</p><ol type="1"><li>微服务</li><li>容器化</li><li>DevOps</li><li>持续交付</li></ol><p>云原生架构依托PaaS产品：</p><ol type="1"><li>Codeless：服务开发。</li><li>Applicationless：服务发布。</li><li>Serverless：服务运维。</li></ol><h3 id="常见的微服务框架">2.2 常见的微服务框架</h3><h4 id="java中的spring-cloud与dubbo框架">2.2.1 Java中的SpringCloud与Dubbo框架</h4><p>Spring Cloud将各家公司开发的比较成熟的服务框架组合起来，通过SprIngBoot风格再封装，屏蔽复杂配置和实现原理，对外提供简单易懂的工具包。</p><p>Dubbo框架是分布式服务框架，提供RPC方案和SOA服务治理方案，特点主要在：远程通信、集群容错、自动发现。</p><h4 id="go语言中的go-kit与go-micro框架">2.2.2 Go语言中的Go Kit与GoMicro框架</h4><p>Go-kit（gokit.io）是Go语言工具包的集合。</p><p>Go-kit不仅是微服务工具包，也非常适合构建优雅的架构设计。</p><p>Go-kit应用程序架构：</p><ol type="1"><li>传输层：网络通信，HTTP、gRPC等，或NATS发布订阅系统。</li><li>接口层：服务对外提供的接口方法定义为端点（Endpoint），端点使用传输层的通信对外提供服务。</li><li>服务层：业务逻辑，不考虑传输、编解码。</li></ol><p>Go Micro是Go语言实现的插件化RPC微服务框架，包含组件：</p><ol type="1"><li>Registry：服务发现，解析服务名字到服务地址。</li><li>Selector：基于Registry的负载均衡组件。</li><li>Broker：发布和订阅组件。服务之间基于消息中间件的异步通信。</li><li>Transport：服务之间的同步通信。</li><li>Codec：服务之间的消息编解码组件。</li><li>Server：服务主体。</li><li>Client：提供访问微服务的客户端。</li></ol><h3 id="微服务设计的六大原则">2.3 微服务设计的六大原则</h3><ol type="1"><li>高内聚低耦合</li><li>高度自治</li><li>以业务为中心</li><li>弹性设计</li><li>日志与监控</li><li>自动化</li></ol><h3 id="领域驱动设计">2.4 领域驱动设计</h3><p>Domain Driven Design</p><p>分为4层：</p><ol type="1"><li>Interface</li><li>Application</li><li>Domain</li><li>Infrastructure</li></ol><p>业务系统</p><ul><li>核心域（Core Domain）：如，秒杀操作。</li><li>子域<ul><li>支撑子域（GenericSubdomain）：如，活动管理领域（创建秒杀、查询秒杀）。</li><li>通用子域（Common Subdomain）：如，用户鉴权领域。</li></ul></li></ul><p>限界上下文和子域一一对应，一个限界上下文只使用一套通用语言，并保证其清晰简洁。</p><p>实际情况中，根据业务，有时将多个界限上下文合并。</p><p>随着微服务架构流行，组织内部产生许多小规模团队。组织架构从层级职能组织变成扁平的小团队集群。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;云原生与微服务概念入门。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Cloud" scheme="https://heary.cn/tags/Cloud/"/>
    
    <category term="Cloud Native" scheme="https://heary.cn/tags/Cloud-Native/"/>
    
    <category term="Microservices" scheme="https://heary.cn/tags/Microservices/"/>
    
  </entry>
  
  <entry>
    <title>《远见》读书笔记</title>
    <link href="https://heary.cn/posts/%E3%80%8A%E8%BF%9C%E8%A7%81%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <id>https://heary.cn/posts/%E3%80%8A%E8%BF%9C%E8%A7%81%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/</id>
    <published>2020-12-23T05:21:39.000Z</published>
    <updated>2022-08-07T04:02:09.874Z</updated>
    
    <content type="html"><![CDATA[<p>从图书馆借阅了[加]布莱恩·费瑟斯通豪（<em>BrainFetherstonhaugh</em>）所著的《远见》（<em>The LongView</em>）一书，记录下核心思想。</p><span id="more"></span><h1 id="远见读书笔记">《远见》读书笔记</h1><h2 id="分阶段规划">分阶段规划</h2><p>职业生涯不是短跑比赛，职业生涯的持续时间长的惊人，可分三个阶段：</p><ol type="1"><li>第一阶段：<ol type="1"><li>职业生涯的前15年</li><li>目标：为接下来的两个阶段打好基础</li><li>策略：加添燃料，强势开局</li></ol></li><li>第二阶段：<ol type="1"><li>职业生涯中段的15年</li><li>目标：在长板、爱好与这个世界的需求之间寻找交集，想方设法脱颖而出。</li><li>策略：锚定甜蜜去，聚焦长板</li></ol></li><li>第三阶段：<ol type="1"><li>职业生涯的最后几年</li><li>目标：确定接班人，完成继任计划，完成角色转变，成为顾问、辅助者等。</li><li>策略：优化长尾，发挥持续影响力</li></ol></li></ol><h2 id="储备职场燃料">储备职场燃料</h2><p>成功的可持续职业生涯是靠职场燃料推动的。</p><p>积累、不断更新并精明地消费职场燃料。</p><p>基本地职场燃料：</p><ol type="1"><li>可迁移的技能<ol type="1"><li>解决问题的能力</li><li>说服式沟通技巧</li><li>完成任务的能力</li><li>”人才引力“</li><li>帮助和求助的能力</li><li>情商</li></ol></li><li>有意义的经验<ol type="1"><li>多样性经验，建立新的职业技能；</li><li>在不同的环境中尝试不同的事情、试验不同的做事方法，能创造出更强的决策技能。</li></ol></li><li>持久的关系，即职业生态系统<ol type="1"><li>上司</li><li>客户</li><li>商业伙伴</li><li>身边的人才</li><li>你的同类</li></ol></li></ol><h2 id="职场思维">职场思维</h2><ol type="1"><li>职业生涯的长度：到退休的年数。</li><li>精通一项技能所需的时间：至少需要10000小时的密集训练和联系。</li><li>40岁之后能赚到的个人财富百分比：85%~90%，大多数人的财富积累要蓄力到40岁、50岁甚至60岁才爆发出来。</li><li>社交网络好友：并不是越多越好。</li><li>职场支持者人数：找到3~5个真正能称为导师的人。</li></ol><h2 id="步入职场策略">步入职场策略</h2><ol type="1"><li>利用在读的时间储备早期形式的职场燃料。</li><li>制订求职作战计划。</li><li>积极参与校园招聘。</li><li>高效地进行在线申请。</li><li>用好你的关系。</li><li>与联系人见面之前，做些功课。</li><li>做好心理准备，找到第一份工作难于上青天。</li><li>不断探索。</li></ol><h2 id="初任管理者的建议">初任管理者的建议</h2><ol type="1"><li>时刻注意你的易容、态度和举止。</li><li>简洁地表达你的愿景，并且不停地重复。</li><li>尽快选好团队成员。</li><li>每一个有意义地商业问题最好能在较小的团队中解决。</li><li>表现得像个被人信赖的解答者。</li><li>你并不需要无所不知，而是应该多多找人咨询。</li></ol><h2 id="首席执行官的特质">首席执行官的特质</h2><ol type="1"><li>诚实，与公司的文化契合度。</li><li>智力上的好奇和敏捷。</li><li>有提升业务业绩的经验。</li><li>真实、自我意识以及平衡。</li><li>活力和热情。</li></ol><h2 id="合理规划第三阶段的建议">合理规划第三阶段的建议</h2><ol type="1"><li>试验，自愿接受挑战。</li><li>创业，开辟全新疆域。</li><li>管理学习曲线，保持关联性。</li></ol><h2 id="职业生涯与为人父母共存之道">职业生涯与为人父母共存之道</h2><ol type="1"><li>不要让职业生涯和为人父母成为非此即彼的选项。</li><li>找到一个热爱家庭的雇主。</li><li>找到后方的恰当支持。</li><li>设立现实的期望和严格的界限。</li><li>管理你的时间和精力。</li></ol><h2 id="回归正轨之法">回归正轨之法</h2><ol type="1"><li>重新组织你的经验。</li><li>重新包装你的技能。</li><li>重新连接职业生态系统。</li><li>重新建立自信。</li></ol><h2 id="其他">其他</h2><h3 id="面对机制的竞争">面对机制的竞争</h3><p>明智之举是培养情商、创造力、协作能力和建立信任关系的技能。</p><h3 id="在哪里找工作">在哪里找工作</h3><p>像领英这样的在线平台将成为公司寻找人才、个人寻找工作的主要场所。</p><h3 id="将时间投资在哪里">将时间投资在哪里</h3><p>创业和自由职业将在不久的将来蓬勃发展，工作目标也将更多样化。</p><h3 id="怎样保持收入稳定">怎样保持收入稳定</h3><p>退休并不代表就能安享晚年，继续工作才能获得稳定的收入。</p><h3 id="享受工作的快乐">享受工作的快乐</h3><p>想在工作中更快乐，就需要提高幸福感。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;从图书馆借阅了[加]布莱恩·费瑟斯通豪（&lt;em&gt;Brain
Fetherstonhaugh&lt;/em&gt;）所著的《远见》（&lt;em&gt;The Long
View&lt;/em&gt;）一书，记录下核心思想。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Books" scheme="https://heary.cn/tags/Books/"/>
    
  </entry>
  
  <entry>
    <title>DistributedDataParallel(DDP) - PyTorch多进程并行计算</title>
    <link href="https://heary.cn/posts/DistributedDataParallel-DDP-PyTorch%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97/"/>
    <id>https://heary.cn/posts/DistributedDataParallel-DDP-PyTorch%E5%A4%9A%E8%BF%9B%E7%A8%8B%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97/</id>
    <published>2020-12-17T11:59:51.000Z</published>
    <updated>2022-08-07T04:02:09.587Z</updated>
    
    <content type="html"><![CDATA[<p>PyTorch的DistributedDataParallel(DDP)可以实现多进程的并行计算，相较于传统的单进程多线程的DataParallel，DDP支持多节点的分布式计算。即使在单机多卡的场景下，DDP通常性能也更好，因为它不仅规避了Python多线程的全局解释器锁争用（GILcontention）造成的性能开销，而且还不需要在多GPU训练中频繁复制同步模型、分发输入数据和收集模型输出。</p><span id="more"></span><h1id="distributeddataparallelddp---pytorch多进程并行计算">DistributedDataParallel(DDP)- PyTorch多进程并行计算</h1><h2 id="背景">背景</h2><h3 id="python-gil">Python GIL</h3><p>GIL（Global InterpreterLock）指的是全局解释器锁，由CPython解释器引入。因为CPython解释器的内存管理是线程不安全的，所以为了避免多线程同时执行Python字节码造成线程安全问题，就加了这么一个全局的互斥锁。可也正是因为这个全局互斥锁，导致Python的多线程实际上同时只有一个线程在运行，显然无法充分利用多处理器的性能。</p><p>参考官方解释：</p><blockquote><p><a href="https://python.land/python-concurrency/the-python-gil">ThePython GIL</a></p><p>Python has one peculiarity that makes concurrent programming harder.It’s called the <strong>GIL</strong>, short for Global Interpreter Lock.The GIL makes sure there is, at any time, only one thread running.Because only one thread can run at a time, it’s impossible to usemultiple processors with threads. But don’t worry, there’s a way aroundthis.</p><p>The GIL was invented because CPython’s memory management is notthread-safe. With only one thread running at a time, CPython can restassured there will never be race conditions.</p></blockquote><h3id="distributeddataparallel较dataparallel的优势">DistributedDataParallel较DataParallel的优势</h3><p>DistributedDataParallel(DDP)相较于DataParallel(DP)有诸多优势，包括功能上的优势和性能上的优势：</p><p>功能上：</p><ol type="1"><li>DDP的原理是多进程，因此DDP支持多机多卡的分布式计算，而DP是但经常多线程，因此最高只支持单机多卡；</li><li>DDP支持模型并行（modelparallel），可以把一个模型拆成几个阶段来跑，而DP还不支持。</li></ol><p>性能上：</p><ol type="1"><li>正是因为DDP基于多进程（通常推荐1个GPU匹配一个工作进程），所以不像DP那样基于单进程多线程的并行性能受到GIL争用开销的阻碍。</li><li>在单机多卡的情况下，DP需要在训练中频繁在多卡之间复制模型以完成同步，需要分发（scatter）输入和收集（gather）输出，而DDP采用的All-Reduce算法采取聚合通信（collectivecommunication）的方式收集梯度，其性能更好。</li></ol><p>总的来讲，功能上的优势其实也是为了更好利用设备性能。</p><h2 id="原理">原理</h2><p>原理可参阅：</p><blockquote><p><a href="https://pytorch.org/docs/master/notes/ddp.html">DistributedData Parallel - PyTorch master Documentation</a></p></blockquote><p>另有一篇2020年的论文：</p><blockquote><p>Li S, Zhao Y, Varma R, et al. PyTorch distributed: experiences onaccelerating data parallel training[J]. Proceedings of the VLDBEndowment, 2020, 13(12): 3005-3018.</p><p><a href="http://www.vldb.org/pvldb/vol13/p3005-li.pdf">PDF onvldb.org</a></p></blockquote><p>总的架构可以参考：</p><ul><li>Distributed System<ul><li>Node 0<ul><li>Process0 [Global Rank=0, Local Rank=0] -&gt; GPU 0-0</li><li>Process1 [Global Rank=1, Local Rank=1] -&gt; GPU 0-1</li><li>Process2 [Global Rank=2, Local Rank=2] -&gt; GPU 0-2</li><li>Process3 [Global Rank=3, Local Rank=3] -&gt; GPU 0-3</li></ul></li><li>Node 1<ul><li>Process4 [Global Rank=4, Local Rank=0] -&gt; GPU 1-0</li><li>Process5 [Global Rank=5, Local Rank=1] -&gt; GPU 1-1</li><li>Process6 [Global Rank=6, Local Rank=2] -&gt; GPU 1-2</li><li>Process7 [Global Rank=7, Local Rank=3] -&gt; GPU 1-3</li></ul></li></ul></li></ul><p>在这样的架构中，有如下术语和数值：</p><ul><li>N=2 <strong>Nodes</strong></li><li>G=4 <strong>GPUs per node</strong></li><li>W=8 Application processes across all nodes (aka. <strong>WorldSize</strong>)</li><li>L=4 Application processes on each nodes (aka. <strong>LocalSize</strong>)</li></ul><h2 id="使用">使用</h2><p>相较于DataParallel只需要简单地套到原模型上，DistributedDataParallel因为其原理是基于多进程的，因此写起来会稍微显得复杂一点点。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">Distributed Data Parallel (DDP) example</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">Author: HearyShen</span></span><br><span class="line"><span class="string">Date:   2020.12.17</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">from</span> argparse <span class="keyword">import</span> ArgumentParser</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">import</span> torch.utils.data <span class="keyword">as</span> data</span><br><span class="line"><span class="keyword">import</span> torch.nn <span class="keyword">as</span> nn</span><br><span class="line"><span class="keyword">import</span> torch.cuda <span class="keyword">as</span> cuda</span><br><span class="line"><span class="keyword">import</span> torch.distributed <span class="keyword">as</span> dist</span><br><span class="line"><span class="keyword">import</span> torch.multiprocessing <span class="keyword">as</span> mp</span><br><span class="line"><span class="keyword">import</span> torch.optim <span class="keyword">as</span> optim</span><br><span class="line"><span class="keyword">import</span> torchvision.models <span class="keyword">as</span> models</span><br><span class="line"></span><br><span class="line">DIST_DEFAULT_BACKEND = <span class="string">&#x27;nccl&#x27;</span></span><br><span class="line">DIST_DEFAULT_ADDR = <span class="string">&#x27;localhost&#x27;</span></span><br><span class="line">DIST_DEFAULT_PORT = <span class="string">&#x27;12344&#x27;</span></span><br><span class="line">DIST_DEFAULT_INIT_METHOD = <span class="string">f&#x27;tcp://<span class="subst">&#123;DIST_DEFAULT_ADDR&#125;</span>:<span class="subst">&#123;DIST_DEFAULT_PORT&#125;</span>&#x27;</span></span><br><span class="line">DIST_DEFAULT_WORLD_SIZE = cuda.device_count()</span><br><span class="line"></span><br><span class="line">DEFAULT_BATCH_SIZE = <span class="number">64</span></span><br><span class="line">DEFAULT_NUM_WORKERS_PER_GPU = <span class="number">8</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TinyNet</span>(nn.Module):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="built_in">super</span>().__init__()</span><br><span class="line">        self.mlp = nn.Linear(<span class="number">3</span>, <span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        out = self.mlp(x)</span><br><span class="line">        <span class="keyword">return</span> out</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TinyDataset</span>(data.dataset.Dataset):</span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__getitem__</span>(<span class="params">self, index</span>):</span><br><span class="line">        x = torch.rand([<span class="number">3</span>, <span class="number">224</span>, <span class="number">224</span>])</span><br><span class="line">        y = random.randint(<span class="number">0</span>, <span class="number">999</span>)</span><br><span class="line">        <span class="keyword">return</span> x, y</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__len__</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">10000</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">worker</span>(<span class="params">rank, args</span>):</span><br><span class="line">    model = models.resnet50(pretrained=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">if</span> args.distributed:</span><br><span class="line">        <span class="built_in">print</span>(</span><br><span class="line">            <span class="string">f&quot;[<span class="subst">&#123;os.getpid()&#125;</span>] Initializing <span class="subst">&#123;rank&#125;</span>/<span class="subst">&#123;DIST_DEFAULT_WORLD_SIZE&#125;</span> at <span class="subst">&#123;DIST_DEFAULT_INIT_METHOD&#125;</span>&quot;</span></span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="comment"># initialize with TCP in this example</span></span><br><span class="line">        dist.init_process_group(backend=DIST_DEFAULT_BACKEND,</span><br><span class="line">                                init_method=DIST_DEFAULT_INIT_METHOD,</span><br><span class="line">                                world_size=DIST_DEFAULT_WORLD_SIZE,</span><br><span class="line">                                rank=rank)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># # Another way to initialize with environment variables</span></span><br><span class="line">        <span class="comment"># os.environ[&quot;MASTER_PORT&quot;] = DIST_DEFAULT_PORT</span></span><br><span class="line">        <span class="comment"># os.environ[&quot;MASTER_ADDR&quot;] = DIST_DEFAULT_ADDR</span></span><br><span class="line">        <span class="comment"># os.environ[&quot;WORLD_SIZE&quot;] = str(DIST_DEFAULT_WORLD_SIZE)</span></span><br><span class="line">        <span class="comment"># os.environ[&quot;RANK&quot;] = str(rank)</span></span><br><span class="line">        <span class="comment"># dist.init_process_group(backend=DIST_DEFAULT_BACKEND)</span></span><br><span class="line"></span><br><span class="line">        <span class="built_in">print</span>(</span><br><span class="line">            <span class="string">f&quot;[<span class="subst">&#123;os.getpid()&#125;</span>] Computing <span class="subst">&#123;rank&#125;</span>/<span class="subst">&#123;DIST_DEFAULT_WORLD_SIZE&#125;</span> at <span class="subst">&#123;DIST_DEFAULT_INIT_METHOD&#125;</span>&quot;</span></span><br><span class="line">        )</span><br><span class="line">        <span class="comment"># ensuring that each process exclusively works on a single GPU</span></span><br><span class="line">        torch.cuda.set_device(rank)</span><br><span class="line">        model.cuda(rank)</span><br><span class="line">        <span class="comment"># When using a single GPU per process and per</span></span><br><span class="line">        <span class="comment"># DistributedDataParallel, we need to divide the batch size</span></span><br><span class="line">        <span class="comment"># ourselves based on the total number of GPUs we have</span></span><br><span class="line">        model = nn.parallel.DistributedDataParallel(model, device_ids=[rank])</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        model = nn.DataParallel(model).cuda()</span><br><span class="line"></span><br><span class="line">    loss_func = nn.CrossEntropyLoss()</span><br><span class="line">    optimizer = optim.SGD(model.parameters(), lr=<span class="number">0.001</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># dataset</span></span><br><span class="line">    dataset = TinyDataset()</span><br><span class="line">    dist_sampler = data.distributed.DistributedSampler(</span><br><span class="line">        dataset) <span class="keyword">if</span> args.distributed <span class="keyword">else</span> <span class="literal">None</span></span><br><span class="line">    dataloader = data.dataloader.DataLoader(</span><br><span class="line">        dataset,</span><br><span class="line">        batch_size=DEFAULT_BATCH_SIZE // DIST_DEFAULT_WORLD_SIZE <span class="keyword">if</span> args.distributed <span class="keyword">else</span> DEFAULT_BATCH_SIZE,</span><br><span class="line">        shuffle=(dist_sampler <span class="keyword">is</span> <span class="literal">None</span>),</span><br><span class="line">        num_workers=DEFAULT_NUM_WORKERS_PER_GPU <span class="keyword">if</span> args.distributed <span class="keyword">else</span> DEFAULT_NUM_WORKERS_PER_GPU * DIST_DEFAULT_WORLD_SIZE,</span><br><span class="line">        sampler=dist_sampler)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># train</span></span><br><span class="line">    model = model.train()</span><br><span class="line">    <span class="keyword">for</span> epoch <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">2</span>):</span><br><span class="line">        <span class="keyword">if</span> args.distributed:</span><br><span class="line">            dist_sampler.set_epoch(epoch)</span><br><span class="line">        <span class="keyword">for</span> i, (x, label) <span class="keyword">in</span> <span class="built_in">enumerate</span>(dataloader):</span><br><span class="line">            y = model(x)</span><br><span class="line">            loss = loss_func(y, label.to(y.device))</span><br><span class="line"></span><br><span class="line">            optimizer.zero_grad()</span><br><span class="line">            loss.backward()</span><br><span class="line">            optimizer.step()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> args.distributed:</span><br><span class="line">            <span class="built_in">print</span>(</span><br><span class="line">                <span class="string">f&quot;[<span class="subst">&#123;os.getpid()&#125;</span>] Epoch-<span class="subst">&#123;epoch&#125;</span> ended <span class="subst">&#123;rank&#125;</span>/<span class="subst">&#123;DIST_DEFAULT_WORLD_SIZE&#125;</span> at <span class="subst">&#123;DIST_DEFAULT_INIT_METHOD&#125;</span> on <span class="subst">&#123;y.device&#125;</span>&quot;</span></span><br><span class="line">            )</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;[<span class="subst">&#123;os.getpid()&#125;</span>] Epoch-<span class="subst">&#123;epoch&#125;</span> ended on <span class="subst">&#123;y.device&#125;</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> args.distributed:</span><br><span class="line">        <span class="built_in">print</span>(</span><br><span class="line">            <span class="string">f&quot;[<span class="subst">&#123;os.getpid()&#125;</span>] Finishing <span class="subst">&#123;rank&#125;</span>/<span class="subst">&#123;DIST_DEFAULT_WORLD_SIZE&#125;</span> at <span class="subst">&#123;DIST_DEFAULT_INIT_METHOD&#125;</span> on <span class="subst">&#123;y.device&#125;</span>&quot;</span></span><br><span class="line">        )</span><br><span class="line">        dist.destroy_process_group()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">launch</span>(<span class="params">args</span>):</span><br><span class="line">    tic = time.time()</span><br><span class="line">    <span class="keyword">if</span> args.distributed:</span><br><span class="line">        mp.spawn(worker,</span><br><span class="line">                 args=(args, ),</span><br><span class="line">                 nprocs=DIST_DEFAULT_WORLD_SIZE,</span><br><span class="line">                 join=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        worker(<span class="literal">None</span>, args)</span><br><span class="line">    toc = time.time()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Finished in <span class="subst">&#123;toc-tic:<span class="number">.2</span>f&#125;</span>s&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    parser = ArgumentParser()</span><br><span class="line">    parser.add_argument(<span class="string">&quot;-d&quot;</span>, <span class="string">&quot;--distributed&quot;</span>, action=<span class="string">&quot;store_true&quot;</span>)</span><br><span class="line">    args = parser.parse_args()</span><br><span class="line"></span><br><span class="line">    launch(args)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="spawn创建多进程">spawn创建多进程</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">launch</span>(<span class="params">args</span>):</span><br><span class="line">    tic = time.time()</span><br><span class="line">    <span class="keyword">if</span> args.distributed:</span><br><span class="line">        mp.spawn(worker,</span><br><span class="line">                 args=(args, ),</span><br><span class="line">                 nprocs=DIST_DEFAULT_WORLD_SIZE,</span><br><span class="line">                 join=<span class="literal">True</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        worker(<span class="literal">None</span>, args)</span><br><span class="line">    toc = time.time()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Finished in <span class="subst">&#123;toc-tic:<span class="number">.2</span>f&#125;</span>s&quot;</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>launch</code>使用<code>multiprocessing.spawn</code>来快速创建<code>nprocs</code>个新进程，每个进程都执行<code>worker</code>函数，并传入<code>args</code>作为函数参数。</p><p>需要注意的是，<code>spawn</code>默认会为函数传入一个<code>i</code>，且<code>i</code>在<code>[0, nprocs)</code>之间。即，<code>worker</code>函数收到的参数列表是<code>(i, args, )</code>。</p><h3 id="worker多进程并行">worker多进程并行</h3><p>每一个worker进程做的工作分以下几个阶段：</p><ol type="1"><li>初始化进程组：并行启动的多进程相互之间得形成一个进程组，即，要知道在哪会合（rendezvous）。DDP的机制会把以rank=0进程上的模型为准，自动保证其他进程上的模型与之保持一致性。</li><li>DDP包装模型：创建模型，用DistributedDataParallel包装，移动到该进程对应的GPU设备上。</li><li>准备数据：为DDP建立<code>DistributedSampler</code>，以便DataLoader将数据加载给每个GPU上训练的模型。</li><li>进行训练：每个进程根据DataLoader分担的batch_size来并行处理训练数据。</li><li>销毁进程组：进程执行完成后，销毁启动的进程组。</li></ol><h4 id="init_process_group">init_process_group</h4><blockquote><p>参阅torch.distributed的官方文档：</p><p><ahref="https://pytorch.org/docs/stable/distributed.html#torch.distributed.init_process_group">Distributedcommunication package - torch.distributed</a></p></blockquote><p>初始化函数原型：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">torch.distributed.init_process_group(backend, init_method=<span class="literal">None</span>, timeout=datetime.timedelta(<span class="number">0</span>, <span class="number">1800</span>), world_size=-<span class="number">1</span>, rank=-<span class="number">1</span>, store=<span class="literal">None</span>, group_name=<span class="string">&#x27;&#x27;</span>)</span><br></pre></td></tr></table></figure><p>其中，进程组后端是负责提供进程组聚合通信（collectivecommunication）的库。PyTorch支持Gloo, MPI和NCCL三种，推荐的做法是，</p><ul><li>在分布式GPU训练时使用NCCL；</li><li>在分布式CPU训练时使用Gloo。</li></ul><p>参考资料：</p><blockquote><p><a href="https://github.com/facebookincubator/gloo">Gloo</a></p><p>Gloo is a collective communications library. It comes with a numberof collective algorithms useful for machine learning applications. Theseinclude a barrier, broadcast, and allreduce.</p></blockquote><blockquote><p><a href="https://developer.nvidia.com/nccl">NVIDIA NCCL</a></p><p>The NVIDIA Collective Communication Library (NCCL) implementsmulti-GPU and multi-node communication primitives optimized for NVIDIAGPUs and Networking. NCCL provides routines such as all-gather,all-reduce, broadcast, reduce, reduce-scatter as well as point-to-pointsend and receive that are optimized to achieve high bandwidth and lowlatency over PCIe and NVLink high-speed interconnects within a node andover NVIDIA Mellanox Network across nodes.</p></blockquote><p>初始化可以选择通过<code>init_method</code>填写通信地址和端口，也可以通过<code>store</code>来传入一个进程间共同访问的键值对容器。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># initialize with TCP in this example</span></span><br><span class="line">dist.init_process_group(backend=DIST_DEFAULT_BACKEND,</span><br><span class="line">                        init_method=DIST_DEFAULT_INIT_METHOD,</span><br><span class="line">                        world_size=DIST_DEFAULT_WORLD_SIZE,</span><br><span class="line">                        rank=rank)</span><br><span class="line"></span><br><span class="line"><span class="comment"># # Another way to initialize with environment variables</span></span><br><span class="line"><span class="comment"># os.environ[&quot;MASTER_PORT&quot;] = DIST_DEFAULT_PORT</span></span><br><span class="line"><span class="comment"># os.environ[&quot;MASTER_ADDR&quot;] = DIST_DEFAULT_ADDR</span></span><br><span class="line"><span class="comment"># os.environ[&quot;WORLD_SIZE&quot;] = str(DIST_DEFAULT_WORLD_SIZE)</span></span><br><span class="line"><span class="comment"># os.environ[&quot;RANK&quot;] = str(rank)</span></span><br><span class="line"><span class="comment"># dist.init_process_group(backend=DIST_DEFAULT_BACKEND)</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>例子中演示了基于<code>init_method</code>的通信方式，具体采用TCP连接的方式来初始化，也可通过环境变量的方式（见注释掉的代码）。另外，还可以使用共享的文件系统来实现初始化，可参阅<code>torch.distributed</code>的官方文档。我觉得TCP连接足够简单且兼容性好，这里就以TCP的方式为主了。</p><h4 id="distributeddataparallel">DistributedDataParallel</h4><blockquote><p>参阅torch.nn.parallel.DistributedDataParallel的官方文档：</p><p><ahref="https://pytorch.org/docs/stable/generated/torch.nn.parallel.DistributedDataParallel.html">torch.nn.parallel.DistributedDataParallel</a></p></blockquote><p>首先，需要注意的是，在建立DDP之前，在N个GPU的机器上，spawn出N个进程的时候，需要确保每个进程负责其对应的那一个GPU，不要互相打架。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ensuring that each process exclusively works on a single GPU</span></span><br><span class="line">torch.cuda.set_device(rank)</span><br><span class="line">model.cuda(rank)</span><br><span class="line"><span class="comment"># When using a single GPU per process and per</span></span><br><span class="line"><span class="comment"># DistributedDataParallel, we need to divide the batch size</span></span><br><span class="line"><span class="comment"># ourselves based on the total number of GPUs we have</span></span><br><span class="line">model = nn.parallel.DistributedDataParallel(model, device_ids=[rank])</span><br></pre></td></tr></table></figure><p>用<code>torch.nn.parallel.DistributedDataParallel</code>类包装原模型，并将该进程的模型映射到对应的GPU设备上。</p><h4 id="distributedsampler">DistributedSampler</h4><blockquote><p>参阅torch.utils.data.distributed.DistributedSampler的官方文档：</p><p><ahref="https://pytorch.org/docs/stable/data.html#torch.utils.data.distributed.DistributedSampler">torch.utils.data.distributed.DistributedSampler</a></p></blockquote><p>其实就是在多进程的情况下，每个进程训练数据集的一个子集，不应互相重复，通过DistributedSampler来实现分布式的采样原数据集中的一个子集：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># dataset</span></span><br><span class="line">dataset = TinyDataset()</span><br><span class="line">dist_sampler = data.distributed.DistributedSampler(</span><br><span class="line">    dataset) <span class="keyword">if</span> args.distributed <span class="keyword">else</span> <span class="literal">None</span></span><br><span class="line">dataloader = data.dataloader.DataLoader(</span><br><span class="line">    dataset,</span><br><span class="line">    batch_size=DEFAULT_BATCH_SIZE // DIST_DEFAULT_WORLD_SIZE <span class="keyword">if</span> args.distributed <span class="keyword">else</span> DEFAULT_BATCH_SIZE,</span><br><span class="line">    shuffle=(dist_sampler <span class="keyword">is</span> <span class="literal">None</span>),</span><br><span class="line">    num_workers=DEFAULT_NUM_WORKERS_PER_GPU <span class="keyword">if</span> args.distributed <span class="keyword">else</span> DEFAULT_NUM_WORKERS_PER_GPU * DIST_DEFAULT_WORLD_SIZE,</span><br><span class="line">    sampler=dist_sampler)</span><br></pre></td></tr></table></figure><p>需要注意的是，多epoch场景下，需要在每个epoch开始前用<code>sampler.set_epoch(epoch)</code>设置当前的epoch，以免每次epoch训练的数据顺序都是相同的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;PyTorch的DistributedDataParallel(DDP)可以实现多进程的并行计算，相较于传统的单进程多线程的DataParallel，DDP支持多节点的分布式计算。即使在单机多卡的场景下，DDP通常性能也更好，因为它不仅规避了Python多线程的全局解释器锁争用（GIL
contention）造成的性能开销，而且还不需要在多GPU训练中频繁复制同步模型、分发输入数据和收集模型输出。&lt;/p&gt;</summary>
    
    
    
    
    <category term="PyTorch" scheme="https://heary.cn/tags/PyTorch/"/>
    
    <category term="Parallel Computing" scheme="https://heary.cn/tags/Parallel-Computing/"/>
    
    <category term="Multiprocessing" scheme="https://heary.cn/tags/Multiprocessing/"/>
    
    <category term="CUDA" scheme="https://heary.cn/tags/CUDA/"/>
    
  </entry>
  
  <entry>
    <title>fasterrcnn_resnet50_fpn - 从torchvision源码理解Faster R-CNN原理</title>
    <link href="https://heary.cn/posts/fasterrcnn-resnet50-fpn-%E4%BB%8Etorchvision%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Faster-R-CNN%E5%8E%9F%E7%90%86/"/>
    <id>https://heary.cn/posts/fasterrcnn-resnet50-fpn-%E4%BB%8Etorchvision%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Faster-R-CNN%E5%8E%9F%E7%90%86/</id>
    <published>2020-11-25T11:41:12.000Z</published>
    <updated>2022-08-07T04:02:09.859Z</updated>
    
    <content type="html"><![CDATA[<p>PyTorch的torchvision包中实现了FasterR-CNN。本文结合对torchvision源码的阅读，深入理解FasterR-CNN的内部原理，以便进行开发利用。</p><span id="more"></span><h1id="fasterrcnn_resnet50_fpn---从torchvision源码理解faster-r-cnn原理">fasterrcnn_resnet50_fpn- 从torchvision源码理解Faster R-CNN原理</h1><h2 id="接口层">1 接口层</h2><h3 id="外部调用">外部调用</h3><p>根据PyTorch的torchvision库的文档，FasterR-CNN模型对象可以直接通过<code>fasterrcnn_resnet50_fpn</code>函数来构造。</p><p>具体地，官方文档给出了训练时和预测时的调用样例：</p><p><ahref="https://pytorch.org/docs/stable/torchvision/models.html?highlight=faster%20rcnn#torchvision.models.detection.fasterrcnn_resnet50_fpn">torchvision.models.detection.fasterrcnn_resnet50_fpn</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="meta">&gt;&gt;&gt; </span>model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=<span class="literal">True</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="comment"># For training</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>images, boxes = torch.rand(<span class="number">4</span>, <span class="number">3</span>, <span class="number">600</span>, <span class="number">1200</span>), torch.rand(<span class="number">4</span>, <span class="number">11</span>, <span class="number">4</span>)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>labels = torch.randint(<span class="number">1</span>, <span class="number">91</span>, (<span class="number">4</span>, <span class="number">11</span>))</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>images = <span class="built_in">list</span>(image <span class="keyword">for</span> image <span class="keyword">in</span> images)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>targets = []</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(images)):</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>    d = &#123;&#125;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>    d[<span class="string">&#x27;boxes&#x27;</span>] = boxes[i]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>    d[<span class="string">&#x27;labels&#x27;</span>] = labels[i]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>    targets.append(d)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>output = model(images, targets)</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="comment"># For inference</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>model.<span class="built_in">eval</span>()</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>x = [torch.rand(<span class="number">3</span>, <span class="number">300</span>, <span class="number">400</span>), torch.rand(<span class="number">3</span>, <span class="number">500</span>, <span class="number">400</span>)]</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>predictions = model(x)</span><br><span class="line">&gt;&gt;&gt;</span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span><span class="comment"># optionally, if you want to export the model to ONNX:</span></span><br><span class="line"><span class="meta">&gt;&gt;&gt; </span>torch.onnx.export(model, x, <span class="string">&quot;faster_rcnn.onnx&quot;</span>, opset_version = <span class="number">11</span>)</span><br></pre></td></tr></table></figure><ul><li>其中，不论是训练，还是预测，模型的输入都是list容器，表示的是若干个图片（与目标框和类别）。</li></ul><h3 id="fasterrcnn_resnet50_fpn">fasterrcnn_resnet50_fpn</h3><p><code>fasterrcnn_resnet50_fpn</code>函数在<code>torchvision.models.detection.faster_rcnn</code>包中实现，文档见<ahref="https://pytorch.org/docs/stable/torchvision/models.html#torchvision.models.detection.fasterrcnn_resnet50_fpn">torchvision.models.detection.fasterrcnn_resnet50_fpn</a>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">fasterrcnn_resnet50_fpn</span>(<span class="params">pretrained=<span class="literal">False</span>, progress=<span class="literal">True</span>,</span></span><br><span class="line"><span class="params">                            num_classes=<span class="number">91</span>, pretrained_backbone=<span class="literal">True</span>, trainable_backbone_layers=<span class="number">3</span>, **kwargs</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Constructs a Faster R-CNN model with a ResNet-50-FPN backbone.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The input to the model is expected to be a list of tensors, each of shape ``[C, H, W]``, one for each</span></span><br><span class="line"><span class="string">    image, and should be in ``0-1`` range. Different images can have different sizes.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The behavior of the model changes depending if it is in training or evaluation mode.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    During training, the model expects both the input tensors, as well as a targets (list of dictionary),</span></span><br><span class="line"><span class="string">    containing:</span></span><br><span class="line"><span class="string">        - boxes (``FloatTensor[N, 4]``): the ground-truth boxes in ``[x1, y1, x2, y2]`` format, with values of ``x``</span></span><br><span class="line"><span class="string">          between ``0`` and ``W`` and values of ``y`` between ``0`` and ``H``</span></span><br><span class="line"><span class="string">        - labels (``Int64Tensor[N]``): the class label for each ground-truth box</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The model returns a ``Dict[Tensor]`` during training, containing the classification and regression</span></span><br><span class="line"><span class="string">    losses for both the RPN and the R-CNN.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    During inference, the model requires only the input tensors, and returns the post-processed</span></span><br><span class="line"><span class="string">    predictions as a ``List[Dict[Tensor]]``, one for each input image. The fields of the ``Dict`` are as</span></span><br><span class="line"><span class="string">    follows:</span></span><br><span class="line"><span class="string">        - boxes (``FloatTensor[N, 4]``): the predicted boxes in ``[x1, y1, x2, y2]`` format, with values of ``x``</span></span><br><span class="line"><span class="string">          between ``0`` and ``W`` and values of ``y`` between ``0`` and ``H``</span></span><br><span class="line"><span class="string">        - labels (``Int64Tensor[N]``): the predicted labels for each image</span></span><br><span class="line"><span class="string">        - scores (``Tensor[N]``): the scores or each prediction</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Faster R-CNN is exportable to ONNX for a fixed batch size with inputs images of fixed size.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Example::</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # For training</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; images, boxes = torch.rand(4, 3, 600, 1200), torch.rand(4, 11, 4)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; labels = torch.randint(1, 91, (4, 11))</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; images = list(image for image in images)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; targets = []</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; for i in range(len(images)):</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;     d = &#123;&#125;</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;     d[&#x27;boxes&#x27;] = boxes[i]</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;     d[&#x27;labels&#x27;] = labels[i]</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;     targets.append(d)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; output = model(images, targets)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # For inference</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; model.eval()</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x = [torch.rand(3, 300, 400), torch.rand(3, 500, 400)]</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; predictions = model(x)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # optionally, if you want to export the model to ONNX:</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; torch.onnx.export(model, x, &quot;faster_rcnn.onnx&quot;, opset_version = 11)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        pretrained (bool): If True, returns a model pre-trained on COCO train2017</span></span><br><span class="line"><span class="string">        progress (bool): If True, displays a progress bar of the download to stderr</span></span><br><span class="line"><span class="string">        pretrained_backbone (bool): If True, returns a model with backbone pre-trained on Imagenet</span></span><br><span class="line"><span class="string">        num_classes (int): number of output classes of the model (including the background)</span></span><br><span class="line"><span class="string">        trainable_backbone_layers (int): number of trainable (not frozen) resnet layers starting from final block.</span></span><br><span class="line"><span class="string">            Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable.</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">assert</span> trainable_backbone_layers &lt;= <span class="number">5</span> <span class="keyword">and</span> trainable_backbone_layers &gt;= <span class="number">0</span></span><br><span class="line">    <span class="comment"># dont freeze any layers if pretrained model or backbone is not used</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> (pretrained <span class="keyword">or</span> pretrained_backbone):</span><br><span class="line">        trainable_backbone_layers = <span class="number">5</span></span><br><span class="line">    <span class="keyword">if</span> pretrained:</span><br><span class="line">        <span class="comment"># no need to download the backbone if pretrained is set</span></span><br><span class="line">        pretrained_backbone = <span class="literal">False</span></span><br><span class="line">    backbone = resnet_fpn_backbone(<span class="string">&#x27;resnet50&#x27;</span>, pretrained_backbone, trainable_layers=trainable_backbone_layers)</span><br><span class="line">    model = FasterRCNN(backbone, num_classes, **kwargs)</span><br><span class="line">    <span class="keyword">if</span> pretrained:</span><br><span class="line">        state_dict = load_state_dict_from_url(model_urls[<span class="string">&#x27;fasterrcnn_resnet50_fpn_coco&#x27;</span>],</span><br><span class="line">                                              progress=progress)</span><br><span class="line">        model.load_state_dict(state_dict)</span><br><span class="line">    <span class="keyword">return</span> model</span><br></pre></td></tr></table></figure><p>该函数的实现中，首先进行参数检查：</p><ol type="1"><li>检查<code>trainable_backbone_layers</code>参数，必须在0~5之间，表示从最后一层开始计数，有几层在训练中是可优化的；</li><li>检查<code>pretrained</code>和<code>pretrained_backbone</code>参数，如果整个模型都设为预训练的，那就当然没必要再单独下载预训练的<code>backbone</code>了，把整个FasterR-CNN模型都载入预训练参数即可。</li></ol><p>FasterR-CNN模型是<code>FasterRCNN</code>类的实例。实例化时，传入指定的<code>backbone</code>作为<code>FasterRCNN</code>的backbone。</p><h3 id="resnet_fpn_backbone">resnet_fpn_backbone</h3><p>backbone通过对外开放的<code>resnet_fpn_backbone</code>函数来构造。</p><p><code>resnet_fpn_backbone</code>函数在<code>torchvision.models.detection.backbone_utils</code>包中实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">def</span> <span class="title function_">resnet_fpn_backbone</span>(<span class="params"></span></span><br><span class="line"><span class="params">    backbone_name,</span></span><br><span class="line"><span class="params">    pretrained,</span></span><br><span class="line"><span class="params">    norm_layer=misc_nn_ops.FrozenBatchNorm2d,</span></span><br><span class="line"><span class="params">    trainable_layers=<span class="number">3</span>,</span></span><br><span class="line"><span class="params">    returned_layers=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    extra_blocks=<span class="literal">None</span></span></span><br><span class="line"><span class="params"></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Constructs a specified ResNet backbone with FPN on top. Freezes the specified number of layers in the backbone.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Examples::</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; from torchvision.models.detection.backbone_utils import resnet_fpn_backbone</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; backbone = resnet_fpn_backbone(&#x27;resnet50&#x27;, pretrained=True, trainable_layers=3)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # get some dummy image</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x = torch.rand(1,3,64,64)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # compute the output</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; output = backbone(x)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; print([(k, v.shape) for k, v in output.items()])</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # returns</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;   [(&#x27;0&#x27;, torch.Size([1, 256, 16, 16])),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;    (&#x27;1&#x27;, torch.Size([1, 256, 8, 8])),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;    (&#x27;2&#x27;, torch.Size([1, 256, 4, 4])),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;    (&#x27;3&#x27;, torch.Size([1, 256, 2, 2])),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;    (&#x27;pool&#x27;, torch.Size([1, 256, 1, 1]))]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        backbone_name (string): resnet architecture. Possible values are &#x27;ResNet&#x27;, &#x27;resnet18&#x27;, &#x27;resnet34&#x27;, &#x27;resnet50&#x27;,</span></span><br><span class="line"><span class="string">             &#x27;resnet101&#x27;, &#x27;resnet152&#x27;, &#x27;resnext50_32x4d&#x27;, &#x27;resnext101_32x8d&#x27;, &#x27;wide_resnet50_2&#x27;, &#x27;wide_resnet101_2&#x27;</span></span><br><span class="line"><span class="string">        norm_layer (torchvision.ops): it is recommended to use the default value. For details visit:</span></span><br><span class="line"><span class="string">            (https://github.com/facebookresearch/maskrcnn-benchmark/issues/267)</span></span><br><span class="line"><span class="string">        pretrained (bool): If True, returns a model with backbone pre-trained on Imagenet</span></span><br><span class="line"><span class="string">        trainable_layers (int): number of trainable (not frozen) resnet layers starting from final block.</span></span><br><span class="line"><span class="string">            Valid values are between 0 and 5, with 5 meaning all backbone layers are trainable.</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    backbone = resnet.__dict__[backbone_name](</span><br><span class="line">        pretrained=pretrained,</span><br><span class="line">        norm_layer=norm_layer)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># select layers that wont be frozen</span></span><br><span class="line">    <span class="keyword">assert</span> trainable_layers &lt;= <span class="number">5</span> <span class="keyword">and</span> trainable_layers &gt;= <span class="number">0</span></span><br><span class="line">    layers_to_train = [<span class="string">&#x27;layer4&#x27;</span>, <span class="string">&#x27;layer3&#x27;</span>, <span class="string">&#x27;layer2&#x27;</span>, <span class="string">&#x27;layer1&#x27;</span>, <span class="string">&#x27;conv1&#x27;</span>][:trainable_layers]</span><br><span class="line">    <span class="comment"># freeze layers only if pretrained backbone is used</span></span><br><span class="line">    <span class="keyword">for</span> name, parameter <span class="keyword">in</span> backbone.named_parameters():</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">all</span>([<span class="keyword">not</span> name.startswith(layer) <span class="keyword">for</span> layer <span class="keyword">in</span> layers_to_train]):</span><br><span class="line">            parameter.requires_grad_(<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> extra_blocks <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        extra_blocks = LastLevelMaxPool()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> returned_layers <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        returned_layers = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>]</span><br><span class="line">    <span class="keyword">assert</span> <span class="built_in">min</span>(returned_layers) &gt; <span class="number">0</span> <span class="keyword">and</span> <span class="built_in">max</span>(returned_layers) &lt; <span class="number">5</span></span><br><span class="line">    return_layers = &#123;<span class="string">f&#x27;layer<span class="subst">&#123;k&#125;</span>&#x27;</span>: <span class="built_in">str</span>(v) <span class="keyword">for</span> v, k <span class="keyword">in</span> <span class="built_in">enumerate</span>(returned_layers)&#125;</span><br><span class="line"></span><br><span class="line">    in_channels_stage2 = backbone.inplanes // <span class="number">8</span></span><br><span class="line">    in_channels_list = [in_channels_stage2 * <span class="number">2</span> ** (i - <span class="number">1</span>) <span class="keyword">for</span> i <span class="keyword">in</span> returned_layers]</span><br><span class="line">    out_channels = <span class="number">256</span></span><br><span class="line">    <span class="keyword">return</span> BackboneWithFPN(backbone, return_layers, in_channels_list, out_channels, extra_blocks=extra_blocks)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>首先，根据传入参数选出对应的resnet模型进行实例化。</p><p>随后，检查<code>trainable_layers</code>参数的合法值范围，并通过<code>parameter.requires_grad_(False)</code>来freeze除此以外的其他层。</p><p>默认未定义<code>extra_blocks</code>的时候，会在featuremap结尾添加一个maxpool2d层，该<code>LastLevelMaxPool</code>类实现并不复杂：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># defined in torchvision.ops.feature_pyramid_network</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LastLevelMaxPool</span>(<span class="title class_ inherited__">ExtraFPNBlock</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Applies a max_pool2d on top of the last feature map</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        x: <span class="type">List</span>[Tensor],</span></span><br><span class="line"><span class="params">        y: <span class="type">List</span>[Tensor],</span></span><br><span class="line"><span class="params">        names: <span class="type">List</span>[<span class="built_in">str</span>],</span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[<span class="built_in">str</span>]]:</span><br><span class="line">        names.append(<span class="string">&quot;pool&quot;</span>)</span><br><span class="line">        x.append(F.max_pool2d(x[-<span class="number">1</span>], <span class="number">1</span>, <span class="number">2</span>, <span class="number">0</span>))</span><br><span class="line">        <span class="keyword">return</span> x, names</span><br></pre></td></tr></table></figure><p>根据官方文档<ahref="https://pytorch.org/docs/stable/nn.functional.html?highlight=max_pool2d#torch.nn.functional.max_pool2d">torch.nn.functional.max_pool2d</a>可进一步查阅<ahref="https://pytorch.org/docs/stable/generated/torch.nn.MaxPool2d.html#torch.nn.MaxPool2d">torch.nn.MaxPool2d</a>，实际上<code>F.max_pool2d(x[-1], 1, 2, 0)</code>表示：</p><ol type="1"><li>输入input为<code>x[-1]</code>；</li><li>池化窗口大小kernel_size为1；</li><li>步长stride为2；</li><li>边界填充padding为0。</li></ol><p>关于卷积类的操作可以结合可视化理解：</p><blockquote><p><ahref="https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md">Convolutionarithmetic</a></p></blockquote><p>然后，处理其他传参：</p><ol type="1"><li><code>return_layers</code>，这是一个dict，与传入的backbone相配合，key是backbone的modulename，value是用户定义的返回名；</li><li><code>in_channels_list</code>，这是一个list，与传入的backbone和<code>return_layers</code>相配合，是backbone返回的每一层featuremap的通道数；</li><li><code>out_channels</code>，一个整数，FPN中的通道数。</li></ol><h2 id="实现层">2 实现层</h2><p>我们以一个例子贯穿始终：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> torch</span><br><span class="line"><span class="keyword">import</span> torchvision</span><br><span class="line"></span><br><span class="line">model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=<span class="literal">True</span>)</span><br><span class="line">model.<span class="built_in">eval</span>()</span><br><span class="line">x = [torch.rand(<span class="number">3</span>, <span class="number">300</span>, <span class="number">400</span>), torch.rand(<span class="number">3</span>, <span class="number">500</span>, <span class="number">400</span>)]<span class="comment"># 模拟输入两张尺寸不同的图片</span></span><br><span class="line">predictions = model(x)</span><br></pre></td></tr></table></figure><p>我们使用预训练模型，并模拟输入两张图片。均为3通道，一张<spanclass="math inline">\(300 \times 400\)</span>的<spanclass="math inline">\(H \times W\)</span>分辨率，一张<spanclass="math inline">\(500 \times 400\)</span>。</p><h3 id="fasterrcnn">FasterRCNN</h3><p><code>FasterRCNN</code>类在<code>torchvision.models.detection.faster_rcnn.py</code>中实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FasterRCNN</span>(<span class="title class_ inherited__">GeneralizedRCNN</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Implements Faster R-CNN.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The input to the model is expected to be a list of tensors, each of shape [C, H, W], one for each</span></span><br><span class="line"><span class="string">    image, and should be in 0-1 range. Different images can have different sizes.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The behavior of the model changes depending if it is in training or evaluation mode.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    During training, the model expects both the input tensors, as well as a targets (list of dictionary),</span></span><br><span class="line"><span class="string">    containing:</span></span><br><span class="line"><span class="string">        - boxes (FloatTensor[N, 4]): the ground-truth boxes in [x1, y1, x2, y2] format, with values of x</span></span><br><span class="line"><span class="string">          between 0 and W and values of y between 0 and H</span></span><br><span class="line"><span class="string">        - labels (Int64Tensor[N]): the class label for each ground-truth box</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The model returns a Dict[Tensor] during training, containing the classification and regression</span></span><br><span class="line"><span class="string">    losses for both the RPN and the R-CNN.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    During inference, the model requires only the input tensors, and returns the post-processed</span></span><br><span class="line"><span class="string">    predictions as a List[Dict[Tensor]], one for each input image. The fields of the Dict are as</span></span><br><span class="line"><span class="string">    follows:</span></span><br><span class="line"><span class="string">        - boxes (FloatTensor[N, 4]): the predicted boxes in [x1, y1, x2, y2] format, with values of x</span></span><br><span class="line"><span class="string">          between 0 and W and values of y between 0 and H</span></span><br><span class="line"><span class="string">        - labels (Int64Tensor[N]): the predicted labels for each image</span></span><br><span class="line"><span class="string">        - scores (Tensor[N]): the scores or each prediction</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        backbone (nn.Module): the network used to compute the features for the model.</span></span><br><span class="line"><span class="string">            It should contain a out_channels attribute, which indicates the number of output</span></span><br><span class="line"><span class="string">            channels that each feature map has (and it should be the same for all feature maps).</span></span><br><span class="line"><span class="string">            The backbone should return a single Tensor or and OrderedDict[Tensor].</span></span><br><span class="line"><span class="string">        num_classes (int): number of output classes of the model (including the background).</span></span><br><span class="line"><span class="string">            If box_predictor is specified, num_classes should be None.</span></span><br><span class="line"><span class="string">        min_size (int): minimum size of the image to be rescaled before feeding it to the backbone</span></span><br><span class="line"><span class="string">        max_size (int): maximum size of the image to be rescaled before feeding it to the backbone</span></span><br><span class="line"><span class="string">        image_mean (Tuple[float, float, float]): mean values used for input normalization.</span></span><br><span class="line"><span class="string">            They are generally the mean values of the dataset on which the backbone has been trained</span></span><br><span class="line"><span class="string">            on</span></span><br><span class="line"><span class="string">        image_std (Tuple[float, float, float]): std values used for input normalization.</span></span><br><span class="line"><span class="string">            They are generally the std values of the dataset on which the backbone has been trained on</span></span><br><span class="line"><span class="string">        rpn_anchor_generator (AnchorGenerator): module that generates the anchors for a set of feature</span></span><br><span class="line"><span class="string">            maps.</span></span><br><span class="line"><span class="string">        rpn_head (nn.Module): module that computes the objectness and regression deltas from the RPN</span></span><br><span class="line"><span class="string">        rpn_pre_nms_top_n_train (int): number of proposals to keep before applying NMS during training</span></span><br><span class="line"><span class="string">        rpn_pre_nms_top_n_test (int): number of proposals to keep before applying NMS during testing</span></span><br><span class="line"><span class="string">        rpn_post_nms_top_n_train (int): number of proposals to keep after applying NMS during training</span></span><br><span class="line"><span class="string">        rpn_post_nms_top_n_test (int): number of proposals to keep after applying NMS during testing</span></span><br><span class="line"><span class="string">        rpn_nms_thresh (float): NMS threshold used for postprocessing the RPN proposals</span></span><br><span class="line"><span class="string">        rpn_fg_iou_thresh (float): minimum IoU between the anchor and the GT box so that they can be</span></span><br><span class="line"><span class="string">            considered as positive during training of the RPN.</span></span><br><span class="line"><span class="string">        rpn_bg_iou_thresh (float): maximum IoU between the anchor and the GT box so that they can be</span></span><br><span class="line"><span class="string">            considered as negative during training of the RPN.</span></span><br><span class="line"><span class="string">        rpn_batch_size_per_image (int): number of anchors that are sampled during training of the RPN</span></span><br><span class="line"><span class="string">            for computing the loss</span></span><br><span class="line"><span class="string">        rpn_positive_fraction (float): proportion of positive anchors in a mini-batch during training</span></span><br><span class="line"><span class="string">            of the RPN</span></span><br><span class="line"><span class="string">        box_roi_pool (MultiScaleRoIAlign): the module which crops and resizes the feature maps in</span></span><br><span class="line"><span class="string">            the locations indicated by the bounding boxes</span></span><br><span class="line"><span class="string">        box_head (nn.Module): module that takes the cropped feature maps as input</span></span><br><span class="line"><span class="string">        box_predictor (nn.Module): module that takes the output of box_head and returns the</span></span><br><span class="line"><span class="string">            classification logits and box regression deltas.</span></span><br><span class="line"><span class="string">        box_score_thresh (float): during inference, only return proposals with a classification score</span></span><br><span class="line"><span class="string">            greater than box_score_thresh</span></span><br><span class="line"><span class="string">        box_nms_thresh (float): NMS threshold for the prediction head. Used during inference</span></span><br><span class="line"><span class="string">        box_detections_per_img (int): maximum number of detections per image, for all classes.</span></span><br><span class="line"><span class="string">        box_fg_iou_thresh (float): minimum IoU between the proposals and the GT box so that they can be</span></span><br><span class="line"><span class="string">            considered as positive during training of the classification head</span></span><br><span class="line"><span class="string">        box_bg_iou_thresh (float): maximum IoU between the proposals and the GT box so that they can be</span></span><br><span class="line"><span class="string">            considered as negative during training of the classification head</span></span><br><span class="line"><span class="string">        box_batch_size_per_image (int): number of proposals that are sampled during training of the</span></span><br><span class="line"><span class="string">            classification head</span></span><br><span class="line"><span class="string">        box_positive_fraction (float): proportion of positive proposals in a mini-batch during training</span></span><br><span class="line"><span class="string">            of the classification head</span></span><br><span class="line"><span class="string">        bbox_reg_weights (Tuple[float, float, float, float]): weights for the encoding/decoding of the</span></span><br><span class="line"><span class="string">            bounding boxes</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Example::</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; import torch</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; import torchvision</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; from torchvision.models.detection import FasterRCNN</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; from torchvision.models.detection.rpn import AnchorGenerator</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # load a pre-trained model for classification and return</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # only the features</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; backbone = torchvision.models.mobilenet_v2(pretrained=True).features</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # FasterRCNN needs to know the number of</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # output channels in a backbone. For mobilenet_v2, it&#x27;s 1280</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # so we need to add it here</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; backbone.out_channels = 1280</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # let&#x27;s make the RPN generate 5 x 3 anchors per spatial</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # location, with 5 different sizes and 3 different aspect</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # ratios. We have a Tuple[Tuple[int]] because each feature</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # map could potentially have different sizes and</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # aspect ratios</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; anchor_generator = AnchorGenerator(sizes=((32, 64, 128, 256, 512),),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;                                    aspect_ratios=((0.5, 1.0, 2.0),))</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # let&#x27;s define what are the feature maps that we will</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # use to perform the region of interest cropping, as well as</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # the size of the crop after rescaling.</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # if your backbone returns a Tensor, featmap_names is expected to</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # be [&#x27;0&#x27;]. More generally, the backbone should return an</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # OrderedDict[Tensor], and in featmap_names you can choose which</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # feature maps to use.</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; roi_pooler = torchvision.ops.MultiScaleRoIAlign(featmap_names=[&#x27;0&#x27;],</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;                                                 output_size=7,</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;                                                 sampling_ratio=2)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # put the pieces together inside a FasterRCNN model</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; model = FasterRCNN(backbone,</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;                    num_classes=2,</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;                    rpn_anchor_generator=anchor_generator,</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;                    box_roi_pool=roi_pooler)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; model.eval()</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x = [torch.rand(3, 300, 400), torch.rand(3, 500, 400)]</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; predictions = model(x)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, backbone, num_classes=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 <span class="comment"># transform parameters</span></span></span><br><span class="line"><span class="params">                 min_size=<span class="number">800</span>, max_size=<span class="number">1333</span>,</span></span><br><span class="line"><span class="params">                 image_mean=<span class="literal">None</span>, image_std=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 <span class="comment"># RPN parameters</span></span></span><br><span class="line"><span class="params">                 rpn_anchor_generator=<span class="literal">None</span>, rpn_head=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 rpn_pre_nms_top_n_train=<span class="number">2000</span>, rpn_pre_nms_top_n_test=<span class="number">1000</span>,</span></span><br><span class="line"><span class="params">                 rpn_post_nms_top_n_train=<span class="number">2000</span>, rpn_post_nms_top_n_test=<span class="number">1000</span>,</span></span><br><span class="line"><span class="params">                 rpn_nms_thresh=<span class="number">0.7</span>,</span></span><br><span class="line"><span class="params">                 rpn_fg_iou_thresh=<span class="number">0.7</span>, rpn_bg_iou_thresh=<span class="number">0.3</span>,</span></span><br><span class="line"><span class="params">                 rpn_batch_size_per_image=<span class="number">256</span>, rpn_positive_fraction=<span class="number">0.5</span>,</span></span><br><span class="line"><span class="params">                 <span class="comment"># Box parameters</span></span></span><br><span class="line"><span class="params">                 box_roi_pool=<span class="literal">None</span>, box_head=<span class="literal">None</span>, box_predictor=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 box_score_thresh=<span class="number">0.05</span>, box_nms_thresh=<span class="number">0.5</span>, box_detections_per_img=<span class="number">100</span>,</span></span><br><span class="line"><span class="params">                 box_fg_iou_thresh=<span class="number">0.5</span>, box_bg_iou_thresh=<span class="number">0.5</span>,</span></span><br><span class="line"><span class="params">                 box_batch_size_per_image=<span class="number">512</span>, box_positive_fraction=<span class="number">0.25</span>,</span></span><br><span class="line"><span class="params">                 bbox_reg_weights=<span class="literal">None</span></span>):</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">hasattr</span>(backbone, <span class="string">&quot;out_channels&quot;</span>):</span><br><span class="line">            <span class="keyword">raise</span> ValueError(</span><br><span class="line">                <span class="string">&quot;backbone should contain an attribute out_channels &quot;</span></span><br><span class="line">                <span class="string">&quot;specifying the number of output channels (assumed to be the &quot;</span></span><br><span class="line">                <span class="string">&quot;same for all the levels)&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">isinstance</span>(rpn_anchor_generator, (AnchorGenerator, <span class="built_in">type</span>(<span class="literal">None</span>)))</span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">isinstance</span>(box_roi_pool, (MultiScaleRoIAlign, <span class="built_in">type</span>(<span class="literal">None</span>)))</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> num_classes <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">if</span> box_predictor <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">                <span class="keyword">raise</span> ValueError(<span class="string">&quot;num_classes should be None when box_predictor is specified&quot;</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">if</span> box_predictor <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">                <span class="keyword">raise</span> ValueError(<span class="string">&quot;num_classes should not be None when box_predictor &quot;</span></span><br><span class="line">                                 <span class="string">&quot;is not specified&quot;</span>)</span><br><span class="line"></span><br><span class="line">        out_channels = backbone.out_channels</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> rpn_anchor_generator <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            anchor_sizes = ((<span class="number">32</span>,), (<span class="number">64</span>,), (<span class="number">128</span>,), (<span class="number">256</span>,), (<span class="number">512</span>,))</span><br><span class="line">            aspect_ratios = ((<span class="number">0.5</span>, <span class="number">1.0</span>, <span class="number">2.0</span>),) * <span class="built_in">len</span>(anchor_sizes)</span><br><span class="line">            rpn_anchor_generator = AnchorGenerator(</span><br><span class="line">                anchor_sizes, aspect_ratios</span><br><span class="line">            )</span><br><span class="line">        <span class="keyword">if</span> rpn_head <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            rpn_head = RPNHead(</span><br><span class="line">                out_channels, rpn_anchor_generator.num_anchors_per_location()[<span class="number">0</span>]</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">        rpn_pre_nms_top_n = <span class="built_in">dict</span>(training=rpn_pre_nms_top_n_train, testing=rpn_pre_nms_top_n_test)</span><br><span class="line">        rpn_post_nms_top_n = <span class="built_in">dict</span>(training=rpn_post_nms_top_n_train, testing=rpn_post_nms_top_n_test)</span><br><span class="line"></span><br><span class="line">        rpn = RegionProposalNetwork(</span><br><span class="line">            rpn_anchor_generator, rpn_head,</span><br><span class="line">            rpn_fg_iou_thresh, rpn_bg_iou_thresh,</span><br><span class="line">            rpn_batch_size_per_image, rpn_positive_fraction,</span><br><span class="line">            rpn_pre_nms_top_n, rpn_post_nms_top_n, rpn_nms_thresh)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> box_roi_pool <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            box_roi_pool = MultiScaleRoIAlign(</span><br><span class="line">                featmap_names=[<span class="string">&#x27;0&#x27;</span>, <span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;2&#x27;</span>, <span class="string">&#x27;3&#x27;</span>],</span><br><span class="line">                output_size=<span class="number">7</span>,</span><br><span class="line">                sampling_ratio=<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> box_head <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            resolution = box_roi_pool.output_size[<span class="number">0</span>]</span><br><span class="line">            representation_size = <span class="number">1024</span></span><br><span class="line">            box_head = TwoMLPHead(</span><br><span class="line">                out_channels * resolution ** <span class="number">2</span>,</span><br><span class="line">                representation_size)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> box_predictor <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            representation_size = <span class="number">1024</span></span><br><span class="line">            box_predictor = FastRCNNPredictor(</span><br><span class="line">                representation_size,</span><br><span class="line">                num_classes)</span><br><span class="line"></span><br><span class="line">        roi_heads = RoIHeads(</span><br><span class="line">            <span class="comment"># Box</span></span><br><span class="line">            box_roi_pool, box_head, box_predictor,</span><br><span class="line">            box_fg_iou_thresh, box_bg_iou_thresh,</span><br><span class="line">            box_batch_size_per_image, box_positive_fraction,</span><br><span class="line">            bbox_reg_weights,</span><br><span class="line">            box_score_thresh, box_nms_thresh, box_detections_per_img)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> image_mean <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            image_mean = [<span class="number">0.485</span>, <span class="number">0.456</span>, <span class="number">0.406</span>]</span><br><span class="line">        <span class="keyword">if</span> image_std <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            image_std = [<span class="number">0.229</span>, <span class="number">0.224</span>, <span class="number">0.225</span>]</span><br><span class="line">        transform = GeneralizedRCNNTransform(min_size, max_size, image_mean, image_std)</span><br><span class="line"></span><br><span class="line">        <span class="built_in">super</span>(FasterRCNN, self).__init__(backbone, rpn, roi_heads, transform)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>FasterRCNN</code>的代码看起来很长，实际上主要是文档注释。</p><p><code>FasterRCNN</code>的实现只有<code>__init__</code>函数，因为<code>FasterRCNN</code>继承自<code>GeneralizedRCNN</code>，主要结构和计算流的实现都在父类中实现了，该子类的实现实际上只需要做一些参数检查和子类的具体子结构的实例化。</p><p>FasterRCNN的<code>__init__</code>函数的主要就是在做参数检查和一些实例化准备工作，其结果就是将准备好的backbone、rpn、roi_heads和transform对象传递给父类（GeneralizedRCNN）的初始化函数，由此构建一个FasterRCNN实例对象。</p><h3 id="generalizedrcnn">GeneralizedRCNN</h3><p><code>GeneralizedRCNN</code>在<code>torchvision.models.detection.generalized_rcnn.py</code>中实现，负责以父类的形式定义RCNN架构的整体计算。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GeneralizedRCNN</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Main class for Generalized R-CNN.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        backbone (nn.Module):</span></span><br><span class="line"><span class="string">        rpn (nn.Module):</span></span><br><span class="line"><span class="string">        roi_heads (nn.Module): takes the features + the proposals from the RPN and computes</span></span><br><span class="line"><span class="string">            detections / masks from it.</span></span><br><span class="line"><span class="string">        transform (nn.Module): performs the data transformation from the inputs to feed into</span></span><br><span class="line"><span class="string">            the model</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, backbone, rpn, roi_heads, transform</span>):</span><br><span class="line">        <span class="built_in">super</span>(GeneralizedRCNN, self).__init__()</span><br><span class="line">        self.transform = transform</span><br><span class="line">        self.backbone = backbone</span><br><span class="line">        self.rpn = rpn</span><br><span class="line">        self.roi_heads = roi_heads</span><br><span class="line">        <span class="comment"># used only on torchscript mode</span></span><br><span class="line">        self._has_warned = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="meta">    @torch.jit.unused</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">eager_outputs</span>(<span class="params">self, losses, detections</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor], <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]) -&gt; <span class="type">Union</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor], <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]]</span></span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">return</span> losses</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> detections</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, images, targets=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="type">Optional</span>[<span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]]) -&gt; <span class="type">Tuple</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor], <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]]</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            images (list[Tensor]): images to be processed</span></span><br><span class="line"><span class="string">            targets (list[Dict[Tensor]]): ground-truth boxes present in the image (optional)</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Returns:</span></span><br><span class="line"><span class="string">            result (list[BoxList] or dict[Tensor]): the output from the model.</span></span><br><span class="line"><span class="string">                During training, it returns a dict[Tensor] which contains the losses.</span></span><br><span class="line"><span class="string">                During testing, it returns list[BoxList] contains additional fields</span></span><br><span class="line"><span class="string">                like `scores`, `labels` and `mask` (for Mask R-CNN models).</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> self.training <span class="keyword">and</span> targets <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">raise</span> ValueError(<span class="string">&quot;In training mode, targets should be passed&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">assert</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">            <span class="keyword">for</span> target <span class="keyword">in</span> targets:</span><br><span class="line">                boxes = target[<span class="string">&quot;boxes&quot;</span>]</span><br><span class="line">                <span class="keyword">if</span> <span class="built_in">isinstance</span>(boxes, torch.Tensor):</span><br><span class="line">                    <span class="keyword">if</span> <span class="built_in">len</span>(boxes.shape) != <span class="number">2</span> <span class="keyword">or</span> boxes.shape[-<span class="number">1</span>] != <span class="number">4</span>:</span><br><span class="line">                        <span class="keyword">raise</span> ValueError(<span class="string">&quot;Expected target boxes to be a tensor&quot;</span></span><br><span class="line">                                         <span class="string">&quot;of shape [N, 4], got &#123;:&#125;.&quot;</span>.<span class="built_in">format</span>(</span><br><span class="line">                                             boxes.shape))</span><br><span class="line">                <span class="keyword">else</span>:</span><br><span class="line">                    <span class="keyword">raise</span> ValueError(<span class="string">&quot;Expected target boxes to be of type &quot;</span></span><br><span class="line">                                     <span class="string">&quot;Tensor, got &#123;:&#125;.&quot;</span>.<span class="built_in">format</span>(<span class="built_in">type</span>(boxes)))</span><br><span class="line"></span><br><span class="line">        original_image_sizes = torch.jit.annotate(<span class="type">List</span>[<span class="type">Tuple</span>[<span class="built_in">int</span>, <span class="built_in">int</span>]], [])</span><br><span class="line">        <span class="keyword">for</span> img <span class="keyword">in</span> images:</span><br><span class="line">            val = img.shape[-<span class="number">2</span>:]</span><br><span class="line">            <span class="keyword">assert</span> <span class="built_in">len</span>(val) == <span class="number">2</span></span><br><span class="line">            original_image_sizes.append((val[<span class="number">0</span>], val[<span class="number">1</span>]))</span><br><span class="line"></span><br><span class="line">        images, targets = self.transform(images, targets)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Check for degenerate boxes</span></span><br><span class="line">        <span class="comment"># <span class="doctag">TODO:</span> Move this to a function</span></span><br><span class="line">        <span class="keyword">if</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">for</span> target_idx, target <span class="keyword">in</span> <span class="built_in">enumerate</span>(targets):</span><br><span class="line">                boxes = target[<span class="string">&quot;boxes&quot;</span>]</span><br><span class="line">                degenerate_boxes = boxes[:, <span class="number">2</span>:] &lt;= boxes[:, :<span class="number">2</span>]</span><br><span class="line">                <span class="keyword">if</span> degenerate_boxes.<span class="built_in">any</span>():</span><br><span class="line">                    <span class="comment"># print the first degenerate box</span></span><br><span class="line">                    bb_idx = torch.where(degenerate_boxes.<span class="built_in">any</span>(dim=<span class="number">1</span>))[<span class="number">0</span>][<span class="number">0</span>]</span><br><span class="line">                    degen_bb: <span class="type">List</span>[<span class="built_in">float</span>] = boxes[bb_idx].tolist()</span><br><span class="line">                    <span class="keyword">raise</span> ValueError(<span class="string">&quot;All bounding boxes should have positive height and width.&quot;</span></span><br><span class="line">                                     <span class="string">&quot; Found invalid box &#123;&#125; for target at index &#123;&#125;.&quot;</span></span><br><span class="line">                                     .<span class="built_in">format</span>(degen_bb, target_idx))</span><br><span class="line"></span><br><span class="line">        features = self.backbone(images.tensors)</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">isinstance</span>(features, torch.Tensor):</span><br><span class="line">            features = OrderedDict([(<span class="string">&#x27;0&#x27;</span>, features)])</span><br><span class="line">        proposals, proposal_losses = self.rpn(images, features, targets)</span><br><span class="line">        detections, detector_losses = self.roi_heads(features, proposals, images.image_sizes, targets)</span><br><span class="line">        detections = self.transform.postprocess(detections, images.image_sizes, original_image_sizes)</span><br><span class="line"></span><br><span class="line">        losses = &#123;&#125;</span><br><span class="line">        losses.update(detector_losses)</span><br><span class="line">        losses.update(proposal_losses)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> torch.jit.is_scripting():</span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">not</span> self._has_warned:</span><br><span class="line">                warnings.warn(<span class="string">&quot;RCNN always returns a (Losses, Detections) tuple in scripting&quot;</span>)</span><br><span class="line">                self._has_warned = <span class="literal">True</span></span><br><span class="line">            <span class="keyword">return</span> (losses, detections)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">return</span> self.eager_outputs(losses, detections)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在<code>__init__</code>中，GenerailizedRCNN把R-CNN架构定义为4个组成部分：</p><ol type="1"><li><code>transform</code>：一个变换模型，用于对图像和其他输入进行变换；</li><li><code>backbone</code>：一个特征提取模型，输入的是进过变换处理的图像张量，输出的是取得的图像特征features；</li><li><code>rpn</code>：一个RPN模型，输入包含——图像images、backbone提取出的图像特征features以及训练时输入的包含bboxground truth的targets，输出包含——预测的区域proposals和相应的损失；</li><li><code>roi_heads</code>：一个RoIHeads模型，输入包含——backbone输出的features，RPN输出的proposals，以及图像尺寸和训练时的targets。</li></ol><p>该类的<code>__forward__</code>计算流差不多就是这四部分依次执行的过程，除了一些参数检查，训练时和预测时对输入的区分以外，主要代码逻辑可以概括为：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, images, targets=<span class="literal">None</span></span>):</span><br><span class="line">    <span class="comment"># 1. transform</span></span><br><span class="line">    images, targets = self.transform(images, targets)</span><br><span class="line">    <span class="comment"># 2. backbone</span></span><br><span class="line">    features = self.backbone(images.tensors)</span><br><span class="line">    <span class="comment"># 3. rpn</span></span><br><span class="line">    proposals, proposal_losses = self.rpn(images, features, targets)</span><br><span class="line">    <span class="comment"># 4. roi_heads</span></span><br><span class="line">    detections, detector_losses = self.roi_heads(features, proposals, images.image_sizes, targets)</span><br><span class="line">    <span class="comment"># 5. postprocess</span></span><br><span class="line">    detections = self.transform.postprocess(detections, images.image_sizes, original_image_sizes)</span><br></pre></td></tr></table></figure><h4 id="generalizedrcnntransform">GeneralizedRCNNTransform</h4><p>FasterR-CNN模型对输入图像的预处理由<code>torchvision.models.detection.transform</code>包的<code>GeneralizedRCNNTransform</code>类实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GeneralizedRCNNTransform</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Performs input / target transformation before feeding the data to a GeneralizedRCNN</span></span><br><span class="line"><span class="string">    model.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The transformations it perform are:</span></span><br><span class="line"><span class="string">        - input normalization (mean subtraction and std division)</span></span><br><span class="line"><span class="string">        - input / target resizing to match min_size / max_size</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    It returns a ImageList for the inputs, and a List[Dict[Tensor]] for the targets</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, min_size, max_size, image_mean, image_std</span>):</span><br><span class="line">        <span class="built_in">super</span>(GeneralizedRCNNTransform, self).__init__()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">isinstance</span>(min_size, (<span class="built_in">list</span>, <span class="built_in">tuple</span>)):</span><br><span class="line">            min_size = (min_size,)</span><br><span class="line">        self.min_size = min_size</span><br><span class="line">        self.max_size = max_size</span><br><span class="line">        self.image_mean = image_mean</span><br><span class="line">        self.image_std = image_std</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                images,       <span class="comment"># type: List[Tensor]</span></span></span><br><span class="line"><span class="params">                targets=<span class="literal">None</span>  <span class="comment"># type: Optional[List[Dict[str, Tensor]]]</span></span></span><br><span class="line"><span class="params">                </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">Tuple</span>[ImageList, <span class="type">Optional</span>[<span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]]]</span></span><br><span class="line">        images = [img <span class="keyword">for</span> img <span class="keyword">in</span> images]</span><br><span class="line">        <span class="keyword">if</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="comment"># make a copy of targets to avoid modifying it in-place</span></span><br><span class="line">            <span class="comment"># once torchscript supports dict comprehension</span></span><br><span class="line">            <span class="comment"># this can be simplified as as follows</span></span><br><span class="line">            <span class="comment"># targets = [&#123;k: v for k,v in t.items()&#125; for t in targets]</span></span><br><span class="line">            targets_copy: <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]] = []</span><br><span class="line">            <span class="keyword">for</span> t <span class="keyword">in</span> targets:</span><br><span class="line">                data: <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor] = &#123;&#125;</span><br><span class="line">                <span class="keyword">for</span> k, v <span class="keyword">in</span> t.items():</span><br><span class="line">                    data[k] = v</span><br><span class="line">                targets_copy.append(data)</span><br><span class="line">            targets = targets_copy</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(images)):</span><br><span class="line">            image = images[i]</span><br><span class="line">            target_index = targets[i] <span class="keyword">if</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">else</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> image.dim() != <span class="number">3</span>:</span><br><span class="line">                <span class="keyword">raise</span> ValueError(<span class="string">&quot;images is expected to be a list of 3d tensors &quot;</span></span><br><span class="line">                                 <span class="string">&quot;of shape [C, H, W], got &#123;&#125;&quot;</span>.<span class="built_in">format</span>(image.shape))</span><br><span class="line">            image = self.normalize(image)</span><br><span class="line">            image, target_index = self.resize(image, target_index)</span><br><span class="line">            images[i] = image</span><br><span class="line">            <span class="keyword">if</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">and</span> target_index <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">                targets[i] = target_index</span><br><span class="line"></span><br><span class="line">        image_sizes = [img.shape[-<span class="number">2</span>:] <span class="keyword">for</span> img <span class="keyword">in</span> images]</span><br><span class="line">        images = self.batch_images(images)</span><br><span class="line">        image_sizes_list = torch.jit.annotate(<span class="type">List</span>[<span class="type">Tuple</span>[<span class="built_in">int</span>, <span class="built_in">int</span>]], [])</span><br><span class="line">        <span class="keyword">for</span> image_size <span class="keyword">in</span> image_sizes:</span><br><span class="line">            <span class="keyword">assert</span> <span class="built_in">len</span>(image_size) == <span class="number">2</span></span><br><span class="line">            image_sizes_list.append((image_size[<span class="number">0</span>], image_size[<span class="number">1</span>]))</span><br><span class="line"></span><br><span class="line">        image_list = ImageList(images, image_sizes_list)</span><br><span class="line">        <span class="keyword">return</span> image_list, targets</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">normalize</span>(<span class="params">self, image</span>):</span><br><span class="line">        dtype, device = image.dtype, image.device</span><br><span class="line">        mean = torch.as_tensor(self.image_mean, dtype=dtype, device=device)</span><br><span class="line">        std = torch.as_tensor(self.image_std, dtype=dtype, device=device)</span><br><span class="line">        <span class="keyword">return</span> (image - mean[:, <span class="literal">None</span>, <span class="literal">None</span>]) / std[:, <span class="literal">None</span>, <span class="literal">None</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">torch_choice</span>(<span class="params">self, k</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[<span class="built_in">int</span>]) -&gt; <span class="built_in">int</span></span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Implements `random.choice` via torch ops so it can be compiled with</span></span><br><span class="line"><span class="string">        TorchScript. Remove if https://github.com/pytorch/pytorch/issues/25803</span></span><br><span class="line"><span class="string">        is fixed.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        index = <span class="built_in">int</span>(torch.empty(<span class="number">1</span>).uniform_(<span class="number">0.</span>, <span class="built_in">float</span>(<span class="built_in">len</span>(k))).item())</span><br><span class="line">        <span class="keyword">return</span> k[index]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">resize</span>(<span class="params">self, image, target</span>):</span><br><span class="line">        <span class="comment"># type: (Tensor, <span class="type">Optional</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]) -&gt; <span class="type">Tuple</span>[Tensor, <span class="type">Optional</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]]</span></span><br><span class="line">        h, w = image.shape[-<span class="number">2</span>:]</span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            size = <span class="built_in">float</span>(self.torch_choice(self.min_size))</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment"># FIXME assume for now that testing uses the largest scale</span></span><br><span class="line">            size = <span class="built_in">float</span>(self.min_size[-<span class="number">1</span>])</span><br><span class="line">        <span class="keyword">if</span> torchvision._is_tracing():</span><br><span class="line">            image, target = _resize_image_and_masks_onnx(image, size, <span class="built_in">float</span>(self.max_size), target)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            image, target = _resize_image_and_masks(image, size, <span class="built_in">float</span>(self.max_size), target)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> target <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> image, target</span><br><span class="line"></span><br><span class="line">        bbox = target[<span class="string">&quot;boxes&quot;</span>]</span><br><span class="line">        bbox = resize_boxes(bbox, (h, w), image.shape[-<span class="number">2</span>:])</span><br><span class="line">        target[<span class="string">&quot;boxes&quot;</span>] = bbox</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="string">&quot;keypoints&quot;</span> <span class="keyword">in</span> target:</span><br><span class="line">            keypoints = target[<span class="string">&quot;keypoints&quot;</span>]</span><br><span class="line">            keypoints = resize_keypoints(keypoints, (h, w), image.shape[-<span class="number">2</span>:])</span><br><span class="line">            target[<span class="string">&quot;keypoints&quot;</span>] = keypoints</span><br><span class="line">        <span class="keyword">return</span> image, target</span><br><span class="line"></span><br><span class="line">    <span class="comment"># _onnx_batch_images() is an implementation of</span></span><br><span class="line">    <span class="comment"># batch_images() that is supported by ONNX tracing.</span></span><br><span class="line"><span class="meta">    @torch.jit.unused</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_onnx_batch_images</span>(<span class="params">self, images, size_divisible=<span class="number">32</span></span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="built_in">int</span>) -&gt; Tensor</span></span><br><span class="line">        max_size = []</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(images[<span class="number">0</span>].dim()):</span><br><span class="line">            max_size_i = torch.<span class="built_in">max</span>(torch.stack([img.shape[i] <span class="keyword">for</span> img <span class="keyword">in</span> images]).to(torch.float32)).to(torch.int64)</span><br><span class="line">            max_size.append(max_size_i)</span><br><span class="line">        stride = size_divisible</span><br><span class="line">        max_size[<span class="number">1</span>] = (torch.ceil((max_size[<span class="number">1</span>].to(torch.float32)) / stride) * stride).to(torch.int64)</span><br><span class="line">        max_size[<span class="number">2</span>] = (torch.ceil((max_size[<span class="number">2</span>].to(torch.float32)) / stride) * stride).to(torch.int64)</span><br><span class="line">        max_size = <span class="built_in">tuple</span>(max_size)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># work around for</span></span><br><span class="line">        <span class="comment"># pad_img[: img.shape[0], : img.shape[1], : img.shape[2]].copy_(img)</span></span><br><span class="line">        <span class="comment"># which is not yet supported in onnx</span></span><br><span class="line">        padded_imgs = []</span><br><span class="line">        <span class="keyword">for</span> img <span class="keyword">in</span> images:</span><br><span class="line">            padding = [(s1 - s2) <span class="keyword">for</span> s1, s2 <span class="keyword">in</span> <span class="built_in">zip</span>(max_size, <span class="built_in">tuple</span>(img.shape))]</span><br><span class="line">            padded_img = torch.nn.functional.pad(img, (<span class="number">0</span>, padding[<span class="number">2</span>], <span class="number">0</span>, padding[<span class="number">1</span>], <span class="number">0</span>, padding[<span class="number">0</span>]))</span><br><span class="line">            padded_imgs.append(padded_img)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> torch.stack(padded_imgs)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">max_by_axis</span>(<span class="params">self, the_list</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[<span class="type">List</span>[<span class="built_in">int</span>]]) -&gt; <span class="type">List</span>[<span class="built_in">int</span>]</span></span><br><span class="line">        maxes = the_list[<span class="number">0</span>]</span><br><span class="line">        <span class="keyword">for</span> sublist <span class="keyword">in</span> the_list[<span class="number">1</span>:]:</span><br><span class="line">            <span class="keyword">for</span> index, item <span class="keyword">in</span> <span class="built_in">enumerate</span>(sublist):</span><br><span class="line">                maxes[index] = <span class="built_in">max</span>(maxes[index], item)</span><br><span class="line">        <span class="keyword">return</span> maxes</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">batch_images</span>(<span class="params">self, images, size_divisible=<span class="number">32</span></span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="built_in">int</span>) -&gt; Tensor</span></span><br><span class="line">        <span class="keyword">if</span> torchvision._is_tracing():</span><br><span class="line">            <span class="comment"># batch_images() does not export well to ONNX</span></span><br><span class="line">            <span class="comment"># call _onnx_batch_images() instead</span></span><br><span class="line">            <span class="keyword">return</span> self._onnx_batch_images(images, size_divisible)</span><br><span class="line"></span><br><span class="line">        max_size = self.max_by_axis([<span class="built_in">list</span>(img.shape) <span class="keyword">for</span> img <span class="keyword">in</span> images])</span><br><span class="line">        stride = <span class="built_in">float</span>(size_divisible)</span><br><span class="line">        max_size = <span class="built_in">list</span>(max_size)</span><br><span class="line">        max_size[<span class="number">1</span>] = <span class="built_in">int</span>(math.ceil(<span class="built_in">float</span>(max_size[<span class="number">1</span>]) / stride) * stride)</span><br><span class="line">        max_size[<span class="number">2</span>] = <span class="built_in">int</span>(math.ceil(<span class="built_in">float</span>(max_size[<span class="number">2</span>]) / stride) * stride)</span><br><span class="line"></span><br><span class="line">        batch_shape = [<span class="built_in">len</span>(images)] + max_size</span><br><span class="line">        batched_imgs = images[<span class="number">0</span>].new_full(batch_shape, <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">for</span> img, pad_img <span class="keyword">in</span> <span class="built_in">zip</span>(images, batched_imgs):</span><br><span class="line">            pad_img[: img.shape[<span class="number">0</span>], : img.shape[<span class="number">1</span>], : img.shape[<span class="number">2</span>]].copy_(img)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> batched_imgs</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">postprocess</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                    result,               <span class="comment"># type: List[Dict[str, Tensor]]</span></span></span><br><span class="line"><span class="params">                    image_shapes,         <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                    original_image_sizes  <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                    </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]</span></span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">return</span> result</span><br><span class="line">        <span class="keyword">for</span> i, (pred, im_s, o_im_s) <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="built_in">zip</span>(result, image_shapes, original_image_sizes)):</span><br><span class="line">            boxes = pred[<span class="string">&quot;boxes&quot;</span>]</span><br><span class="line">            boxes = resize_boxes(boxes, im_s, o_im_s)</span><br><span class="line">            result[i][<span class="string">&quot;boxes&quot;</span>] = boxes</span><br><span class="line">            <span class="keyword">if</span> <span class="string">&quot;masks&quot;</span> <span class="keyword">in</span> pred:</span><br><span class="line">                masks = pred[<span class="string">&quot;masks&quot;</span>]</span><br><span class="line">                masks = paste_masks_in_image(masks, boxes, o_im_s)</span><br><span class="line">                result[i][<span class="string">&quot;masks&quot;</span>] = masks</span><br><span class="line">            <span class="keyword">if</span> <span class="string">&quot;keypoints&quot;</span> <span class="keyword">in</span> pred:</span><br><span class="line">                keypoints = pred[<span class="string">&quot;keypoints&quot;</span>]</span><br><span class="line">                keypoints = resize_keypoints(keypoints, im_s, o_im_s)</span><br><span class="line">                result[i][<span class="string">&quot;keypoints&quot;</span>] = keypoints</span><br><span class="line">        <span class="keyword">return</span> result</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__repr__</span>(<span class="params">self</span>):</span><br><span class="line">        format_string = self.__class__.__name__ + <span class="string">&#x27;(&#x27;</span></span><br><span class="line">        _indent = <span class="string">&#x27;\n    &#x27;</span></span><br><span class="line">        format_string += <span class="string">&quot;&#123;0&#125;Normalize(mean=&#123;1&#125;, std=&#123;2&#125;)&quot;</span>.<span class="built_in">format</span>(_indent, self.image_mean, self.image_std)</span><br><span class="line">        format_string += <span class="string">&quot;&#123;0&#125;Resize(min_size=&#123;1&#125;, max_size=&#123;2&#125;, mode=&#x27;bilinear&#x27;)&quot;</span>.<span class="built_in">format</span>(_indent, self.min_size,</span><br><span class="line">                                                                                         self.max_size)</span><br><span class="line">        format_string += <span class="string">&#x27;\n)&#x27;</span></span><br><span class="line">        <span class="keyword">return</span> format_string</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>对输入图像的初步转换处理在<code>forward</code>前向传播函数中实现，主要实现normalize和resize操作：</p><ol type="1"><li><code>self.normalize</code>：初始参数在FasterRCNN的初始化中被设为<code>image_mean = [0.485, 0.456, 0.406]</code>和<code>image_std = [0.229, 0.224, 0.225]</code>；</li><li><code>self.resize</code>：初始参数在FasterRCNN的初始化中被设为<code>min_size=800, max_size=1333</code>；</li><li><code>self.batch_images</code>，对一个batch的图像做了Padding，使其输出的张量尺寸一致。</li></ol><p>根据该转换模块的默认值，结合本节开头的例子：</p><ol type="1"><li>经过resize处理后，因为最小尺寸必须为800，因此<spanclass="math inline">\(300 \times 400\)</span>的图片1转换为了<spanclass="math inline">\(800 \times 1066\)</span>，<spanclass="math inline">\(400 \times 500\)</span>的图片2转换为了<spanclass="math inline">\(1000 \times 800\)</span>；</li><li>因为batch处理转tensors时加padding的缘故，两个图片的张量尺寸被统一为<spanclass="math inline">\(1024 \times 1088\)</span>。</li></ol><h4 id="backbonewithfpn">BackboneWithFPN</h4><p><code>BackboneWithFPN</code>在<code>torchvision.models.detection.backbone_utils</code>中实现，其作用就是以ResNet模型中提取出的一些中间层作为backbone，在backbone后面继续接上一个FPN。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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">class</span> <span class="title class_">BackboneWithFPN</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Adds a FPN on top of a model.</span></span><br><span class="line"><span class="string">    Internally, it uses torchvision.models._utils.IntermediateLayerGetter to</span></span><br><span class="line"><span class="string">    extract a submodel that returns the feature maps specified in return_layers.</span></span><br><span class="line"><span class="string">    The same limitations of IntermediatLayerGetter apply here.</span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        backbone (nn.Module)</span></span><br><span class="line"><span class="string">        return_layers (Dict[name, new_name]): a dict containing the names</span></span><br><span class="line"><span class="string">            of the modules for which the activations will be returned as</span></span><br><span class="line"><span class="string">            the key of the dict, and the value of the dict is the name</span></span><br><span class="line"><span class="string">            of the returned activation (which the user can specify).</span></span><br><span class="line"><span class="string">        in_channels_list (List[int]): number of channels for each feature map</span></span><br><span class="line"><span class="string">            that is returned, in the order they are present in the OrderedDict</span></span><br><span class="line"><span class="string">        out_channels (int): number of channels in the FPN.</span></span><br><span class="line"><span class="string">    Attributes:</span></span><br><span class="line"><span class="string">        out_channels (int): the number of channels in the FPN</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, backbone, return_layers, in_channels_list, out_channels, extra_blocks=<span class="literal">None</span></span>):</span><br><span class="line">        <span class="built_in">super</span>(BackboneWithFPN, self).__init__()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> extra_blocks <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            extra_blocks = LastLevelMaxPool()</span><br><span class="line"></span><br><span class="line">        self.body = IntermediateLayerGetter(backbone, return_layers=return_layers)</span><br><span class="line">        self.fpn = FeaturePyramidNetwork(</span><br><span class="line">            in_channels_list=in_channels_list,</span><br><span class="line">            out_channels=out_channels,</span><br><span class="line">            extra_blocks=extra_blocks,</span><br><span class="line">        )</span><br><span class="line">        self.out_channels = out_channels</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        x = self.body(x)</span><br><span class="line">        x = self.fpn(x)</span><br><span class="line">        <span class="keyword">return</span> x</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>该类的实现很简单，就像是一个组合，把backbone和FPN装起来：</p><ol type="1"><li>把从backbone中取出的（用于提供featuremaps）中间层作为模型的body；</li><li>构造出FPN（FeaturePyramidNetworkj）作为模型的fpn；</li></ol><p>然后数据流定义很简洁，就是输入数据x先经过body，再经过fpn，就完成了。</p><p>有了backbone+FPN的模型，就可以进一步构造Faster R-CNN模型了。</p><p><code>torchvision</code>的FasterR-CNN的backbone负责提取图像特征，具体实现由ResNet中间层衔接FPN组成。</p><h5 id="resnet">ResNet</h5><p><code>class ResNet(nn.Module)</code></p><p>ResNet在<code>torchvision.models.resnet</code>包中实现，属于卷积神经网络实现的范畴，本文不再赘述。</p><p>有了resnet作为backbone，就可以通过<code>resnet_fpn_backbone</code>构造一个在resnet后面接上FPN的模型，具体地，是构造<code>BackboneWithFPN</code>类的对象。</p><p><code>torchvision</code>实现中：</p><ol type="1"><li>默认后三层，即ResNet的layer4, layer3,layer2为可训练层，其余freeze；</li><li>默认返回后四层的feature map，即layer1, layer2, layer3,layer4，命名index依次为0, 1, 2, 3，每层输出feature map的通道数依次为256,512, 1024, 2048。</li></ol><p>本节的例子经过ResNet部分的计算后，从输入的<spanclass="math inline">\(2 \times 3 \times 1024 \times1088\)</span>的tensor，转换为了一个有序字典OrderedDict：</p><ol type="1"><li><code>'0': shape[2, 256, 256, 272]</code>，源自ResNet的layer1；</li><li><code>'1': shape[2, 512, 128, 136]</code>，源自ResNet的layer2；</li><li><code>'2': shape[2, 1024, 64, 68]</code>，源自ResNet的layer3；</li><li><code>'3': shape[2, 2048, 32, 34]</code>，源自ResNet的layer4；</li></ol><h5 id="fpnfeaturepyramidnetwork">FPN(FeaturePyramidNetwork)</h5><p><code>FeaturePyramidNetwork</code>在<code>torchvision.ops.feature_pyramid_network</code>包中实现。FPN实现了金字塔结构的特征提取，低层的卷积感受野小，其特征代表小目标的特征，而高层的卷积感受野大，因此其特征适合表示大目标特征。在目标检测中运用FPN，在低层配合小尺寸anchor，在高层配合大尺寸anchors，有利于同时有效检测小目标和大目标。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FeaturePyramidNetwork</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Module that adds a FPN from on top of a set of feature maps. This is based on</span></span><br><span class="line"><span class="string">    `&quot;Feature Pyramid Network for Object Detection&quot; &lt;https://arxiv.org/abs/1612.03144&gt;`_.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The feature maps are currently supposed to be in increasing depth</span></span><br><span class="line"><span class="string">    order.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The input to the model is expected to be an OrderedDict[Tensor], containing</span></span><br><span class="line"><span class="string">    the feature maps on top of which the FPN will be added.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        in_channels_list (list[int]): number of channels for each feature map that</span></span><br><span class="line"><span class="string">            is passed to the module</span></span><br><span class="line"><span class="string">        out_channels (int): number of channels of the FPN representation</span></span><br><span class="line"><span class="string">        extra_blocks (ExtraFPNBlock or None): if provided, extra operations will</span></span><br><span class="line"><span class="string">            be performed. It is expected to take the fpn features, the original</span></span><br><span class="line"><span class="string">            features and the names of the original features as input, and returns</span></span><br><span class="line"><span class="string">            a new list of feature maps and their corresponding names</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Examples::</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; m = torchvision.ops.FeaturePyramidNetwork([10, 20, 30], 5)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # get some dummy data</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x = OrderedDict()</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x[&#x27;feat0&#x27;] = torch.rand(1, 10, 64, 64)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x[&#x27;feat2&#x27;] = torch.rand(1, 20, 16, 16)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; x[&#x27;feat3&#x27;] = torch.rand(1, 30, 8, 8)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # compute the FPN on top of x</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; output = m(x)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; print([(k, v.shape) for k, v in output.items()])</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # returns</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;   [(&#x27;feat0&#x27;, torch.Size([1, 5, 64, 64])),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;    (&#x27;feat2&#x27;, torch.Size([1, 5, 16, 16])),</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt;    (&#x27;feat3&#x27;, torch.Size([1, 5, 8, 8]))]</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        in_channels_list: <span class="type">List</span>[<span class="built_in">int</span>],</span></span><br><span class="line"><span class="params">        out_channels: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">        extra_blocks: <span class="type">Optional</span>[ExtraFPNBlock] = <span class="literal">None</span>,</span></span><br><span class="line"><span class="params">    </span>):</span><br><span class="line">        <span class="built_in">super</span>(FeaturePyramidNetwork, self).__init__()</span><br><span class="line">        self.inner_blocks = nn.ModuleList()</span><br><span class="line">        self.layer_blocks = nn.ModuleList()</span><br><span class="line">        <span class="keyword">for</span> in_channels <span class="keyword">in</span> in_channels_list:</span><br><span class="line">            <span class="keyword">if</span> in_channels == <span class="number">0</span>:</span><br><span class="line">                <span class="keyword">raise</span> ValueError(<span class="string">&quot;in_channels=0 is currently not supported&quot;</span>)</span><br><span class="line">            inner_block_module = nn.Conv2d(in_channels, out_channels, <span class="number">1</span>)</span><br><span class="line">            layer_block_module = nn.Conv2d(out_channels, out_channels, <span class="number">3</span>, padding=<span class="number">1</span>)</span><br><span class="line">            self.inner_blocks.append(inner_block_module)</span><br><span class="line">            self.layer_blocks.append(layer_block_module)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># initialize parameters now to avoid modifying the initialization of top_blocks</span></span><br><span class="line">        <span class="keyword">for</span> m <span class="keyword">in</span> self.children():</span><br><span class="line">            <span class="keyword">if</span> <span class="built_in">isinstance</span>(m, nn.Conv2d):</span><br><span class="line">                nn.init.kaiming_uniform_(m.weight, a=<span class="number">1</span>)</span><br><span class="line">                nn.init.constant_(m.bias, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> extra_blocks <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">assert</span> <span class="built_in">isinstance</span>(extra_blocks, ExtraFPNBlock)</span><br><span class="line">        self.extra_blocks = extra_blocks</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_result_from_inner_blocks</span>(<span class="params">self, x: Tensor, idx: <span class="built_in">int</span></span>) -&gt; Tensor:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        This is equivalent to self.inner_blocks[idx](x),</span></span><br><span class="line"><span class="string">        but torchscript doesn&#x27;t support this yet</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        num_blocks = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> m <span class="keyword">in</span> self.inner_blocks:</span><br><span class="line">            num_blocks += <span class="number">1</span></span><br><span class="line">        <span class="keyword">if</span> idx &lt; <span class="number">0</span>:</span><br><span class="line">            idx += num_blocks</span><br><span class="line">        i = <span class="number">0</span></span><br><span class="line">        out = x</span><br><span class="line">        <span class="keyword">for</span> module <span class="keyword">in</span> self.inner_blocks:</span><br><span class="line">            <span class="keyword">if</span> i == idx:</span><br><span class="line">                out = module(x)</span><br><span class="line">            i += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> out</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">get_result_from_layer_blocks</span>(<span class="params">self, x: Tensor, idx: <span class="built_in">int</span></span>) -&gt; Tensor:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        This is equivalent to self.layer_blocks[idx](x),</span></span><br><span class="line"><span class="string">        but torchscript doesn&#x27;t support this yet</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        num_blocks = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> m <span class="keyword">in</span> self.layer_blocks:</span><br><span class="line">            num_blocks += <span class="number">1</span></span><br><span class="line">        <span class="keyword">if</span> idx &lt; <span class="number">0</span>:</span><br><span class="line">            idx += num_blocks</span><br><span class="line">        i = <span class="number">0</span></span><br><span class="line">        out = x</span><br><span class="line">        <span class="keyword">for</span> module <span class="keyword">in</span> self.layer_blocks:</span><br><span class="line">            <span class="keyword">if</span> i == idx:</span><br><span class="line">                out = module(x)</span><br><span class="line">            i += <span class="number">1</span></span><br><span class="line">        <span class="keyword">return</span> out</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x: <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]</span>) -&gt; <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Computes the FPN for a set of feature maps.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            x (OrderedDict[Tensor]): feature maps for each feature level.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Returns:</span></span><br><span class="line"><span class="string">            results (OrderedDict[Tensor]): feature maps after FPN layers.</span></span><br><span class="line"><span class="string">                They are ordered from highest resolution first.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># unpack OrderedDict into two lists for easier handling</span></span><br><span class="line">        names = <span class="built_in">list</span>(x.keys())</span><br><span class="line">        x = <span class="built_in">list</span>(x.values())</span><br><span class="line"></span><br><span class="line">        last_inner = self.get_result_from_inner_blocks(x[-<span class="number">1</span>], -<span class="number">1</span>)</span><br><span class="line">        results = []</span><br><span class="line">        results.append(self.get_result_from_layer_blocks(last_inner, -<span class="number">1</span>))</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> idx <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(x) - <span class="number">2</span>, -<span class="number">1</span>, -<span class="number">1</span>):</span><br><span class="line">            inner_lateral = self.get_result_from_inner_blocks(x[idx], idx)</span><br><span class="line">            feat_shape = inner_lateral.shape[-<span class="number">2</span>:]</span><br><span class="line">            inner_top_down = F.interpolate(last_inner, size=feat_shape, mode=<span class="string">&quot;nearest&quot;</span>)</span><br><span class="line">            last_inner = inner_lateral + inner_top_down</span><br><span class="line">            results.insert(<span class="number">0</span>, self.get_result_from_layer_blocks(last_inner, idx))</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> self.extra_blocks <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            results, names = self.extra_blocks(results, x, names)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># make it back an OrderedDict</span></span><br><span class="line">        out = OrderedDict([(k, v) <span class="keyword">for</span> k, v <span class="keyword">in</span> <span class="built_in">zip</span>(names, results)])</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> out</span><br></pre></td></tr></table></figure><p>按原论文的思路，FPN第n层输出feature map <spanclass="math inline">\(P_n\)</span>的是把两者进行合并：</p><ol type="1"><li>lateral：CNN的第n层feature map <spanclass="math inline">\(C_n\)</span>，做1×1卷积；</li><li>top-down upsampling：FPN的n+1层feature map <spanclass="math inline">\(P_{n+1}\)</span>做2×上采样（长宽各2倍）变成第n层的尺寸；</li></ol><p>此后，采用3×3卷积对合并后的featuremap进行卷积处理，以便消除上采样操作造成的失真效应（aliasingeffect）。</p><p>此时，形成的每层的最终的feature map就是最终的feature map <spanclass="math inline">\(P_n\)</span>，例如：从ResNet的2~5层feature map<span class="math inline">\(\{C_2, C_3, C_4,C_5\}\)</span>经过FPN取得<span class="math inline">\(\{P_2, P_3, P_4,P_5\}\)</span>，对应的两者的空域尺寸（spatial size）是相同的。</p><p>在<code>torchvision</code>的具体实现中：</p><ol type="1"><li><code>self.inner_blocks</code>就是FPN的所有1×1卷积；</li><li><code>self.layer_blocks</code>就是FPN合并后需要用到的3×3卷积；</li></ol><p>这两者都是<code>nn.ModuleList()</code>，在<code>__init__</code>初始化时，在一个n次（n个featuremap）的for循环中进行初始化，都填入<code>nn.Conv2d</code>对象，设置为统一的<code>out_channels</code>。</p><p>在<code>__forward__</code>定义的计算流中，核心代码逻辑可以概括为：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x: <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]</span>):</span><br><span class="line">    <span class="keyword">for</span> idx <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(x) - <span class="number">2</span>, -<span class="number">1</span>, -<span class="number">1</span>):</span><br><span class="line">        inner_lateral = self.get_result_from_inner_blocks(x[idx], idx)</span><br><span class="line">        feat_shape = inner_lateral.shape[-<span class="number">2</span>:]</span><br><span class="line">        inner_top_down = F.interpolate(last_inner, size=feat_shape, mode=<span class="string">&quot;nearest&quot;</span>)</span><br><span class="line">        last_inner = inner_lateral + inner_top_down</span><br><span class="line">        results.insert(<span class="number">0</span>, self.get_result_from_layer_blocks(last_inner, idx))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> self.extra_blocks <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">        results, names = self.extra_blocks(results, x, names)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> out</span><br></pre></td></tr></table></figure><p>具体步骤是从后往前计算每一层的result，即论文中的<spanclass="math inline">\(P_n\)</span>：</p><ol type="1"><li><code>inner_lateral</code>就是CNN的featuremap经过1×1卷积计算的结果，该卷积通过<code>self.get_result_from_inner_blocks(x[idx], idx)</code>实现；</li><li><code>inner_top_down</code>就是从后一层<spanclass="math inline">\(P_{n+1}\)</span>上采样出来的结果，该上采样通过插值实现<code>F.interpolate(last_inner, size=feat_shape, mode="nearest")</code>；</li><li><code>last_inner</code>就是两者合并的结果，通过element-wiseaddition实现；</li><li>在加入<code>results</code>前，还需要用3×3卷积计算一下，即<code>self.get_result_from_layer_blocks(last_inner, idx)</code>。</li></ol><p>最后，如果还有额外计算块的话，就再算一遍，取得这层的结果也加入。</p><p>在具体实现中，在FPN尾部增加了<code>LastLevelMaxPool</code>，并将其计算结果命名为<code>pool</code>加入了<code>names</code>。</p><p>本节的例子经过FPN部分的计算后，从ResNet输出的4个通道数不同的featuremaps，转换为了各层通道数一致的一个有序字典OrderedDict：</p><ol type="1"><li><code>'0': shape[2, 256, 256, 272]</code>，源自ResNet的layer1；</li><li><code>'1': shape[2, 256, 128, 136]</code>，源自ResNet的layer2；</li><li><code>'2': shape[2, 256, 64, 68]</code>，源自ResNet的layer3；</li><li><code>'3': shape[2, 256, 32, 34]</code>，源自ResNet的layer4；</li><li><code>'pool': shape[2, 256, 16, 17]</code>，源自FPN作为<code>extra_blocks</code>的<code>LastLevelMaxPool</code>。</li></ol><h4 id="regionproposalnetwork">RegionProposalNetwork</h4><p><code>RegionProposalNetwork</code>在<code>torchvision.models.detection.rpn</code>包中实现。</p><p><code>RegionProposalNetwork</code>的实现比较长，主要看<code>__init__</code>和<code>__forward__</code>就可以了。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">RegionProposalNetwork</span>(torch.nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Implements Region Proposal Network (RPN).</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        anchor_generator (AnchorGenerator): module that generates the anchors for a set of feature</span></span><br><span class="line"><span class="string">            maps.</span></span><br><span class="line"><span class="string">        head (nn.Module): module that computes the objectness and regression deltas</span></span><br><span class="line"><span class="string">        fg_iou_thresh (float): minimum IoU between the anchor and the GT box so that they can be</span></span><br><span class="line"><span class="string">            considered as positive during training of the RPN.</span></span><br><span class="line"><span class="string">        bg_iou_thresh (float): maximum IoU between the anchor and the GT box so that they can be</span></span><br><span class="line"><span class="string">            considered as negative during training of the RPN.</span></span><br><span class="line"><span class="string">        batch_size_per_image (int): number of anchors that are sampled during training of the RPN</span></span><br><span class="line"><span class="string">            for computing the loss</span></span><br><span class="line"><span class="string">        positive_fraction (float): proportion of positive anchors in a mini-batch during training</span></span><br><span class="line"><span class="string">            of the RPN</span></span><br><span class="line"><span class="string">        pre_nms_top_n (Dict[int]): number of proposals to keep before applying NMS. It should</span></span><br><span class="line"><span class="string">            contain two fields: training and testing, to allow for different values depending</span></span><br><span class="line"><span class="string">            on training or evaluation</span></span><br><span class="line"><span class="string">        post_nms_top_n (Dict[int]): number of proposals to keep after applying NMS. It should</span></span><br><span class="line"><span class="string">            contain two fields: training and testing, to allow for different values depending</span></span><br><span class="line"><span class="string">            on training or evaluation</span></span><br><span class="line"><span class="string">        nms_thresh (float): NMS threshold used for postprocessing the RPN proposals</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    __annotations__ = &#123;</span><br><span class="line">        <span class="string">&#x27;box_coder&#x27;</span>: det_utils.BoxCoder,</span><br><span class="line">        <span class="string">&#x27;proposal_matcher&#x27;</span>: det_utils.Matcher,</span><br><span class="line">        <span class="string">&#x27;fg_bg_sampler&#x27;</span>: det_utils.BalancedPositiveNegativeSampler,</span><br><span class="line">        <span class="string">&#x27;pre_nms_top_n&#x27;</span>: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="built_in">int</span>],</span><br><span class="line">        <span class="string">&#x27;post_nms_top_n&#x27;</span>: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="built_in">int</span>],</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                 anchor_generator,</span></span><br><span class="line"><span class="params">                 head,</span></span><br><span class="line"><span class="params">                 <span class="comment">#</span></span></span><br><span class="line"><span class="params">                 fg_iou_thresh, bg_iou_thresh,</span></span><br><span class="line"><span class="params">                 batch_size_per_image, positive_fraction,</span></span><br><span class="line"><span class="params">                 <span class="comment">#</span></span></span><br><span class="line"><span class="params">                 pre_nms_top_n, post_nms_top_n, nms_thresh</span>):</span><br><span class="line">        <span class="built_in">super</span>(RegionProposalNetwork, self).__init__()</span><br><span class="line">        self.anchor_generator = anchor_generator</span><br><span class="line">        self.head = head</span><br><span class="line">        self.box_coder = det_utils.BoxCoder(weights=(<span class="number">1.0</span>, <span class="number">1.0</span>, <span class="number">1.0</span>, <span class="number">1.0</span>))</span><br><span class="line"></span><br><span class="line">        <span class="comment"># used during training</span></span><br><span class="line">        self.box_similarity = box_ops.box_iou</span><br><span class="line"></span><br><span class="line">        self.proposal_matcher = det_utils.Matcher(</span><br><span class="line">            fg_iou_thresh,</span><br><span class="line">            bg_iou_thresh,</span><br><span class="line">            allow_low_quality_matches=<span class="literal">True</span>,</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        self.fg_bg_sampler = det_utils.BalancedPositiveNegativeSampler(</span><br><span class="line">            batch_size_per_image, positive_fraction</span><br><span class="line">        )</span><br><span class="line">        <span class="comment"># used during testing</span></span><br><span class="line">        self._pre_nms_top_n = pre_nms_top_n</span><br><span class="line">        self._post_nms_top_n = post_nms_top_n</span><br><span class="line">        self.nms_thresh = nms_thresh</span><br><span class="line">        self.min_size = <span class="number">1e-3</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">pre_nms_top_n</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">return</span> self._pre_nms_top_n[<span class="string">&#x27;training&#x27;</span>]</span><br><span class="line">        <span class="keyword">return</span> self._pre_nms_top_n[<span class="string">&#x27;testing&#x27;</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">post_nms_top_n</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">return</span> self._post_nms_top_n[<span class="string">&#x27;training&#x27;</span>]</span><br><span class="line">        <span class="keyword">return</span> self._post_nms_top_n[<span class="string">&#x27;testing&#x27;</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">assign_targets_to_anchors</span>(<span class="params">self, anchors, targets</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        labels = []</span><br><span class="line">        matched_gt_boxes = []</span><br><span class="line">        <span class="keyword">for</span> anchors_per_image, targets_per_image <span class="keyword">in</span> <span class="built_in">zip</span>(anchors, targets):</span><br><span class="line">            gt_boxes = targets_per_image[<span class="string">&quot;boxes&quot;</span>]</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> gt_boxes.numel() == <span class="number">0</span>:</span><br><span class="line">                <span class="comment"># Background image (negative example)</span></span><br><span class="line">                device = anchors_per_image.device</span><br><span class="line">                matched_gt_boxes_per_image = torch.zeros(anchors_per_image.shape, dtype=torch.float32, device=device)</span><br><span class="line">                labels_per_image = torch.zeros((anchors_per_image.shape[<span class="number">0</span>],), dtype=torch.float32, device=device)</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                match_quality_matrix = self.box_similarity(gt_boxes, anchors_per_image)</span><br><span class="line">                matched_idxs = self.proposal_matcher(match_quality_matrix)</span><br><span class="line">                <span class="comment"># get the targets corresponding GT for each proposal</span></span><br><span class="line">                <span class="comment"># NB: need to clamp the indices because we can have a single</span></span><br><span class="line">                <span class="comment"># GT in the image, and matched_idxs can be -2, which goes</span></span><br><span class="line">                <span class="comment"># out of bounds</span></span><br><span class="line">                matched_gt_boxes_per_image = gt_boxes[matched_idxs.clamp(<span class="built_in">min</span>=<span class="number">0</span>)]</span><br><span class="line"></span><br><span class="line">                labels_per_image = matched_idxs &gt;= <span class="number">0</span></span><br><span class="line">                labels_per_image = labels_per_image.to(dtype=torch.float32)</span><br><span class="line"></span><br><span class="line">                <span class="comment"># Background (negative examples)</span></span><br><span class="line">                bg_indices = matched_idxs == self.proposal_matcher.BELOW_LOW_THRESHOLD</span><br><span class="line">                labels_per_image[bg_indices] = <span class="number">0.0</span></span><br><span class="line"></span><br><span class="line">                <span class="comment"># discard indices that are between thresholds</span></span><br><span class="line">                inds_to_discard = matched_idxs == self.proposal_matcher.BETWEEN_THRESHOLDS</span><br><span class="line">                labels_per_image[inds_to_discard] = -<span class="number">1.0</span></span><br><span class="line"></span><br><span class="line">            labels.append(labels_per_image)</span><br><span class="line">            matched_gt_boxes.append(matched_gt_boxes_per_image)</span><br><span class="line">        <span class="keyword">return</span> labels, matched_gt_boxes</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">_get_top_n_idx</span>(<span class="params">self, objectness, num_anchors_per_level</span>):</span><br><span class="line">        <span class="comment"># type: (Tensor, <span class="type">List</span>[<span class="built_in">int</span>]) -&gt; Tensor</span></span><br><span class="line">        r = []</span><br><span class="line">        offset = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> ob <span class="keyword">in</span> objectness.split(num_anchors_per_level, <span class="number">1</span>):</span><br><span class="line">            <span class="keyword">if</span> torchvision._is_tracing():</span><br><span class="line">                num_anchors, pre_nms_top_n = _onnx_get_num_anchors_and_pre_nms_top_n(ob, self.pre_nms_top_n())</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                num_anchors = ob.shape[<span class="number">1</span>]</span><br><span class="line">                pre_nms_top_n = <span class="built_in">min</span>(self.pre_nms_top_n(), num_anchors)</span><br><span class="line">            _, top_n_idx = ob.topk(pre_nms_top_n, dim=<span class="number">1</span>)</span><br><span class="line">            r.append(top_n_idx + offset)</span><br><span class="line">            offset += num_anchors</span><br><span class="line">        <span class="keyword">return</span> torch.cat(r, dim=<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">filter_proposals</span>(<span class="params">self, proposals, objectness, image_shapes, num_anchors_per_level</span>):</span><br><span class="line">        <span class="comment"># type: (Tensor, Tensor, <span class="type">List</span>[<span class="type">Tuple</span>[<span class="built_in">int</span>, <span class="built_in">int</span>]], <span class="type">List</span>[<span class="built_in">int</span>]) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        num_images = proposals.shape[<span class="number">0</span>]</span><br><span class="line">        device = proposals.device</span><br><span class="line">        <span class="comment"># do not backprop throught objectness</span></span><br><span class="line">        objectness = objectness.detach()</span><br><span class="line">        objectness = objectness.reshape(num_images, -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">        levels = [</span><br><span class="line">            torch.full((n,), idx, dtype=torch.int64, device=device)</span><br><span class="line">            <span class="keyword">for</span> idx, n <span class="keyword">in</span> <span class="built_in">enumerate</span>(num_anchors_per_level)</span><br><span class="line">        ]</span><br><span class="line">        levels = torch.cat(levels, <span class="number">0</span>)</span><br><span class="line">        levels = levels.reshape(<span class="number">1</span>, -<span class="number">1</span>).expand_as(objectness)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># select top_n boxes independently per level before applying nms</span></span><br><span class="line">        top_n_idx = self._get_top_n_idx(objectness, num_anchors_per_level)</span><br><span class="line"></span><br><span class="line">        image_range = torch.arange(num_images, device=device)</span><br><span class="line">        batch_idx = image_range[:, <span class="literal">None</span>]</span><br><span class="line"></span><br><span class="line">        objectness = objectness[batch_idx, top_n_idx]</span><br><span class="line">        levels = levels[batch_idx, top_n_idx]</span><br><span class="line">        proposals = proposals[batch_idx, top_n_idx]</span><br><span class="line"></span><br><span class="line">        final_boxes = []</span><br><span class="line">        final_scores = []</span><br><span class="line">        <span class="keyword">for</span> boxes, scores, lvl, img_shape <span class="keyword">in</span> <span class="built_in">zip</span>(proposals, objectness, levels, image_shapes):</span><br><span class="line">            boxes = box_ops.clip_boxes_to_image(boxes, img_shape)</span><br><span class="line">            keep = box_ops.remove_small_boxes(boxes, self.min_size)</span><br><span class="line">            boxes, scores, lvl = boxes[keep], scores[keep], lvl[keep]</span><br><span class="line">            <span class="comment"># non-maximum suppression, independently done per level</span></span><br><span class="line">            keep = box_ops.batched_nms(boxes, scores, lvl, self.nms_thresh)</span><br><span class="line">            <span class="comment"># keep only topk scoring predictions</span></span><br><span class="line">            keep = keep[:self.post_nms_top_n()]</span><br><span class="line">            boxes, scores = boxes[keep], scores[keep]</span><br><span class="line">            final_boxes.append(boxes)</span><br><span class="line">            final_scores.append(scores)</span><br><span class="line">        <span class="keyword">return</span> final_boxes, final_scores</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">compute_loss</span>(<span class="params">self, objectness, pred_bbox_deltas, labels, regression_targets</span>):</span><br><span class="line">        <span class="comment"># type: (Tensor, Tensor, <span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]) -&gt; <span class="type">Tuple</span>[Tensor, Tensor]</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            objectness (Tensor)</span></span><br><span class="line"><span class="string">            pred_bbox_deltas (Tensor)</span></span><br><span class="line"><span class="string">            labels (List[Tensor])</span></span><br><span class="line"><span class="string">            regression_targets (List[Tensor])</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Returns:</span></span><br><span class="line"><span class="string">            objectness_loss (Tensor)</span></span><br><span class="line"><span class="string">            box_loss (Tensor)</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        sampled_pos_inds, sampled_neg_inds = self.fg_bg_sampler(labels)</span><br><span class="line">        sampled_pos_inds = torch.where(torch.cat(sampled_pos_inds, dim=<span class="number">0</span>))[<span class="number">0</span>]</span><br><span class="line">        sampled_neg_inds = torch.where(torch.cat(sampled_neg_inds, dim=<span class="number">0</span>))[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">        sampled_inds = torch.cat([sampled_pos_inds, sampled_neg_inds], dim=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">        objectness = objectness.flatten()</span><br><span class="line"></span><br><span class="line">        labels = torch.cat(labels, dim=<span class="number">0</span>)</span><br><span class="line">        regression_targets = torch.cat(regression_targets, dim=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">        box_loss = det_utils.smooth_l1_loss(</span><br><span class="line">            pred_bbox_deltas[sampled_pos_inds],</span><br><span class="line">            regression_targets[sampled_pos_inds],</span><br><span class="line">            beta=<span class="number">1</span> / <span class="number">9</span>,</span><br><span class="line">            size_average=<span class="literal">False</span>,</span><br><span class="line">        ) / (sampled_inds.numel())</span><br><span class="line"></span><br><span class="line">        objectness_loss = F.binary_cross_entropy_with_logits(</span><br><span class="line">            objectness[sampled_inds], labels[sampled_inds]</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> objectness_loss, box_loss</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                images,       <span class="comment"># type: ImageList</span></span></span><br><span class="line"><span class="params">                features,     <span class="comment"># type: Dict[str, Tensor]</span></span></span><br><span class="line"><span class="params">                targets=<span class="literal">None</span>  <span class="comment"># type: Optional[List[Dict[str, Tensor]]]</span></span></span><br><span class="line"><span class="params">                </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            images (ImageList): images for which we want to compute the predictions</span></span><br><span class="line"><span class="string">            features (OrderedDict[Tensor]): features computed from the images that are</span></span><br><span class="line"><span class="string">                used for computing the predictions. Each tensor in the list</span></span><br><span class="line"><span class="string">                correspond to different feature levels</span></span><br><span class="line"><span class="string">            targets (List[Dict[Tensor]]): ground-truth boxes present in the image (optional).</span></span><br><span class="line"><span class="string">                If provided, each element in the dict should contain a field `boxes`,</span></span><br><span class="line"><span class="string">                with the locations of the ground-truth boxes.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Returns:</span></span><br><span class="line"><span class="string">            boxes (List[Tensor]): the predicted boxes from the RPN, one Tensor per</span></span><br><span class="line"><span class="string">                image.</span></span><br><span class="line"><span class="string">            losses (Dict[Tensor]): the losses for the model during training. During</span></span><br><span class="line"><span class="string">                testing, it is an empty dict.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="comment"># RPN uses all feature maps that are available</span></span><br><span class="line">        features = <span class="built_in">list</span>(features.values())</span><br><span class="line">        objectness, pred_bbox_deltas = self.head(features)</span><br><span class="line">        anchors = self.anchor_generator(images, features)</span><br><span class="line"></span><br><span class="line">        num_images = <span class="built_in">len</span>(anchors)</span><br><span class="line">        num_anchors_per_level_shape_tensors = [o[<span class="number">0</span>].shape <span class="keyword">for</span> o <span class="keyword">in</span> objectness]</span><br><span class="line">        num_anchors_per_level = [s[<span class="number">0</span>] * s[<span class="number">1</span>] * s[<span class="number">2</span>] <span class="keyword">for</span> s <span class="keyword">in</span> num_anchors_per_level_shape_tensors]</span><br><span class="line">        objectness, pred_bbox_deltas = \</span><br><span class="line">            concat_box_prediction_layers(objectness, pred_bbox_deltas)</span><br><span class="line">        <span class="comment"># apply pred_bbox_deltas to anchors to obtain the decoded proposals</span></span><br><span class="line">        <span class="comment"># note that we detach the deltas because Faster R-CNN do not backprop through</span></span><br><span class="line">        <span class="comment"># the proposals</span></span><br><span class="line">        proposals = self.box_coder.decode(pred_bbox_deltas.detach(), anchors)</span><br><span class="line">        proposals = proposals.view(num_images, -<span class="number">1</span>, <span class="number">4</span>)</span><br><span class="line">        boxes, scores = self.filter_proposals(proposals, objectness, images.image_sizes, num_anchors_per_level)</span><br><span class="line"></span><br><span class="line">        losses = &#123;&#125;</span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">assert</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">            labels, matched_gt_boxes = self.assign_targets_to_anchors(anchors, targets)</span><br><span class="line">            regression_targets = self.box_coder.encode(matched_gt_boxes, anchors)</span><br><span class="line">            loss_objectness, loss_rpn_box_reg = self.compute_loss(</span><br><span class="line">                objectness, pred_bbox_deltas, labels, regression_targets)</span><br><span class="line">            losses = &#123;</span><br><span class="line">                <span class="string">&quot;loss_objectness&quot;</span>: loss_objectness,</span><br><span class="line">                <span class="string">&quot;loss_rpn_box_reg&quot;</span>: loss_rpn_box_reg,</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">return</span> boxes, losses</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>主要看<code>__forward__</code>中的计算流，RPN的完整过程</p><ol type="1"><li>把输入的特征features输入到RPNHead（<code>self.head</code>）中，输出object/non-object分类分值（<code>objectness</code>）和bbox回归数值（<code>pred_bbox_deltas</code>）；</li><li><code>self.anchor_generator</code>为当前输入的图像和featuremap生成<code>anchors</code>；</li><li><code>self.box_coder.decode</code>把bbox回归数值<code>pred_bbox_deltas</code>算到锚框<code>anchors</code>上，得到预测出的候选框<code>proposals</code>；</li><li>计算出的<code>proposals</code>可能很多且相互密集重叠，那么就通过<code>self.filter_proposals</code>做一遍过滤，输出候选框<code>proposals</code>和与之对应的分值<code>scores</code>；</li><li>如果是训练时，当然在RPN阶段需要根据预测出的<code>proposals</code>与候选框真值之间的误差来计算损失。</li></ol><h5 id="anchorgenerator">AnchorGenerator</h5><p><code>AnchorGenerator</code>在<code>torchvision.models.detection.anchor_utils</code>中实现，其作用是根据预定义的anchor的sizes和aspect_ratios，针对图像到featuremap的尺寸比例，计算feature map对应的anchors。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">AnchorGenerator</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Module that generates anchors for a set of feature maps and</span></span><br><span class="line"><span class="string">    image sizes.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    The module support computing anchors at multiple sizes and aspect ratios</span></span><br><span class="line"><span class="string">    per feature map. This module assumes aspect ratio = height / width for</span></span><br><span class="line"><span class="string">    each anchor.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    sizes and aspect_ratios should have the same number of elements, and it should</span></span><br><span class="line"><span class="string">    correspond to the number of feature maps.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    sizes[i] and aspect_ratios[i] can have an arbitrary number of elements,</span></span><br><span class="line"><span class="string">    and AnchorGenerator will output a set of sizes[i] * aspect_ratios[i] anchors</span></span><br><span class="line"><span class="string">    per spatial location for feature map i.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        sizes (Tuple[Tuple[int]]):</span></span><br><span class="line"><span class="string">        aspect_ratios (Tuple[Tuple[float]]):</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    __annotations__ = &#123;</span><br><span class="line">        <span class="string">&quot;cell_anchors&quot;</span>: <span class="type">Optional</span>[<span class="type">List</span>[torch.Tensor]],</span><br><span class="line">        <span class="string">&quot;_cache&quot;</span>: <span class="type">Dict</span>[<span class="built_in">str</span>, <span class="type">List</span>[torch.Tensor]]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        sizes=(<span class="params">(<span class="params"><span class="number">128</span>, <span class="number">256</span>, <span class="number">512</span></span>),</span>),</span></span><br><span class="line"><span class="params">        aspect_ratios=(<span class="params">(<span class="params"><span class="number">0.5</span>, <span class="number">1.0</span>, <span class="number">2.0</span></span>),</span>),</span></span><br><span class="line"><span class="params">    </span>):</span><br><span class="line">        <span class="built_in">super</span>(AnchorGenerator, self).__init__()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">isinstance</span>(sizes[<span class="number">0</span>], (<span class="built_in">list</span>, <span class="built_in">tuple</span>)):</span><br><span class="line">            <span class="comment"># TODO change this</span></span><br><span class="line">            sizes = <span class="built_in">tuple</span>((s,) <span class="keyword">for</span> s <span class="keyword">in</span> sizes)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">isinstance</span>(aspect_ratios[<span class="number">0</span>], (<span class="built_in">list</span>, <span class="built_in">tuple</span>)):</span><br><span class="line">            aspect_ratios = (aspect_ratios,) * <span class="built_in">len</span>(sizes)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">len</span>(sizes) == <span class="built_in">len</span>(aspect_ratios)</span><br><span class="line"></span><br><span class="line">        self.sizes = sizes</span><br><span class="line">        self.aspect_ratios = aspect_ratios</span><br><span class="line">        self.cell_anchors = <span class="literal">None</span></span><br><span class="line">        self._cache = &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># <span class="doctag">TODO:</span> https://github.com/pytorch/pytorch/issues/26792</span></span><br><span class="line">    <span class="comment"># For every (aspect_ratios, scales) combination, output a zero-centered anchor with those values.</span></span><br><span class="line">    <span class="comment"># (scales, aspect_ratios) are usually an element of zip(self.scales, self.aspect_ratios)</span></span><br><span class="line">    <span class="comment"># This method assumes aspect ratio = height / width for an anchor.</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">generate_anchors</span>(<span class="params">self, scales, aspect_ratios, dtype=torch.float32, device=<span class="string">&quot;cpu&quot;</span></span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[<span class="built_in">int</span>], <span class="type">List</span>[<span class="built_in">float</span>], <span class="built_in">int</span>, Device) -&gt; Tensor  # noqa: F821</span></span><br><span class="line">        scales = torch.as_tensor(scales, dtype=dtype, device=device)</span><br><span class="line">        aspect_ratios = torch.as_tensor(aspect_ratios, dtype=dtype, device=device)</span><br><span class="line">        h_ratios = torch.sqrt(aspect_ratios)</span><br><span class="line">        w_ratios = <span class="number">1</span> / h_ratios</span><br><span class="line"></span><br><span class="line">        ws = (w_ratios[:, <span class="literal">None</span>] * scales[<span class="literal">None</span>, :]).view(-<span class="number">1</span>)</span><br><span class="line">        hs = (h_ratios[:, <span class="literal">None</span>] * scales[<span class="literal">None</span>, :]).view(-<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">        base_anchors = torch.stack([-ws, -hs, ws, hs], dim=<span class="number">1</span>) / <span class="number">2</span></span><br><span class="line">        <span class="keyword">return</span> base_anchors.<span class="built_in">round</span>()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">set_cell_anchors</span>(<span class="params">self, dtype, device</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="built_in">int</span>, Device) -&gt; <span class="literal">None</span>  # noqa: F821</span></span><br><span class="line">        <span class="keyword">if</span> self.cell_anchors <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            cell_anchors = self.cell_anchors</span><br><span class="line">            <span class="keyword">assert</span> cell_anchors <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">            <span class="comment"># suppose that all anchors have the same device</span></span><br><span class="line">            <span class="comment"># which is a valid assumption in the current state of the codebase</span></span><br><span class="line">            <span class="keyword">if</span> cell_anchors[<span class="number">0</span>].device == device:</span><br><span class="line">                <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">        cell_anchors = [</span><br><span class="line">            self.generate_anchors(</span><br><span class="line">                sizes,</span><br><span class="line">                aspect_ratios,</span><br><span class="line">                dtype,</span><br><span class="line">                device</span><br><span class="line">            )</span><br><span class="line">            <span class="keyword">for</span> sizes, aspect_ratios <span class="keyword">in</span> <span class="built_in">zip</span>(self.sizes, self.aspect_ratios)</span><br><span class="line">        ]</span><br><span class="line">        self.cell_anchors = cell_anchors</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">num_anchors_per_location</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> [<span class="built_in">len</span>(s) * <span class="built_in">len</span>(a) <span class="keyword">for</span> s, a <span class="keyword">in</span> <span class="built_in">zip</span>(self.sizes, self.aspect_ratios)]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># For every combination of (a, (g, s), i) in (self.cell_anchors, zip(grid_sizes, strides), 0:2),</span></span><br><span class="line">    <span class="comment"># output g[i] anchors that are s[i] distance apart in direction i, with the same dimensions as a.</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">grid_anchors</span>(<span class="params">self, grid_sizes, strides</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[<span class="type">List</span>[<span class="built_in">int</span>]], <span class="type">List</span>[<span class="type">List</span>[Tensor]]) -&gt; <span class="type">List</span>[Tensor]</span></span><br><span class="line">        anchors = []</span><br><span class="line">        cell_anchors = self.cell_anchors</span><br><span class="line">        <span class="keyword">assert</span> cell_anchors <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">len</span>(grid_sizes) == <span class="built_in">len</span>(strides) == <span class="built_in">len</span>(cell_anchors)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> size, stride, base_anchors <span class="keyword">in</span> <span class="built_in">zip</span>(</span><br><span class="line">            grid_sizes, strides, cell_anchors</span><br><span class="line">        ):</span><br><span class="line">            grid_height, grid_width = size</span><br><span class="line">            stride_height, stride_width = stride</span><br><span class="line">            device = base_anchors.device</span><br><span class="line"></span><br><span class="line">            <span class="comment"># For output anchor, compute [x_center, y_center, x_center, y_center]</span></span><br><span class="line">            shifts_x = torch.arange(</span><br><span class="line">                <span class="number">0</span>, grid_width, dtype=torch.float32, device=device</span><br><span class="line">            ) * stride_width</span><br><span class="line">            shifts_y = torch.arange(</span><br><span class="line">                <span class="number">0</span>, grid_height, dtype=torch.float32, device=device</span><br><span class="line">            ) * stride_height</span><br><span class="line">            shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)</span><br><span class="line">            shift_x = shift_x.reshape(-<span class="number">1</span>)</span><br><span class="line">            shift_y = shift_y.reshape(-<span class="number">1</span>)</span><br><span class="line">            shifts = torch.stack((shift_x, shift_y, shift_x, shift_y), dim=<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># For every (base anchor, output anchor) pair,</span></span><br><span class="line">            <span class="comment"># offset each zero-centered base anchor by the center of the output anchor.</span></span><br><span class="line">            anchors.append(</span><br><span class="line">                (shifts.view(-<span class="number">1</span>, <span class="number">1</span>, <span class="number">4</span>) + base_anchors.view(<span class="number">1</span>, -<span class="number">1</span>, <span class="number">4</span>)).reshape(-<span class="number">1</span>, <span class="number">4</span>)</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> anchors</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">cached_grid_anchors</span>(<span class="params">self, grid_sizes, strides</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[<span class="type">List</span>[<span class="built_in">int</span>]], <span class="type">List</span>[<span class="type">List</span>[Tensor]]) -&gt; <span class="type">List</span>[Tensor]</span></span><br><span class="line">        key = <span class="built_in">str</span>(grid_sizes) + <span class="built_in">str</span>(strides)</span><br><span class="line">        <span class="keyword">if</span> key <span class="keyword">in</span> self._cache:</span><br><span class="line">            <span class="keyword">return</span> self._cache[key]</span><br><span class="line">        anchors = self.grid_anchors(grid_sizes, strides)</span><br><span class="line">        self._cache[key] = anchors</span><br><span class="line">        <span class="keyword">return</span> anchors</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, image_list, feature_maps</span>):</span><br><span class="line">        <span class="comment"># type: (ImageList, <span class="type">List</span>[Tensor]) -&gt; <span class="type">List</span>[Tensor]</span></span><br><span class="line">        grid_sizes = <span class="built_in">list</span>([feature_map.shape[-<span class="number">2</span>:] <span class="keyword">for</span> feature_map <span class="keyword">in</span> feature_maps])</span><br><span class="line">        image_size = image_list.tensors.shape[-<span class="number">2</span>:]</span><br><span class="line">        dtype, device = feature_maps[<span class="number">0</span>].dtype, feature_maps[<span class="number">0</span>].device</span><br><span class="line">        strides = [[torch.tensor(image_size[<span class="number">0</span>] // g[<span class="number">0</span>], dtype=torch.int64, device=device),</span><br><span class="line">                    torch.tensor(image_size[<span class="number">1</span>] // g[<span class="number">1</span>], dtype=torch.int64, device=device)] <span class="keyword">for</span> g <span class="keyword">in</span> grid_sizes]</span><br><span class="line">        self.set_cell_anchors(dtype, device)</span><br><span class="line">        anchors_over_all_feature_maps = self.cached_grid_anchors(grid_sizes, strides)</span><br><span class="line">        anchors = torch.jit.annotate(<span class="type">List</span>[<span class="type">List</span>[torch.Tensor]], [])</span><br><span class="line">        <span class="keyword">for</span> i, (image_height, image_width) <span class="keyword">in</span> <span class="built_in">enumerate</span>(image_list.image_sizes):</span><br><span class="line">            anchors_in_image = []</span><br><span class="line">            <span class="keyword">for</span> anchors_per_feature_map <span class="keyword">in</span> anchors_over_all_feature_maps:</span><br><span class="line">                anchors_in_image.append(anchors_per_feature_map)</span><br><span class="line">            anchors.append(anchors_in_image)</span><br><span class="line">        anchors = [torch.cat(anchors_per_image) <span class="keyword">for</span> anchors_per_image <span class="keyword">in</span> anchors]</span><br><span class="line">        <span class="comment"># Clear the cache in case that memory leaks.</span></span><br><span class="line">        self._cache.clear()</span><br><span class="line">        <span class="keyword">return</span> anchors</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这部分代码也有点长，主要原理也还是看<code>__forward__</code>计算流即可，这里面有一系列预备的计算，随后就是两层的for循环，表示：每一个图片可以传入<spanclass="math inline">\(n\)</span>个（尺寸不同的）featuremap，每一个feature map上都有<spanclass="math inline">\(k_i\)</span>个anchor，那么每个图片就有<spanclass="math inline">\(K = \sum_{i=1}^{n}k_i\)</span>个anchors。其中，每个滑窗位置上有<spanclass="math inline">\(A\)</span>个anchor，第<spanclass="math inline">\(i\)</span>个feature map上有<spanclass="math inline">\(L_i\)</span>个滑窗位置，则该层feature map上有<spanclass="math inline">\(k_i = A L_i\)</span>个anchors。</p><p>两层循环，外层遍历images，内层遍历featuremaps，由此输出所有图片的feature map上的anchors。</p><p>在具体实现中，AnchorGenerator：</p><ol type="1"><li><code>self.set_cell_anchors</code>函数负责为每一层featuremap生成<code>self.cell_anchors</code>，这个CellAnchors的尺寸基于的是输入图片tensor的尺寸，；</li><li><code>self.cached_grid_anchors</code>函数内会进一步调用<code>self.grid_anchors</code>函数，该函数负责根据featuremap的网格尺寸以及该featuremap相较于输入图片tensor的步长，计算出<code>anchors_over_all_feature_maps</code>，它的尺寸则是基于输入图片tensor的尺寸。</li><li>双重for循环，输入<spanclass="math inline">\(N\)</span>个图片，就相应地将anchors复制出<spanclass="math inline">\(N\)</span>份。</li><li>最后<code>torch.cat</code>拉平每个图片上不同feature map上的所有<spanclass="math inline">\(K\)</span>个anchors，形成一个长度为<spanclass="math inline">\(N\)</span>的list，每个元素是<spanclass="math inline">\(K \times 4\)</span>的anchors张量。</li></ol><p>对应到例子，<code>torchvision</code>实现默认为：</p><ol type="1"><li>3种aspect ratio，分别为0.5, 1.0, 2.0；</li><li>每层feature map对应1个scale，5层feature map分别为16, 32, 64, 128,256。</li></ol><p>因此，<code>cell_anchors</code>中，每层feature map都是3个anchorcells：</p><ol type="1"><li><code>'0'</code>: shape[3, 4]</li><li><code>'1'</code>: shape[3, 4]</li><li><code>'2'</code>: shape[3, 4]</li><li><code>'3'</code>: shape[3, 4]</li><li><code>'4'</code>: shape[3, 4]</li></ol><p>结合例子来算，把<code>cell_anchors</code>算到输入图像张量的每一个滑窗位置上，就可以算出所有位置上的所有<code>anchors_over_all_feature_maps</code>：</p><ol type="1"><li><code>'0'</code>: shape[208896, 4]，<spanclass="math inline">\(208896 = 256 \times 272 \times 3\)</span>；</li><li><code>'1'</code>: shape[52224, 4]，<span class="math inline">\(52224= 128 \times 136 \times 3\)</span>；</li><li><code>'2'</code>: shape[13056, 4]，<span class="math inline">\(13056= 64 \times 68 \times 3\)</span>；</li><li><code>'3'</code>: shape[3264, 4]，<span class="math inline">\(3264 =32 \times 34 \times 3\)</span>；</li><li><code>'4'</code>: shape[816, 4]，<span class="math inline">\(816 =16 \times 17 \times 3\)</span>；</li></ol><p>最后返回的<code>anchors</code>会为输入的每个图片复制一份，并通过<code>torch.cat</code>拉平：</p><ol type="1"><li>shape[278256, 4], <span class="math inline">\(278256 = 208896 +52224 + 13056 + 3264 + 816\)</span>；</li><li>shape[278256, 4], <span class="math inline">\(278256 = 208896 +52224 + 13056 + 3264 + 816\)</span>；</li></ol><h5 id="rpnhead">RPNHead</h5><p><code>RPNHead</code>在<code>torchvision.models.detection.rpn</code>包中实现。RPNHead被用于以滑窗的形式在特征提取出的featuremap上滑动并计算每个anchor的bbox回归值和object/non-object二分类。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">class</span> <span class="title class_">RPNHead</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Adds a simple RPN Head with classification and regression heads</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        in_channels (int): number of channels of the input feature</span></span><br><span class="line"><span class="string">        num_anchors (int): number of anchors to be predicted</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, in_channels, num_anchors</span>):</span><br><span class="line">        <span class="built_in">super</span>(RPNHead, self).__init__()</span><br><span class="line">        self.conv = nn.Conv2d(</span><br><span class="line">            in_channels, in_channels, kernel_size=<span class="number">3</span>, stride=<span class="number">1</span>, padding=<span class="number">1</span></span><br><span class="line">        )</span><br><span class="line">        self.cls_logits = nn.Conv2d(in_channels, num_anchors, kernel_size=<span class="number">1</span>, stride=<span class="number">1</span>)</span><br><span class="line">        self.bbox_pred = nn.Conv2d(</span><br><span class="line">            in_channels, num_anchors * <span class="number">4</span>, kernel_size=<span class="number">1</span>, stride=<span class="number">1</span></span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> layer <span class="keyword">in</span> self.children():</span><br><span class="line">            torch.nn.init.normal_(layer.weight, std=<span class="number">0.01</span>)</span><br><span class="line">            torch.nn.init.constant_(layer.bias, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor]) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        logits = []</span><br><span class="line">        bbox_reg = []</span><br><span class="line">        <span class="keyword">for</span> feature <span class="keyword">in</span> x:</span><br><span class="line">            t = F.relu(self.conv(feature))</span><br><span class="line">            logits.append(self.cls_logits(t))</span><br><span class="line">            bbox_reg.append(self.bbox_pred(t))</span><br><span class="line">        <span class="keyword">return</span> logits, bbox_reg</span><br></pre></td></tr></table></figure><p>可以看到，RPNHead的结构不复杂，就是三个卷积：</p><ol type="1"><li><code>self.conv</code>：3×3卷积，对输入的featuremap做卷积处理；</li><li><code>self.cls_logits</code>：1×1卷积，对处理后的feature map <spanclass="math inline">\(t\)</span>做卷积，取得object/non-object的分类数值；</li><li><code>self.bbox_reg</code>：1×1卷积，对处理后的feature map <spanclass="math inline">\(t\)</span>做卷积，取得bbox坐标值的回归数值。</li></ol><p>在<code>forward</code>前向传播计算的时候，输入的x是一个<code>List[Tensor]</code>，即FPN的输出。值得注意的是，<code>for</code>循环遍历的并不是每一张图片，而是FPN输出的每一层特征。</p><p>在本例中，RPNHead的两个卷积分支输出了两个<code>List[Tensor]</code>：</p><p><code>logits</code> (<code>objectness</code>):</p><ol type="1"><li><code>'0'</code>: shape[2, 3, 252, 272]；</li><li><code>'1'</code>: shape[2, 3, 128, 136]；</li><li><code>'2'</code>: shape[2, 3, 64, 68]；</li><li><code>'3'</code>: shape[2, 3, 32, 34]；</li><li><code>'4'</code>: shape[2, 3, 16, 17]；</li></ol><p><code>bbox_regs</code> (<code>pred_bbox_deltas</code>):</p><ol type="1"><li><code>'0'</code>: shape[2, 12, 252, 272]；</li><li><code>'1'</code>: shape[2, 12, 128, 136]；</li><li><code>'2'</code>: shape[2, 12, 64, 68]；</li><li><code>'3'</code>: shape[2, 12, 32, 34]；</li><li><code>'4'</code>: shape[2, 12, 16, 17]；</li></ol><p>因为每个滑窗位置对应三种<code>ratios</code>，即3个anchors，所以<code>logits</code>是3个值，而<code>bbox_regs</code>因为坐标乘4，所以是12个值。</p><h5 id="boxcoder">BoxCoder</h5><p><code>BoxCoder</code>在<code>torchvision.models.detection._utils</code>中实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">BoxCoder</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    This class encodes and decodes a set of bounding boxes into</span></span><br><span class="line"><span class="string">    the representation used for training the regressors.</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, weights, bbox_xform_clip=math.log(<span class="params"><span class="number">1000.</span> / <span class="number">16</span></span>)</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">Tuple</span>[<span class="built_in">float</span>, <span class="built_in">float</span>, <span class="built_in">float</span>, <span class="built_in">float</span>], <span class="built_in">float</span>) -&gt; <span class="literal">None</span></span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            weights (4-element tuple)</span></span><br><span class="line"><span class="string">            bbox_xform_clip (float)</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        self.weights = weights</span><br><span class="line">        self.bbox_xform_clip = bbox_xform_clip</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">encode</span>(<span class="params">self, reference_boxes, proposals</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]) -&gt; <span class="type">List</span>[Tensor]</span></span><br><span class="line">        boxes_per_image = [<span class="built_in">len</span>(b) <span class="keyword">for</span> b <span class="keyword">in</span> reference_boxes]</span><br><span class="line">        reference_boxes = torch.cat(reference_boxes, dim=<span class="number">0</span>)</span><br><span class="line">        proposals = torch.cat(proposals, dim=<span class="number">0</span>)</span><br><span class="line">        targets = self.encode_single(reference_boxes, proposals)</span><br><span class="line">        <span class="keyword">return</span> targets.split(boxes_per_image, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">encode_single</span>(<span class="params">self, reference_boxes, proposals</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Encode a set of proposals with respect to some</span></span><br><span class="line"><span class="string">        reference boxes</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            reference_boxes (Tensor): reference boxes</span></span><br><span class="line"><span class="string">            proposals (Tensor): boxes to be encoded</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        dtype = reference_boxes.dtype</span><br><span class="line">        device = reference_boxes.device</span><br><span class="line">        weights = torch.as_tensor(self.weights, dtype=dtype, device=device)</span><br><span class="line">        targets = encode_boxes(reference_boxes, proposals, weights)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> targets</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">decode</span>(<span class="params">self, rel_codes, boxes</span>):</span><br><span class="line">        <span class="comment"># type: (Tensor, <span class="type">List</span>[Tensor]) -&gt; Tensor</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">isinstance</span>(boxes, (<span class="built_in">list</span>, <span class="built_in">tuple</span>))</span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">isinstance</span>(rel_codes, torch.Tensor)</span><br><span class="line">        boxes_per_image = [b.size(<span class="number">0</span>) <span class="keyword">for</span> b <span class="keyword">in</span> boxes]</span><br><span class="line">        concat_boxes = torch.cat(boxes, dim=<span class="number">0</span>)</span><br><span class="line">        box_sum = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> val <span class="keyword">in</span> boxes_per_image:</span><br><span class="line">            box_sum += val</span><br><span class="line">        pred_boxes = self.decode_single(</span><br><span class="line">            rel_codes.reshape(box_sum, -<span class="number">1</span>), concat_boxes</span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">return</span> pred_boxes.reshape(box_sum, -<span class="number">1</span>, <span class="number">4</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">decode_single</span>(<span class="params">self, rel_codes, boxes</span>):</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        From a set of original boxes and encoded relative box offsets,</span></span><br><span class="line"><span class="string">        get the decoded boxes.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            rel_codes (Tensor): encoded boxes</span></span><br><span class="line"><span class="string">            boxes (Tensor): reference boxes.</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">        boxes = boxes.to(rel_codes.dtype)</span><br><span class="line"></span><br><span class="line">        widths = boxes[:, <span class="number">2</span>] - boxes[:, <span class="number">0</span>]</span><br><span class="line">        heights = boxes[:, <span class="number">3</span>] - boxes[:, <span class="number">1</span>]</span><br><span class="line">        ctr_x = boxes[:, <span class="number">0</span>] + <span class="number">0.5</span> * widths</span><br><span class="line">        ctr_y = boxes[:, <span class="number">1</span>] + <span class="number">0.5</span> * heights</span><br><span class="line"></span><br><span class="line">        wx, wy, ww, wh = self.weights</span><br><span class="line">        dx = rel_codes[:, <span class="number">0</span>::<span class="number">4</span>] / wx</span><br><span class="line">        dy = rel_codes[:, <span class="number">1</span>::<span class="number">4</span>] / wy</span><br><span class="line">        dw = rel_codes[:, <span class="number">2</span>::<span class="number">4</span>] / ww</span><br><span class="line">        dh = rel_codes[:, <span class="number">3</span>::<span class="number">4</span>] / wh</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Prevent sending too large values into torch.exp()</span></span><br><span class="line">        dw = torch.clamp(dw, <span class="built_in">max</span>=self.bbox_xform_clip)</span><br><span class="line">        dh = torch.clamp(dh, <span class="built_in">max</span>=self.bbox_xform_clip)</span><br><span class="line"></span><br><span class="line">        pred_ctr_x = dx * widths[:, <span class="literal">None</span>] + ctr_x[:, <span class="literal">None</span>]</span><br><span class="line">        pred_ctr_y = dy * heights[:, <span class="literal">None</span>] + ctr_y[:, <span class="literal">None</span>]</span><br><span class="line">        pred_w = torch.exp(dw) * widths[:, <span class="literal">None</span>]</span><br><span class="line">        pred_h = torch.exp(dh) * heights[:, <span class="literal">None</span>]</span><br><span class="line"></span><br><span class="line">        pred_boxes1 = pred_ctr_x - torch.tensor(<span class="number">0.5</span>, dtype=pred_ctr_x.dtype, device=pred_w.device) * pred_w</span><br><span class="line">        pred_boxes2 = pred_ctr_y - torch.tensor(<span class="number">0.5</span>, dtype=pred_ctr_y.dtype, device=pred_h.device) * pred_h</span><br><span class="line">        pred_boxes3 = pred_ctr_x + torch.tensor(<span class="number">0.5</span>, dtype=pred_ctr_x.dtype, device=pred_w.device) * pred_w</span><br><span class="line">        pred_boxes4 = pred_ctr_y + torch.tensor(<span class="number">0.5</span>, dtype=pred_ctr_y.dtype, device=pred_h.device) * pred_h</span><br><span class="line">        pred_boxes = torch.stack((pred_boxes1, pred_boxes2, pred_boxes3, pred_boxes4), dim=<span class="number">2</span>).flatten(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">return</span> pred_boxes</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>RPN中<code>self.box_coder</code>使用BoxCoder作为bbox的编解码器：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">proposals = self.box_coder.decode(pred_bbox_deltas.detach(), anchors)</span><br></pre></td></tr></table></figure><p>通过BoxCoder实例，将RPNHead回归出的<code>pred_bbox_deltas</code>与RPN的锚框<code>anchors</code>做解码计算，把回归出的偏移值加到基准anchors位置上，解码输出候选框<code>proposals</code>。</p><p>在本例中，RPN的<code>forward</code>对解码出的原始<code>proposals</code>做了维度整理<code>proposals = proposals.view(num_images, -1, 4)</code>，得到的<code>proposals</code>是：</p><ul><li>shape[2, 278256, 4]</li></ul><h5 id="filter_proposals">filter_proposals</h5><p><code>filter_proposals</code>是一个对RPNHead生成的候选框<code>proposals</code>的过滤操作，在RPN类<code>RegionProposalNetwork</code>中作为成员函数实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">class</span> <span class="title class_">RegionProposalNetwork</span>(torch.nn.Module):</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">filter_proposals</span>(<span class="params">self, proposals, objectness, image_shapes, num_anchors_per_level</span>):</span><br><span class="line">        <span class="comment"># type: (Tensor, Tensor, <span class="type">List</span>[<span class="type">Tuple</span>[<span class="built_in">int</span>, <span class="built_in">int</span>]], <span class="type">List</span>[<span class="built_in">int</span>]) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        num_images = proposals.shape[<span class="number">0</span>]</span><br><span class="line">        device = proposals.device</span><br><span class="line">        <span class="comment"># do not backprop throught objectness</span></span><br><span class="line">        objectness = objectness.detach()</span><br><span class="line">        objectness = objectness.reshape(num_images, -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">        levels = [</span><br><span class="line">            torch.full((n,), idx, dtype=torch.int64, device=device)</span><br><span class="line">            <span class="keyword">for</span> idx, n <span class="keyword">in</span> <span class="built_in">enumerate</span>(num_anchors_per_level)</span><br><span class="line">        ]</span><br><span class="line">        levels = torch.cat(levels, <span class="number">0</span>)</span><br><span class="line">        levels = levels.reshape(<span class="number">1</span>, -<span class="number">1</span>).expand_as(objectness)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># select top_n boxes independently per level before applying nms</span></span><br><span class="line">        top_n_idx = self._get_top_n_idx(objectness, num_anchors_per_level)</span><br><span class="line"></span><br><span class="line">        image_range = torch.arange(num_images, device=device)</span><br><span class="line">        batch_idx = image_range[:, <span class="literal">None</span>]</span><br><span class="line"></span><br><span class="line">        objectness = objectness[batch_idx, top_n_idx]</span><br><span class="line">        levels = levels[batch_idx, top_n_idx]</span><br><span class="line">        proposals = proposals[batch_idx, top_n_idx]</span><br><span class="line"></span><br><span class="line">        final_boxes = []</span><br><span class="line">        final_scores = []</span><br><span class="line">        <span class="keyword">for</span> boxes, scores, lvl, img_shape <span class="keyword">in</span> <span class="built_in">zip</span>(proposals, objectness, levels, image_shapes):</span><br><span class="line">            boxes = box_ops.clip_boxes_to_image(boxes, img_shape)</span><br><span class="line">            keep = box_ops.remove_small_boxes(boxes, self.min_size)</span><br><span class="line">            boxes, scores, lvl = boxes[keep], scores[keep], lvl[keep]</span><br><span class="line">            <span class="comment"># non-maximum suppression, independently done per level</span></span><br><span class="line">            keep = box_ops.batched_nms(boxes, scores, lvl, self.nms_thresh)</span><br><span class="line">            <span class="comment"># keep only topk scoring predictions</span></span><br><span class="line">            keep = keep[:self.post_nms_top_n()]</span><br><span class="line">            boxes, scores = boxes[keep], scores[keep]</span><br><span class="line">            final_boxes.append(boxes)</span><br><span class="line">            final_scores.append(scores)</span><br><span class="line">        <span class="keyword">return</span> final_boxes, final_scores</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>对<code>proposals</code>的过滤操作分几个阶段实现：</p><ol type="1"><li>首先，根据<code>objectness</code>分值和<code>num_anchors_per_level</code>来在每层选出<code>top_n_idx(pre_nms_top_n)</code>用于在NMS前先筛选一下<code>proposals</code>；</li><li>此后，进入for循环，遍历batch中的每张图片，<ol type="1"><li>先做一些裁边界、去小框的处理；</li><li>然后再做（类间）NMS；</li><li>保留当前图片所有结果的<code>post_nms_top_n</code>的目标作为返回结果。</li></ol></li></ol><p>最后将这么多筛选操作筛选出的<code>final_boxes</code>和<code>final_scores</code>返回（<code>boxes</code>是筛选后的<code>proposals</code>，<code>scores</code>是筛选后的<code>objectness</code>）。</p><p>在本例中，有两张图片，每张图片上有278256个<code>anchors</code>，因此产生278256个<code>proposals</code>和<code>objectness</code>，进过筛选处理后：</p><ol type="1"><li><code>final_boxes</code>：<ol type="1"><li>shape[1000, 4]</li><li>shape[1000, 4]</li></ol></li><li><code>final_scores</code>:<ol type="1"><li>shape 1000</li><li>shape 1000</li></ol></li></ol><p>因为FasterRCNN中默认值<code>rpn_post_nms_top_n_test=1000</code>，所以在eval模式（即test,infer情况）下，例子中的两张图片都各筛选出了top-1000个boxes。</p><h4 id="roiheads">RoIHeads</h4><p><code>RoIHeads</code>在<code>torchvision.models.detection.roi_heads</code>包中实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">RoIHeads</span>(torch.nn.Module):</span><br><span class="line">    __annotations__ = &#123;</span><br><span class="line">        <span class="string">&#x27;box_coder&#x27;</span>: det_utils.BoxCoder,</span><br><span class="line">        <span class="string">&#x27;proposal_matcher&#x27;</span>: det_utils.Matcher,</span><br><span class="line">        <span class="string">&#x27;fg_bg_sampler&#x27;</span>: det_utils.BalancedPositiveNegativeSampler,</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                 box_roi_pool,</span></span><br><span class="line"><span class="params">                 box_head,</span></span><br><span class="line"><span class="params">                 box_predictor,</span></span><br><span class="line"><span class="params">                 <span class="comment"># Faster R-CNN training</span></span></span><br><span class="line"><span class="params">                 fg_iou_thresh, bg_iou_thresh,</span></span><br><span class="line"><span class="params">                 batch_size_per_image, positive_fraction,</span></span><br><span class="line"><span class="params">                 bbox_reg_weights,</span></span><br><span class="line"><span class="params">                 <span class="comment"># Faster R-CNN inference</span></span></span><br><span class="line"><span class="params">                 score_thresh,</span></span><br><span class="line"><span class="params">                 nms_thresh,</span></span><br><span class="line"><span class="params">                 detections_per_img,</span></span><br><span class="line"><span class="params">                 <span class="comment"># Mask</span></span></span><br><span class="line"><span class="params">                 mask_roi_pool=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 mask_head=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 mask_predictor=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 keypoint_roi_pool=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 keypoint_head=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 keypoint_predictor=<span class="literal">None</span>,</span></span><br><span class="line"><span class="params">                 </span>):</span><br><span class="line">        <span class="built_in">super</span>(RoIHeads, self).__init__()</span><br><span class="line"></span><br><span class="line">        self.box_similarity = box_ops.box_iou</span><br><span class="line">        <span class="comment"># assign ground-truth boxes for each proposal</span></span><br><span class="line">        self.proposal_matcher = det_utils.Matcher(</span><br><span class="line">            fg_iou_thresh,</span><br><span class="line">            bg_iou_thresh,</span><br><span class="line">            allow_low_quality_matches=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line">        self.fg_bg_sampler = det_utils.BalancedPositiveNegativeSampler(</span><br><span class="line">            batch_size_per_image,</span><br><span class="line">            positive_fraction)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> bbox_reg_weights <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            bbox_reg_weights = (<span class="number">10.</span>, <span class="number">10.</span>, <span class="number">5.</span>, <span class="number">5.</span>)</span><br><span class="line">        self.box_coder = det_utils.BoxCoder(bbox_reg_weights)</span><br><span class="line"></span><br><span class="line">        self.box_roi_pool = box_roi_pool</span><br><span class="line">        self.box_head = box_head</span><br><span class="line">        self.box_predictor = box_predictor</span><br><span class="line"></span><br><span class="line">        self.score_thresh = score_thresh</span><br><span class="line">        self.nms_thresh = nms_thresh</span><br><span class="line">        self.detections_per_img = detections_per_img</span><br><span class="line"></span><br><span class="line">        self.mask_roi_pool = mask_roi_pool</span><br><span class="line">        self.mask_head = mask_head</span><br><span class="line">        self.mask_predictor = mask_predictor</span><br><span class="line"></span><br><span class="line">        self.keypoint_roi_pool = keypoint_roi_pool</span><br><span class="line">        self.keypoint_head = keypoint_head</span><br><span class="line">        self.keypoint_predictor = keypoint_predictor</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">has_mask</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> self.mask_roi_pool <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">if</span> self.mask_head <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">if</span> self.mask_predictor <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">has_keypoint</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">if</span> self.keypoint_roi_pool <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">if</span> self.keypoint_head <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">if</span> self.keypoint_predictor <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">assign_targets_to_proposals</span>(<span class="params">self, proposals, gt_boxes, gt_labels</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        matched_idxs = []</span><br><span class="line">        labels = []</span><br><span class="line">        <span class="keyword">for</span> proposals_in_image, gt_boxes_in_image, gt_labels_in_image <span class="keyword">in</span> <span class="built_in">zip</span>(proposals, gt_boxes, gt_labels):</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> gt_boxes_in_image.numel() == <span class="number">0</span>:</span><br><span class="line">                <span class="comment"># Background image</span></span><br><span class="line">                device = proposals_in_image.device</span><br><span class="line">                clamped_matched_idxs_in_image = torch.zeros(</span><br><span class="line">                    (proposals_in_image.shape[<span class="number">0</span>],), dtype=torch.int64, device=device</span><br><span class="line">                )</span><br><span class="line">                labels_in_image = torch.zeros(</span><br><span class="line">                    (proposals_in_image.shape[<span class="number">0</span>],), dtype=torch.int64, device=device</span><br><span class="line">                )</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="comment">#  set to self.box_similarity when https://github.com/pytorch/pytorch/issues/27495 lands</span></span><br><span class="line">                match_quality_matrix = box_ops.box_iou(gt_boxes_in_image, proposals_in_image)</span><br><span class="line">                matched_idxs_in_image = self.proposal_matcher(match_quality_matrix)</span><br><span class="line"></span><br><span class="line">                clamped_matched_idxs_in_image = matched_idxs_in_image.clamp(<span class="built_in">min</span>=<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">                labels_in_image = gt_labels_in_image[clamped_matched_idxs_in_image]</span><br><span class="line">                labels_in_image = labels_in_image.to(dtype=torch.int64)</span><br><span class="line"></span><br><span class="line">                <span class="comment"># Label background (below the low threshold)</span></span><br><span class="line">                bg_inds = matched_idxs_in_image == self.proposal_matcher.BELOW_LOW_THRESHOLD</span><br><span class="line">                labels_in_image[bg_inds] = <span class="number">0</span></span><br><span class="line"></span><br><span class="line">                <span class="comment"># Label ignore proposals (between low and high thresholds)</span></span><br><span class="line">                ignore_inds = matched_idxs_in_image == self.proposal_matcher.BETWEEN_THRESHOLDS</span><br><span class="line">                labels_in_image[ignore_inds] = -<span class="number">1</span>  <span class="comment"># -1 is ignored by sampler</span></span><br><span class="line"></span><br><span class="line">            matched_idxs.append(clamped_matched_idxs_in_image)</span><br><span class="line">            labels.append(labels_in_image)</span><br><span class="line">        <span class="keyword">return</span> matched_idxs, labels</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">subsample</span>(<span class="params">self, labels</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor]) -&gt; <span class="type">List</span>[Tensor]</span></span><br><span class="line">        sampled_pos_inds, sampled_neg_inds = self.fg_bg_sampler(labels)</span><br><span class="line">        sampled_inds = []</span><br><span class="line">        <span class="keyword">for</span> img_idx, (pos_inds_img, neg_inds_img) <span class="keyword">in</span> <span class="built_in">enumerate</span>(</span><br><span class="line">            <span class="built_in">zip</span>(sampled_pos_inds, sampled_neg_inds)</span><br><span class="line">        ):</span><br><span class="line">            img_sampled_inds = torch.where(pos_inds_img | neg_inds_img)[<span class="number">0</span>]</span><br><span class="line">            sampled_inds.append(img_sampled_inds)</span><br><span class="line">        <span class="keyword">return</span> sampled_inds</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">add_gt_proposals</span>(<span class="params">self, proposals, gt_boxes</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]) -&gt; <span class="type">List</span>[Tensor]</span></span><br><span class="line">        proposals = [</span><br><span class="line">            torch.cat((proposal, gt_box))</span><br><span class="line">            <span class="keyword">for</span> proposal, gt_box <span class="keyword">in</span> <span class="built_in">zip</span>(proposals, gt_boxes)</span><br><span class="line">        ]</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> proposals</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">check_targets</span>(<span class="params">self, targets</span>):</span><br><span class="line">        <span class="comment"># type: (<span class="type">Optional</span>[<span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]]) -&gt; <span class="literal">None</span></span></span><br><span class="line">        <span class="keyword">assert</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">all</span>([<span class="string">&quot;boxes&quot;</span> <span class="keyword">in</span> t <span class="keyword">for</span> t <span class="keyword">in</span> targets])</span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">all</span>([<span class="string">&quot;labels&quot;</span> <span class="keyword">in</span> t <span class="keyword">for</span> t <span class="keyword">in</span> targets])</span><br><span class="line">        <span class="keyword">if</span> self.has_mask():</span><br><span class="line">            <span class="keyword">assert</span> <span class="built_in">all</span>([<span class="string">&quot;masks&quot;</span> <span class="keyword">in</span> t <span class="keyword">for</span> t <span class="keyword">in</span> targets])</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">select_training_samples</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                                proposals,  <span class="comment"># type: List[Tensor]</span></span></span><br><span class="line"><span class="params">                                targets     <span class="comment"># type: Optional[List[Dict[str, Tensor]]]</span></span></span><br><span class="line"><span class="params">                                </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        self.check_targets(targets)</span><br><span class="line">        <span class="keyword">assert</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">        dtype = proposals[<span class="number">0</span>].dtype</span><br><span class="line">        device = proposals[<span class="number">0</span>].device</span><br><span class="line"></span><br><span class="line">        gt_boxes = [t[<span class="string">&quot;boxes&quot;</span>].to(dtype) <span class="keyword">for</span> t <span class="keyword">in</span> targets]</span><br><span class="line">        gt_labels = [t[<span class="string">&quot;labels&quot;</span>] <span class="keyword">for</span> t <span class="keyword">in</span> targets]</span><br><span class="line"></span><br><span class="line">        <span class="comment"># append ground-truth bboxes to propos</span></span><br><span class="line">        proposals = self.add_gt_proposals(proposals, gt_boxes)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># get matching gt indices for each proposal</span></span><br><span class="line">        matched_idxs, labels = self.assign_targets_to_proposals(proposals, gt_boxes, gt_labels)</span><br><span class="line">        <span class="comment"># sample a fixed proportion of positive-negative proposals</span></span><br><span class="line">        sampled_inds = self.subsample(labels)</span><br><span class="line">        matched_gt_boxes = []</span><br><span class="line">        num_images = <span class="built_in">len</span>(proposals)</span><br><span class="line">        <span class="keyword">for</span> img_id <span class="keyword">in</span> <span class="built_in">range</span>(num_images):</span><br><span class="line">            img_sampled_inds = sampled_inds[img_id]</span><br><span class="line">            proposals[img_id] = proposals[img_id][img_sampled_inds]</span><br><span class="line">            labels[img_id] = labels[img_id][img_sampled_inds]</span><br><span class="line">            matched_idxs[img_id] = matched_idxs[img_id][img_sampled_inds]</span><br><span class="line"></span><br><span class="line">            gt_boxes_in_image = gt_boxes[img_id]</span><br><span class="line">            <span class="keyword">if</span> gt_boxes_in_image.numel() == <span class="number">0</span>:</span><br><span class="line">                gt_boxes_in_image = torch.zeros((<span class="number">1</span>, <span class="number">4</span>), dtype=dtype, device=device)</span><br><span class="line">            matched_gt_boxes.append(gt_boxes_in_image[matched_idxs[img_id]])</span><br><span class="line"></span><br><span class="line">        regression_targets = self.box_coder.encode(matched_gt_boxes, proposals)</span><br><span class="line">        <span class="keyword">return</span> proposals, matched_idxs, labels, regression_targets</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">postprocess_detections</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                               class_logits,    <span class="comment"># type: Tensor</span></span></span><br><span class="line"><span class="params">                               box_regression,  <span class="comment"># type: Tensor</span></span></span><br><span class="line"><span class="params">                               proposals,       <span class="comment"># type: List[Tensor]</span></span></span><br><span class="line"><span class="params">                               image_shapes     <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                               </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        device = class_logits.device</span><br><span class="line">        num_classes = class_logits.shape[-<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">        boxes_per_image = [boxes_in_image.shape[<span class="number">0</span>] <span class="keyword">for</span> boxes_in_image <span class="keyword">in</span> proposals]</span><br><span class="line">        pred_boxes = self.box_coder.decode(box_regression, proposals)</span><br><span class="line"></span><br><span class="line">        pred_scores = F.softmax(class_logits, -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">        pred_boxes_list = pred_boxes.split(boxes_per_image, <span class="number">0</span>)</span><br><span class="line">        pred_scores_list = pred_scores.split(boxes_per_image, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">        all_boxes = []</span><br><span class="line">        all_scores = []</span><br><span class="line">        all_labels = []</span><br><span class="line">        <span class="keyword">for</span> boxes, scores, image_shape <span class="keyword">in</span> <span class="built_in">zip</span>(pred_boxes_list, pred_scores_list, image_shapes):</span><br><span class="line">            boxes = box_ops.clip_boxes_to_image(boxes, image_shape)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># create labels for each prediction</span></span><br><span class="line">            labels = torch.arange(num_classes, device=device)</span><br><span class="line">            labels = labels.view(<span class="number">1</span>, -<span class="number">1</span>).expand_as(scores)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># remove predictions with the background label</span></span><br><span class="line">            boxes = boxes[:, <span class="number">1</span>:]</span><br><span class="line">            scores = scores[:, <span class="number">1</span>:]</span><br><span class="line">            labels = labels[:, <span class="number">1</span>:]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># batch everything, by making every class prediction be a separate instance</span></span><br><span class="line">            boxes = boxes.reshape(-<span class="number">1</span>, <span class="number">4</span>)</span><br><span class="line">            scores = scores.reshape(-<span class="number">1</span>)</span><br><span class="line">            labels = labels.reshape(-<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># remove low scoring boxes</span></span><br><span class="line">            inds = torch.where(scores &gt; self.score_thresh)[<span class="number">0</span>]</span><br><span class="line">            boxes, scores, labels = boxes[inds], scores[inds], labels[inds]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># remove empty boxes</span></span><br><span class="line">            keep = box_ops.remove_small_boxes(boxes, min_size=<span class="number">1e-2</span>)</span><br><span class="line">            boxes, scores, labels = boxes[keep], scores[keep], labels[keep]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># non-maximum suppression, independently done per class</span></span><br><span class="line">            keep = box_ops.batched_nms(boxes, scores, labels, self.nms_thresh)</span><br><span class="line">            <span class="comment"># keep only topk scoring predictions</span></span><br><span class="line">            keep = keep[:self.detections_per_img]</span><br><span class="line">            boxes, scores, labels = boxes[keep], scores[keep], labels[keep]</span><br><span class="line"></span><br><span class="line">            all_boxes.append(boxes)</span><br><span class="line">            all_scores.append(scores)</span><br><span class="line">            all_labels.append(labels)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> all_boxes, all_scores, all_labels</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                features,      <span class="comment"># type: Dict[str, Tensor]</span></span></span><br><span class="line"><span class="params">                proposals,     <span class="comment"># type: List[Tensor]</span></span></span><br><span class="line"><span class="params">                image_shapes,  <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                targets=<span class="literal">None</span>   <span class="comment"># type: Optional[List[Dict[str, Tensor]]]</span></span></span><br><span class="line"><span class="params">                </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]], <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]</span></span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            features (List[Tensor])</span></span><br><span class="line"><span class="string">            proposals (List[Tensor[N, 4]])</span></span><br><span class="line"><span class="string">            image_shapes (List[Tuple[H, W]])</span></span><br><span class="line"><span class="string">            targets (List[Dict])</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            <span class="keyword">for</span> t <span class="keyword">in</span> targets:</span><br><span class="line">                <span class="comment"># <span class="doctag">TODO:</span> https://github.com/pytorch/pytorch/issues/26731</span></span><br><span class="line">                floating_point_types = (torch.<span class="built_in">float</span>, torch.double, torch.half)</span><br><span class="line">                <span class="keyword">assert</span> t[<span class="string">&quot;boxes&quot;</span>].dtype <span class="keyword">in</span> floating_point_types, <span class="string">&#x27;target boxes must of float type&#x27;</span></span><br><span class="line">                <span class="keyword">assert</span> t[<span class="string">&quot;labels&quot;</span>].dtype == torch.int64, <span class="string">&#x27;target labels must of int64 type&#x27;</span></span><br><span class="line">                <span class="keyword">if</span> self.has_keypoint():</span><br><span class="line">                    <span class="keyword">assert</span> t[<span class="string">&quot;keypoints&quot;</span>].dtype == torch.float32, <span class="string">&#x27;target keypoints must of float type&#x27;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            proposals, matched_idxs, labels, regression_targets = self.select_training_samples(proposals, targets)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            labels = <span class="literal">None</span></span><br><span class="line">            regression_targets = <span class="literal">None</span></span><br><span class="line">            matched_idxs = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        box_features = self.box_roi_pool(features, proposals, image_shapes)</span><br><span class="line">        box_features = self.box_head(box_features)</span><br><span class="line">        class_logits, box_regression = self.box_predictor(box_features)</span><br><span class="line"></span><br><span class="line">        result = torch.jit.annotate(<span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, torch.Tensor]], [])</span><br><span class="line">        losses = &#123;&#125;</span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">assert</span> labels <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">and</span> regression_targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">            loss_classifier, loss_box_reg = fastrcnn_loss(</span><br><span class="line">                class_logits, box_regression, labels, regression_targets)</span><br><span class="line">            losses = &#123;</span><br><span class="line">                <span class="string">&quot;loss_classifier&quot;</span>: loss_classifier,</span><br><span class="line">                <span class="string">&quot;loss_box_reg&quot;</span>: loss_box_reg</span><br><span class="line">            &#125;</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            boxes, scores, labels = self.postprocess_detections(class_logits, box_regression, proposals, image_shapes)</span><br><span class="line">            num_images = <span class="built_in">len</span>(boxes)</span><br><span class="line">            <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(num_images):</span><br><span class="line">                result.append(</span><br><span class="line">                    &#123;</span><br><span class="line">                        <span class="string">&quot;boxes&quot;</span>: boxes[i],</span><br><span class="line">                        <span class="string">&quot;labels&quot;</span>: labels[i],</span><br><span class="line">                        <span class="string">&quot;scores&quot;</span>: scores[i],</span><br><span class="line">                    &#125;</span><br><span class="line">                )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> self.has_mask():</span><br><span class="line">            mask_proposals = [p[<span class="string">&quot;boxes&quot;</span>] <span class="keyword">for</span> p <span class="keyword">in</span> result]</span><br><span class="line">            <span class="keyword">if</span> self.training:</span><br><span class="line">                <span class="keyword">assert</span> matched_idxs <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">                <span class="comment"># during training, only focus on positive boxes</span></span><br><span class="line">                num_images = <span class="built_in">len</span>(proposals)</span><br><span class="line">                mask_proposals = []</span><br><span class="line">                pos_matched_idxs = []</span><br><span class="line">                <span class="keyword">for</span> img_id <span class="keyword">in</span> <span class="built_in">range</span>(num_images):</span><br><span class="line">                    pos = torch.where(labels[img_id] &gt; <span class="number">0</span>)[<span class="number">0</span>]</span><br><span class="line">                    mask_proposals.append(proposals[img_id][pos])</span><br><span class="line">                    pos_matched_idxs.append(matched_idxs[img_id][pos])</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                pos_matched_idxs = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> self.mask_roi_pool <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">                mask_features = self.mask_roi_pool(features, mask_proposals, image_shapes)</span><br><span class="line">                mask_features = self.mask_head(mask_features)</span><br><span class="line">                mask_logits = self.mask_predictor(mask_features)</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                mask_logits = torch.tensor(<span class="number">0</span>)</span><br><span class="line">                <span class="keyword">raise</span> Exception(<span class="string">&quot;Expected mask_roi_pool to be not None&quot;</span>)</span><br><span class="line"></span><br><span class="line">            loss_mask = &#123;&#125;</span><br><span class="line">            <span class="keyword">if</span> self.training:</span><br><span class="line">                <span class="keyword">assert</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">                <span class="keyword">assert</span> pos_matched_idxs <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">                <span class="keyword">assert</span> mask_logits <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">                gt_masks = [t[<span class="string">&quot;masks&quot;</span>] <span class="keyword">for</span> t <span class="keyword">in</span> targets]</span><br><span class="line">                gt_labels = [t[<span class="string">&quot;labels&quot;</span>] <span class="keyword">for</span> t <span class="keyword">in</span> targets]</span><br><span class="line">                rcnn_loss_mask = maskrcnn_loss(</span><br><span class="line">                    mask_logits, mask_proposals,</span><br><span class="line">                    gt_masks, gt_labels, pos_matched_idxs)</span><br><span class="line">                loss_mask = &#123;</span><br><span class="line">                    <span class="string">&quot;loss_mask&quot;</span>: rcnn_loss_mask</span><br><span class="line">                &#125;</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                labels = [r[<span class="string">&quot;labels&quot;</span>] <span class="keyword">for</span> r <span class="keyword">in</span> result]</span><br><span class="line">                masks_probs = maskrcnn_inference(mask_logits, labels)</span><br><span class="line">                <span class="keyword">for</span> mask_prob, r <span class="keyword">in</span> <span class="built_in">zip</span>(masks_probs, result):</span><br><span class="line">                    r[<span class="string">&quot;masks&quot;</span>] = mask_prob</span><br><span class="line"></span><br><span class="line">            losses.update(loss_mask)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># keep none checks in if conditional so torchscript will conditionally</span></span><br><span class="line">        <span class="comment"># compile each branch</span></span><br><span class="line">        <span class="keyword">if</span> self.keypoint_roi_pool <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">and</span> self.keypoint_head <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> \</span><br><span class="line">                <span class="keyword">and</span> self.keypoint_predictor <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">            keypoint_proposals = [p[<span class="string">&quot;boxes&quot;</span>] <span class="keyword">for</span> p <span class="keyword">in</span> result]</span><br><span class="line">            <span class="keyword">if</span> self.training:</span><br><span class="line">                <span class="comment"># during training, only focus on positive boxes</span></span><br><span class="line">                num_images = <span class="built_in">len</span>(proposals)</span><br><span class="line">                keypoint_proposals = []</span><br><span class="line">                pos_matched_idxs = []</span><br><span class="line">                <span class="keyword">assert</span> matched_idxs <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">                <span class="keyword">for</span> img_id <span class="keyword">in</span> <span class="built_in">range</span>(num_images):</span><br><span class="line">                    pos = torch.where(labels[img_id] &gt; <span class="number">0</span>)[<span class="number">0</span>]</span><br><span class="line">                    keypoint_proposals.append(proposals[img_id][pos])</span><br><span class="line">                    pos_matched_idxs.append(matched_idxs[img_id][pos])</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                pos_matched_idxs = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">            keypoint_features = self.keypoint_roi_pool(features, keypoint_proposals, image_shapes)</span><br><span class="line">            keypoint_features = self.keypoint_head(keypoint_features)</span><br><span class="line">            keypoint_logits = self.keypoint_predictor(keypoint_features)</span><br><span class="line"></span><br><span class="line">            loss_keypoint = &#123;&#125;</span><br><span class="line">            <span class="keyword">if</span> self.training:</span><br><span class="line">                <span class="keyword">assert</span> targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">                <span class="keyword">assert</span> pos_matched_idxs <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">                gt_keypoints = [t[<span class="string">&quot;keypoints&quot;</span>] <span class="keyword">for</span> t <span class="keyword">in</span> targets]</span><br><span class="line">                rcnn_loss_keypoint = keypointrcnn_loss(</span><br><span class="line">                    keypoint_logits, keypoint_proposals,</span><br><span class="line">                    gt_keypoints, pos_matched_idxs)</span><br><span class="line">                loss_keypoint = &#123;</span><br><span class="line">                    <span class="string">&quot;loss_keypoint&quot;</span>: rcnn_loss_keypoint</span><br><span class="line">                &#125;</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="keyword">assert</span> keypoint_logits <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">                <span class="keyword">assert</span> keypoint_proposals <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">                keypoints_probs, kp_scores = keypointrcnn_inference(keypoint_logits, keypoint_proposals)</span><br><span class="line">                <span class="keyword">for</span> keypoint_prob, kps, r <span class="keyword">in</span> <span class="built_in">zip</span>(keypoints_probs, kp_scores, result):</span><br><span class="line">                    r[<span class="string">&quot;keypoints&quot;</span>] = keypoint_prob</span><br><span class="line">                    r[<span class="string">&quot;keypoints_scores&quot;</span>] = kps</span><br><span class="line"></span><br><span class="line">            losses.update(loss_keypoint)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> result, losses</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>主要看<code>__forward__</code>函数的实现，虽然很长，但是如果只考虑FasterR-CNN需要的部分（不考虑用于MaskR-CNN的图像分割分支），其实可以概括为：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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">def</span> <span class="title function_">forward</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">            features,      <span class="comment"># type: Dict[str, Tensor]</span></span></span><br><span class="line"><span class="params">            proposals,     <span class="comment"># type: List[Tensor]</span></span></span><br><span class="line"><span class="params">            image_shapes,  <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">            targets=<span class="literal">None</span>   <span class="comment"># type: Optional[List[Dict[str, Tensor]]]</span></span></span><br><span class="line"><span class="params">            </span>):</span><br><span class="line">    <span class="comment"># 1. RoI Pool (or RoI Align)</span></span><br><span class="line">    box_features = self.box_roi_pool(features, proposals, image_shapes)</span><br><span class="line">    <span class="comment"># 2. MLP Head</span></span><br><span class="line">    box_features = self.box_head(box_features)</span><br><span class="line">    <span class="comment"># 3. Predictor</span></span><br><span class="line">    class_logits, box_regression = self.box_predictor(box_features)</span><br><span class="line"></span><br><span class="line">    result = torch.jit.annotate(<span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, torch.Tensor]], [])</span><br><span class="line">    losses = &#123;&#125;</span><br><span class="line">    <span class="keyword">if</span> self.training:</span><br><span class="line">        <span class="keyword">assert</span> labels <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span> <span class="keyword">and</span> regression_targets <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line">        loss_classifier, loss_box_reg = fastrcnn_loss(</span><br><span class="line">            class_logits, box_regression, labels, regression_targets)</span><br><span class="line">        losses = &#123;</span><br><span class="line">            <span class="string">&quot;loss_classifier&quot;</span>: loss_classifier,</span><br><span class="line">            <span class="string">&quot;loss_box_reg&quot;</span>: loss_box_reg</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># 4. Postprocess Detections</span></span><br><span class="line">        boxes, scores, labels = self.postprocess_detections(class_logits, box_regression, proposals, image_shapes)</span><br><span class="line">        num_images = <span class="built_in">len</span>(boxes)</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(num_images):</span><br><span class="line">            result.append(</span><br><span class="line">                &#123;</span><br><span class="line">                    <span class="string">&quot;boxes&quot;</span>: boxes[i],</span><br><span class="line">                    <span class="string">&quot;labels&quot;</span>: labels[i],</span><br><span class="line">                    <span class="string">&quot;scores&quot;</span>: scores[i],</span><br><span class="line">                &#125;</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> result, losses</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Faster R-CNN的RoIHeads主要包含几个步骤：</p><ol type="1"><li>RoIPool：由<code>box_features = self.box_roi_pool(features, proposals, image_shapes)</code>执行，FasterR-CNN的RoIPool的具体实现是<code>torchvision.ops.poolers</code>包中的<code>MultiScaleRoIAlign</code>类。因为目标的形状不尽相同，所以涉及到的特征窗口就不尽相同。RoIPool的目的在于通过把尺寸不定的RoIwindow划分为固定的网格做池化，来把输入的变长的RoI特征池化为定长的特征输出，方便后续的特征处理。</li><li>MLPHead：由<code>box_features = self.box_head(box_features)</code>执行，FasterR-CNN的MLPHead的具体实现是<code>torchvision.models.detection.faster_rcnn</code>中的<code>TwoMLPHead</code>类。MLPHead承接RoIPool池化出的定长特征向量，并通过MLP做非线性计算，输出最终特征用于后续的任务（分类、回归等）。</li><li>Predictor：由<code>class_logits, box_regression = self.box_predictor(box_features)</code>执行，FasterR-CNN的Predictor的具体实现是<code>torchvision.models.detection.faster_rcnn</code>中的<code>FastRCNNPredictor</code>类。上一步MLP操作输出的特征作为最后的特征，交给Predictor去做具体任务的预测，例如：目标分类，bbox位置和尺寸值的回归预测。</li><li>PostprocessDetections：由<code>boxes, scores, labels = self.postprocess_detections(class_logits, box_regression, proposals, image_shapes)</code>，该函数是RoIHeads类的一个成员函数。</li></ol><h5 id="multiscaleroialign">MultiScaleRoIAlign</h5><p><code>torchvision</code>采用<code>torchvision.ops.poolers</code>包中的<code>MultiScaleRoIAlign</code>作为FasterR-CNN的RoI Pool的实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MultiScaleRoIAlign</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Multi-scale RoIAlign pooling, which is useful for detection with or without FPN.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    It infers the scale of the pooling via the heuristics present in the FPN paper.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        featmap_names (List[str]): the names of the feature maps that will be used</span></span><br><span class="line"><span class="string">            for the pooling.</span></span><br><span class="line"><span class="string">        output_size (List[Tuple[int, int]] or List[int]): output size for the pooled region</span></span><br><span class="line"><span class="string">        sampling_ratio (int): sampling ratio for ROIAlign</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Examples::</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; m = torchvision.ops.MultiScaleRoIAlign([&#x27;feat1&#x27;, &#x27;feat3&#x27;], 3, 2)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; i = OrderedDict()</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; i[&#x27;feat1&#x27;] = torch.rand(1, 5, 64, 64)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; i[&#x27;feat2&#x27;] = torch.rand(1, 5, 32, 32)  # this feature won&#x27;t be used in the pooling</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; i[&#x27;feat3&#x27;] = torch.rand(1, 5, 16, 16)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # create some random bounding boxes</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; boxes = torch.rand(6, 4) * 256; boxes[:, 2:] += boxes[:, :2]</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; # original image size, before computing the feature maps</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; image_sizes = [(512, 512)]</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; output = m(i, [boxes], image_sizes)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; print(output.shape)</span></span><br><span class="line"><span class="string">        &gt;&gt;&gt; torch.Size([6, 5, 3, 3])</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    __annotations__ = &#123;</span><br><span class="line">        <span class="string">&#x27;scales&#x27;</span>: <span class="type">Optional</span>[<span class="type">List</span>[<span class="built_in">float</span>]],</span><br><span class="line">        <span class="string">&#x27;map_levels&#x27;</span>: <span class="type">Optional</span>[LevelMapper]</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        featmap_names: <span class="type">List</span>[<span class="built_in">str</span>],</span></span><br><span class="line"><span class="params">        output_size: <span class="type">Union</span>[<span class="built_in">int</span>, <span class="type">Tuple</span>[<span class="built_in">int</span>], <span class="type">List</span>[<span class="built_in">int</span>]],</span></span><br><span class="line"><span class="params">        sampling_ratio: <span class="built_in">int</span>,</span></span><br><span class="line"><span class="params">    </span>):</span><br><span class="line">        <span class="built_in">super</span>(MultiScaleRoIAlign, self).__init__()</span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">isinstance</span>(output_size, <span class="built_in">int</span>):</span><br><span class="line">            output_size = (output_size, output_size)</span><br><span class="line">        self.featmap_names = featmap_names</span><br><span class="line">        self.sampling_ratio = sampling_ratio</span><br><span class="line">        self.output_size = <span class="built_in">tuple</span>(output_size)</span><br><span class="line">        self.scales = <span class="literal">None</span></span><br><span class="line">        self.map_levels = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">convert_to_roi_format</span>(<span class="params">self, boxes: <span class="type">List</span>[Tensor]</span>) -&gt; Tensor:</span><br><span class="line">        concat_boxes = torch.cat(boxes, dim=<span class="number">0</span>)</span><br><span class="line">        device, dtype = concat_boxes.device, concat_boxes.dtype</span><br><span class="line">        ids = torch.cat(</span><br><span class="line">            [</span><br><span class="line">                torch.full_like(b[:, :<span class="number">1</span>], i, dtype=dtype, layout=torch.strided, device=device)</span><br><span class="line">                <span class="keyword">for</span> i, b <span class="keyword">in</span> <span class="built_in">enumerate</span>(boxes)</span><br><span class="line">            ],</span><br><span class="line">            dim=<span class="number">0</span>,</span><br><span class="line">        )</span><br><span class="line">        rois = torch.cat([ids, concat_boxes], dim=<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">return</span> rois</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">infer_scale</span>(<span class="params">self, feature: Tensor, original_size: <span class="type">List</span>[<span class="built_in">int</span>]</span>) -&gt; <span class="built_in">float</span>:</span><br><span class="line">        <span class="comment"># assumption: the scale is of the form 2 ** (-k), with k integer</span></span><br><span class="line">        size = feature.shape[-<span class="number">2</span>:]</span><br><span class="line">        possible_scales = torch.jit.annotate(<span class="type">List</span>[<span class="built_in">float</span>], [])</span><br><span class="line">        <span class="keyword">for</span> s1, s2 <span class="keyword">in</span> <span class="built_in">zip</span>(size, original_size):</span><br><span class="line">            approx_scale = <span class="built_in">float</span>(s1) / <span class="built_in">float</span>(s2)</span><br><span class="line">            scale = <span class="number">2</span> ** <span class="built_in">float</span>(torch.tensor(approx_scale).log2().<span class="built_in">round</span>())</span><br><span class="line">            possible_scales.append(scale)</span><br><span class="line">        <span class="keyword">assert</span> possible_scales[<span class="number">0</span>] == possible_scales[<span class="number">1</span>]</span><br><span class="line">        <span class="keyword">return</span> possible_scales[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">setup_scales</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        features: <span class="type">List</span>[Tensor],</span></span><br><span class="line"><span class="params">        image_shapes: <span class="type">List</span>[<span class="type">Tuple</span>[<span class="built_in">int</span>, <span class="built_in">int</span>]],</span></span><br><span class="line"><span class="params">    </span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">assert</span> <span class="built_in">len</span>(image_shapes) != <span class="number">0</span></span><br><span class="line">        max_x = <span class="number">0</span></span><br><span class="line">        max_y = <span class="number">0</span></span><br><span class="line">        <span class="keyword">for</span> shape <span class="keyword">in</span> image_shapes:</span><br><span class="line">            max_x = <span class="built_in">max</span>(shape[<span class="number">0</span>], max_x)</span><br><span class="line">            max_y = <span class="built_in">max</span>(shape[<span class="number">1</span>], max_y)</span><br><span class="line">        original_input_shape = (max_x, max_y)</span><br><span class="line"></span><br><span class="line">        scales = [self.infer_scale(feat, original_input_shape) <span class="keyword">for</span> feat <span class="keyword">in</span> features]</span><br><span class="line">        <span class="comment"># get the levels in the feature map by leveraging the fact that the network always</span></span><br><span class="line">        <span class="comment"># downsamples by a factor of 2 at each level.</span></span><br><span class="line">        lvl_min = -torch.log2(torch.tensor(scales[<span class="number">0</span>], dtype=torch.float32)).item()</span><br><span class="line">        lvl_max = -torch.log2(torch.tensor(scales[-<span class="number">1</span>], dtype=torch.float32)).item()</span><br><span class="line">        self.scales = scales</span><br><span class="line">        self.map_levels = initLevelMapper(<span class="built_in">int</span>(lvl_min), <span class="built_in">int</span>(lvl_max))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params"></span></span><br><span class="line"><span class="params">        self,</span></span><br><span class="line"><span class="params">        x: <span class="type">Dict</span>[<span class="built_in">str</span>, Tensor],</span></span><br><span class="line"><span class="params">        boxes: <span class="type">List</span>[Tensor],</span></span><br><span class="line"><span class="params">        image_shapes: <span class="type">List</span>[<span class="type">Tuple</span>[<span class="built_in">int</span>, <span class="built_in">int</span>]],</span></span><br><span class="line"><span class="params">    </span>) -&gt; Tensor:</span><br><span class="line">        <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">        Arguments:</span></span><br><span class="line"><span class="string">            x (OrderedDict[Tensor]): feature maps for each level. They are assumed to have</span></span><br><span class="line"><span class="string">                all the same number of channels, but they can have different sizes.</span></span><br><span class="line"><span class="string">            boxes (List[Tensor[N, 4]]): boxes to be used to perform the pooling operation, in</span></span><br><span class="line"><span class="string">                (x1, y1, x2, y2) format and in the image reference size, not the feature map</span></span><br><span class="line"><span class="string">                reference.</span></span><br><span class="line"><span class="string">            image_shapes (List[Tuple[height, width]]): the sizes of each image before they</span></span><br><span class="line"><span class="string">                have been fed to a CNN to obtain feature maps. This allows us to infer the</span></span><br><span class="line"><span class="string">                scale factor for each one of the levels to be pooled.</span></span><br><span class="line"><span class="string">        Returns:</span></span><br><span class="line"><span class="string">            result (Tensor)</span></span><br><span class="line"><span class="string">        &quot;&quot;&quot;</span></span><br><span class="line">        x_filtered = []</span><br><span class="line">        <span class="keyword">for</span> k, v <span class="keyword">in</span> x.items():</span><br><span class="line">            <span class="keyword">if</span> k <span class="keyword">in</span> self.featmap_names:</span><br><span class="line">                x_filtered.append(v)</span><br><span class="line">        num_levels = <span class="built_in">len</span>(x_filtered)</span><br><span class="line">        rois = self.convert_to_roi_format(boxes)</span><br><span class="line">        <span class="keyword">if</span> self.scales <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">            self.setup_scales(x_filtered, image_shapes)</span><br><span class="line"></span><br><span class="line">        scales = self.scales</span><br><span class="line">        <span class="keyword">assert</span> scales <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> num_levels == <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span> roi_align(</span><br><span class="line">                x_filtered[<span class="number">0</span>], rois,</span><br><span class="line">                output_size=self.output_size,</span><br><span class="line">                spatial_scale=scales[<span class="number">0</span>],</span><br><span class="line">                sampling_ratio=self.sampling_ratio</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">        mapper = self.map_levels</span><br><span class="line">        <span class="keyword">assert</span> mapper <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        levels = mapper(boxes)</span><br><span class="line"></span><br><span class="line">        num_rois = <span class="built_in">len</span>(rois)</span><br><span class="line">        num_channels = x_filtered[<span class="number">0</span>].shape[<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">        dtype, device = x_filtered[<span class="number">0</span>].dtype, x_filtered[<span class="number">0</span>].device</span><br><span class="line">        result = torch.zeros(</span><br><span class="line">            (num_rois, num_channels,) + self.output_size,</span><br><span class="line">            dtype=dtype,</span><br><span class="line">            device=device,</span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        tracing_results = []</span><br><span class="line">        <span class="keyword">for</span> level, (per_level_feature, scale) <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="built_in">zip</span>(x_filtered, scales)):</span><br><span class="line">            idx_in_level = torch.where(levels == level)[<span class="number">0</span>]</span><br><span class="line">            rois_per_level = rois[idx_in_level]</span><br><span class="line"></span><br><span class="line">            result_idx_in_level = roi_align(</span><br><span class="line">                per_level_feature, rois_per_level,</span><br><span class="line">                output_size=self.output_size,</span><br><span class="line">                spatial_scale=scale, sampling_ratio=self.sampling_ratio)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> torchvision._is_tracing():</span><br><span class="line">                tracing_results.append(result_idx_in_level.to(dtype))</span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="comment"># result and result_idx_in_level&#x27;s dtypes are based on dtypes of different</span></span><br><span class="line">                <span class="comment"># elements in x_filtered.  x_filtered contains tensors output by different</span></span><br><span class="line">                <span class="comment"># layers.  When autocast is active, it may choose different dtypes for</span></span><br><span class="line">                <span class="comment"># different layers&#x27; outputs.  Therefore, we defensively match result&#x27;s dtype</span></span><br><span class="line">                <span class="comment"># before copying elements from result_idx_in_level in the following op.</span></span><br><span class="line">                <span class="comment"># We need to cast manually (can&#x27;t rely on autocast to cast for us) because</span></span><br><span class="line">                <span class="comment"># the op acts on result in-place, and autocast only affects out-of-place ops.</span></span><br><span class="line">                result[idx_in_level] = result_idx_in_level.to(result.dtype)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> torchvision._is_tracing():</span><br><span class="line">            result = _onnx_merge_levels(levels, tracing_results)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> result</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>实际上Faster R-CNN论文发表时并没有RoI Align技术，当时仍然沿用的是FastR-CNN中的RoI Pool。RoIPool指的是对RoI内的特征做池化，取得一个小的featuremap，即，把原来形状不定的<span class="math inline">\(h \timesw\)</span>（<span class="math inline">\(h,w\)</span>均为变量）的RoI窗口内的特征池化为统一的<spanclass="math inline">\(H \times W\)</span>（<spanclass="math inline">\(H, W\)</span>均为常量）的小feature map。</p><p>RoI Align其实是Mask R-CNN论文中提出的概念。RoI Align觉得RoIPool的处理太粗糙了，存在量化（Quantization）的问题，计算featuremap上的窗口坐标的时候就舍入取整了，窗口内划分bins的时候又舍入取整了，这样就很不精确。这样的量化处理，用作分类任务倒还影响不大，但是用作图像分割这种像素级精度的任务时就是个问题了。</p><blockquote><p>RoI Align对RoIPool的改进及其二次插值的数学计算原理可以仔细阅读这篇文章：</p><p><ahref="https://towardsdatascience.com/understanding-region-of-interest-part-2-roi-align-and-roi-warp-f795196fc193">UnderstandingRegion of Interest — (RoI Align and RoI Warp) | by Kemal Erdem(burnpiro) | Towards Data Science</a></p></blockquote><p>MultiScaleRoIAlign核心的RoIAlign操作是通过调用<code>torchvision.ops.roi_align</code>包的<code>roi_align</code>函数实现的，而该函数实际上也只是执行了对底层<code>torch.ops.torchvision.roi_align</code>函数的调用。</p><p>在本例中，输入的两张图片经过RPN处理后，各得到1000个boxes，即共2000个boxes。经过RoIPool/ RoIAlign处理后，输出为：</p><ul><li><code>box_features</code>（<code>results</code>）：shape[2000, 256,7, 7]</li></ul><p>表示2000个boxes，都被池化为了<span class="math inline">\(C, H, W =256, 7, 7\)</span>的特征。</p><h5 id="twomlphead">TwoMLPHead</h5><p><code>torchvision</code>采用<code>torchvision.models.detection.faster_rcnn</code>包中的<code>TwoMLPHead</code>作为MLPHead的实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">class</span> <span class="title class_">TwoMLPHead</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Standard heads for FPN-based models</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        in_channels (int): number of input channels</span></span><br><span class="line"><span class="string">        representation_size (int): size of the intermediate representation</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, in_channels, representation_size</span>):</span><br><span class="line">        <span class="built_in">super</span>(TwoMLPHead, self).__init__()</span><br><span class="line"></span><br><span class="line">        self.fc6 = nn.Linear(in_channels, representation_size)</span><br><span class="line">        self.fc7 = nn.Linear(representation_size, representation_size)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        x = x.flatten(start_dim=<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">        x = F.relu(self.fc6(x))</span><br><span class="line">        x = F.relu(self.fc7(x))</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> x</span><br></pre></td></tr></table></figure><p>这部分并不复杂，实际上就是实现了两层的MLP，名为<code>fc6</code>和<code>fc7</code>。</p><p>在本例中，RoIAlign输出的<code>box_features</code>原shape[2000, 256,7, 7]，在TwoMLPHead中：</p><ol type="1"><li>首先经过flatten处理，变为shape[2000, 12544]；</li><li>进过双层MLP处理后，变为shape[2000, 1024]。</li></ol><p>返回的是如上非线性转换后的<code>box_features</code>特征，此时shape[2000,1024]。</p><h5 id="fastrcnnpredictor">FastRCNNPredictor</h5><p><code>torchvision</code>采用<code>torchvision.models.detection.faster_rcnn</code>包中的<code>FastRCNNPredictor</code>作为Predictor的实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FastRCNNPredictor</span>(nn.Module):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    Standard classification + bounding box regression layers</span></span><br><span class="line"><span class="string">    for Fast R-CNN.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">    Arguments:</span></span><br><span class="line"><span class="string">        in_channels (int): number of input channels</span></span><br><span class="line"><span class="string">        num_classes (int): number of output classes (including background)</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, in_channels, num_classes</span>):</span><br><span class="line">        <span class="built_in">super</span>(FastRCNNPredictor, self).__init__()</span><br><span class="line">        self.cls_score = nn.Linear(in_channels, num_classes)</span><br><span class="line">        self.bbox_pred = nn.Linear(in_channels, num_classes * <span class="number">4</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">forward</span>(<span class="params">self, x</span>):</span><br><span class="line">        <span class="keyword">if</span> x.dim() == <span class="number">4</span>:</span><br><span class="line">            <span class="keyword">assert</span> <span class="built_in">list</span>(x.shape[<span class="number">2</span>:]) == [<span class="number">1</span>, <span class="number">1</span>]</span><br><span class="line">        x = x.flatten(start_dim=<span class="number">1</span>)</span><br><span class="line">        scores = self.cls_score(x)</span><br><span class="line">        bbox_deltas = self.bbox_pred(x)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> scores, bbox_deltas</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这部分也并不复杂，实际上就是同论文中描述的一样，通过MLP实现了两个预测分支：</p><ol type="1"><li><code>self.cls_score</code>分支预测目标的分类分值<code>scores</code>；</li><li><code>self.bbox_pred</code>分支回归目标对应各个分类的目标框回归值。</li></ol><p>在<code>torchvision</code>的预训练模型中，FastRCNNPredictor的<code>num_classes</code>是91，即能识别含背景在内的91个类。</p><p>在本例中，两个分支根据RoIAlign和TwoMLPHead提取出的特征，分别预测输出：</p><ol type="1"><li><code>class_logits</code>（<code>socres</code>）：shape[2000,91]；</li><li><code>box_regression</code>（<code>bbox_deltas</code>）：shape[2000,364]。</li></ol><p>意思是输入的2张图片上共2000个框（1000个/图片），这2000个框都做了分类预测，并且为每个类分别计算了目标框的回归修正值。</p><h5 id="postprocess_detections">postprocess_detections</h5><p>在模型的Predictor完成预测后，还需要做后续的一些处理，该部分的处理在<code>torchvision.models.detection.roi_heads.RoIHeads</code>的<code>postprocess_detections</code>函数中实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">class</span> <span class="title class_">RoIHeads</span>(torch.nn.Module):</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">postprocess_detections</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                               class_logits,    <span class="comment"># type: Tensor</span></span></span><br><span class="line"><span class="params">                               box_regression,  <span class="comment"># type: Tensor</span></span></span><br><span class="line"><span class="params">                               proposals,       <span class="comment"># type: List[Tensor]</span></span></span><br><span class="line"><span class="params">                               image_shapes     <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                               </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">Tuple</span>[<span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor], <span class="type">List</span>[Tensor]]</span></span><br><span class="line">        device = class_logits.device</span><br><span class="line">        num_classes = class_logits.shape[-<span class="number">1</span>]</span><br><span class="line"></span><br><span class="line">        boxes_per_image = [boxes_in_image.shape[<span class="number">0</span>] <span class="keyword">for</span> boxes_in_image <span class="keyword">in</span> proposals]</span><br><span class="line">        pred_boxes = self.box_coder.decode(box_regression, proposals)</span><br><span class="line"></span><br><span class="line">        pred_scores = F.softmax(class_logits, -<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">        pred_boxes_list = pred_boxes.split(boxes_per_image, <span class="number">0</span>)</span><br><span class="line">        pred_scores_list = pred_scores.split(boxes_per_image, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">        all_boxes = []</span><br><span class="line">        all_scores = []</span><br><span class="line">        all_labels = []</span><br><span class="line">        <span class="keyword">for</span> boxes, scores, image_shape <span class="keyword">in</span> <span class="built_in">zip</span>(pred_boxes_list, pred_scores_list, image_shapes):</span><br><span class="line">            boxes = box_ops.clip_boxes_to_image(boxes, image_shape)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># create labels for each prediction</span></span><br><span class="line">            labels = torch.arange(num_classes, device=device)</span><br><span class="line">            labels = labels.view(<span class="number">1</span>, -<span class="number">1</span>).expand_as(scores)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># remove predictions with the background label</span></span><br><span class="line">            boxes = boxes[:, <span class="number">1</span>:]</span><br><span class="line">            scores = scores[:, <span class="number">1</span>:]</span><br><span class="line">            labels = labels[:, <span class="number">1</span>:]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># batch everything, by making every class prediction be a separate instance</span></span><br><span class="line">            boxes = boxes.reshape(-<span class="number">1</span>, <span class="number">4</span>)</span><br><span class="line">            scores = scores.reshape(-<span class="number">1</span>)</span><br><span class="line">            labels = labels.reshape(-<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># remove low scoring boxes</span></span><br><span class="line">            inds = torch.where(scores &gt; self.score_thresh)[<span class="number">0</span>]</span><br><span class="line">            boxes, scores, labels = boxes[inds], scores[inds], labels[inds]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># remove empty boxes</span></span><br><span class="line">            keep = box_ops.remove_small_boxes(boxes, min_size=<span class="number">1e-2</span>)</span><br><span class="line">            boxes, scores, labels = boxes[keep], scores[keep], labels[keep]</span><br><span class="line"></span><br><span class="line">            <span class="comment"># non-maximum suppression, independently done per class</span></span><br><span class="line">            keep = box_ops.batched_nms(boxes, scores, labels, self.nms_thresh)</span><br><span class="line">            <span class="comment"># keep only topk scoring predictions</span></span><br><span class="line">            keep = keep[:self.detections_per_img]</span><br><span class="line">            boxes, scores, labels = boxes[keep], scores[keep], labels[keep]</span><br><span class="line"></span><br><span class="line">            all_boxes.append(boxes)</span><br><span class="line">            all_scores.append(scores)</span><br><span class="line">            all_labels.append(labels)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> all_boxes, all_scores, all_labels</span><br></pre></td></tr></table></figure><p>后处理在for循环中，遍历每一张图片：</p><ol type="1"><li>去除背景类的框；</li><li>去除低分框；</li><li>去除空框（尺寸极小的无意义小框）；</li><li>对它的boxes和scores做（类内）NMS；</li><li>保留<code>self.detections_per_img</code>个的top-k个目标。</li></ol><p>因为本例输入的是随机值填充的模拟图片，所以在去除低分框的环节，2000个候选框就因为没有实际的目标而被全部滤除了。</p><h4id="generalizedrcnntransform.postprocess">GeneralizedRCNNTransform.postprocess</h4><p>FasterR-CNN模型的后期处理由<code>torchvision.models.detection.transform</code>包的<code>GeneralizedRCNNTransform</code>类实现。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">GeneralizedRCNNTransform</span>(nn.Module):</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># ...</span></span><br><span class="line">    </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">postprocess</span>(<span class="params">self,</span></span><br><span class="line"><span class="params">                    result,               <span class="comment"># type: List[Dict[str, Tensor]]</span></span></span><br><span class="line"><span class="params">                    image_shapes,         <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                    original_image_sizes  <span class="comment"># type: List[Tuple[int, int]]</span></span></span><br><span class="line"><span class="params">                    </span>):</span><br><span class="line">        <span class="comment"># type: (...) -&gt; <span class="type">List</span>[<span class="type">Dict</span>[<span class="built_in">str</span>, Tensor]]</span></span><br><span class="line">        <span class="keyword">if</span> self.training:</span><br><span class="line">            <span class="keyword">return</span> result</span><br><span class="line">        <span class="keyword">for</span> i, (pred, im_s, o_im_s) <span class="keyword">in</span> <span class="built_in">enumerate</span>(<span class="built_in">zip</span>(result, image_shapes, original_image_sizes)):</span><br><span class="line">            boxes = pred[<span class="string">&quot;boxes&quot;</span>]</span><br><span class="line">            boxes = resize_boxes(boxes, im_s, o_im_s)</span><br><span class="line">            result[i][<span class="string">&quot;boxes&quot;</span>] = boxes</span><br><span class="line">            <span class="keyword">if</span> <span class="string">&quot;masks&quot;</span> <span class="keyword">in</span> pred:</span><br><span class="line">                masks = pred[<span class="string">&quot;masks&quot;</span>]</span><br><span class="line">                masks = paste_masks_in_image(masks, boxes, o_im_s)</span><br><span class="line">                result[i][<span class="string">&quot;masks&quot;</span>] = masks</span><br><span class="line">            <span class="keyword">if</span> <span class="string">&quot;keypoints&quot;</span> <span class="keyword">in</span> pred:</span><br><span class="line">                keypoints = pred[<span class="string">&quot;keypoints&quot;</span>]</span><br><span class="line">                keypoints = resize_keypoints(keypoints, im_s, o_im_s)</span><br><span class="line">                result[i][<span class="string">&quot;keypoints&quot;</span>] = keypoints</span><br><span class="line">        <span class="keyword">return</span> result</span><br></pre></td></tr></table></figure><p>其实对于目标检测而言，实际上只对<code>boxes</code>的坐标做了resize的操作。因为<code>GeneralizedRCNNTransform</code>在对输入图像做预处理的时候，有进行尺寸转换，而且转tensor的时候又增加了padding是同一batch的图像张量能够保持尺寸一致。所以输出结果的时候，还是要把在tensor上的坐标转换为原始图像尺度上的坐标。</p><h2 id="总结">3 总结</h2><p>最后总览一下整个模型的实现结构，只需通过简单的<code>print</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(model)</span><br></pre></td></tr></table></figure><p>，即可输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br></pre></td><td class="code"><pre><span class="line">FasterRCNN(</span><br><span class="line">  (transform): GeneralizedRCNNTransform(</span><br><span class="line">      Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])</span><br><span class="line">      Resize(min_size=(800,), max_size=1333, mode=&#x27;bilinear&#x27;)</span><br><span class="line">  )</span><br><span class="line">  (backbone): BackboneWithFPN(</span><br><span class="line">    (body): IntermediateLayerGetter(</span><br><span class="line">      (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)</span><br><span class="line">      (bn1): FrozenBatchNorm2d(64)</span><br><span class="line">      (relu): ReLU(inplace=True)</span><br><span class="line">      (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)</span><br><span class="line">      (layer1): Sequential(</span><br><span class="line">        (0): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(64)</span><br><span class="line">          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(64)</span><br><span class="line">          (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(256)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">          (downsample): Sequential(</span><br><span class="line">            (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">            (1): FrozenBatchNorm2d(256)</span><br><span class="line">          )</span><br><span class="line">        )</span><br><span class="line">        (1): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(64)</span><br><span class="line">          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(64)</span><br><span class="line">          (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(256)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (2): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(64)</span><br><span class="line">          (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(64)</span><br><span class="line">          (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(256)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">      )</span><br><span class="line">      (layer2): Sequential(</span><br><span class="line">        (0): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(512)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">          (downsample): Sequential(</span><br><span class="line">            (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)</span><br><span class="line">            (1): FrozenBatchNorm2d(512)</span><br><span class="line">          )</span><br><span class="line">        )</span><br><span class="line">        (1): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(512)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (2): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(512)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (3): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(128)</span><br><span class="line">          (conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(512)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">      )</span><br><span class="line">      (layer3): Sequential(</span><br><span class="line">        (0): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(1024)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">          (downsample): Sequential(</span><br><span class="line">            (0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)</span><br><span class="line">            (1): FrozenBatchNorm2d(1024)</span><br><span class="line">          )</span><br><span class="line">        )</span><br><span class="line">        (1): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(1024)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (2): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(1024)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (3): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(1024)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (4): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(1024)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (5): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(256)</span><br><span class="line">          (conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(1024)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">      )</span><br><span class="line">      (layer4): Sequential(</span><br><span class="line">        (0): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(512)</span><br><span class="line">          (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(512)</span><br><span class="line">          (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(2048)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">          (downsample): Sequential(</span><br><span class="line">            (0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)</span><br><span class="line">            (1): FrozenBatchNorm2d(2048)</span><br><span class="line">          )</span><br><span class="line">        )</span><br><span class="line">        (1): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(512)</span><br><span class="line">          (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(512)</span><br><span class="line">          (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(2048)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">        (2): Bottleneck(</span><br><span class="line">          (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn1): FrozenBatchNorm2d(512)</span><br><span class="line">          (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)</span><br><span class="line">          (bn2): FrozenBatchNorm2d(512)</span><br><span class="line">          (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)</span><br><span class="line">          (bn3): FrozenBatchNorm2d(2048)</span><br><span class="line">          (relu): ReLU(inplace=True)</span><br><span class="line">        )</span><br><span class="line">      )</span><br><span class="line">    )</span><br><span class="line">    (fpn): FeaturePyramidNetwork(</span><br><span class="line">      (inner_blocks): ModuleList(</span><br><span class="line">        (0): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1))</span><br><span class="line">        (1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))</span><br><span class="line">        (2): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1))</span><br><span class="line">        (3): Conv2d(2048, 256, kernel_size=(1, 1), stride=(1, 1))</span><br><span class="line">      )</span><br><span class="line">      (layer_blocks): ModuleList(</span><br><span class="line">        (0): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))</span><br><span class="line">        (1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))</span><br><span class="line">        (2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))</span><br><span class="line">        (3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))</span><br><span class="line">      )</span><br><span class="line">      (extra_blocks): LastLevelMaxPool()</span><br><span class="line">    )</span><br><span class="line">  )</span><br><span class="line">  (rpn): RegionProposalNetwork(</span><br><span class="line">    (anchor_generator): AnchorGenerator()</span><br><span class="line">    (head): RPNHead(</span><br><span class="line">      (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))</span><br><span class="line">      (cls_logits): Conv2d(256, 3, kernel_size=(1, 1), stride=(1, 1))</span><br><span class="line">      (bbox_pred): Conv2d(256, 12, kernel_size=(1, 1), stride=(1, 1))</span><br><span class="line">    )</span><br><span class="line">  )</span><br><span class="line">  (roi_heads): RoIHeads(</span><br><span class="line">    (box_roi_pool): MultiScaleRoIAlign()</span><br><span class="line">    (box_head): TwoMLPHead(</span><br><span class="line">      (fc6): Linear(in_features=12544, out_features=1024, bias=True)</span><br><span class="line">      (fc7): Linear(in_features=1024, out_features=1024, bias=True)</span><br><span class="line">    )</span><br><span class="line">    (box_predictor): FastRCNNPredictor(</span><br><span class="line">      (cls_score): Linear(in_features=1024, out_features=91, bias=True)</span><br><span class="line">      (bbox_pred): Linear(in_features=1024, out_features=364, bias=True)</span><br><span class="line">    )</span><br><span class="line">  )</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>模型结构可以总结为层次结构：</p><ul><li>Faster R-CNN<ul><li>(transform): GeneralizedRCNNTransform</li><li>(backbone): BackboneWithFPN<ul><li>(body): IntermediateLayerGetter</li><li>(fpn): FeaturePyramidNetwork</li></ul></li><li>(rpn): RegionProposalNetwork<ul><li>(anchor_generator): AnchorGenerator</li><li>(head): RPNHead</li></ul></li><li>(roi_heads): RoIHeads<ul><li>(box_roi_pool): MultiScaleRoIAlign</li><li>(box_head): TwoMLPHead</li><li>(box_predictor): FastRCNNPredictor</li></ul></li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;PyTorch的torchvision包中实现了Faster
R-CNN。本文结合对torchvision源码的阅读，深入理解Faster
R-CNN的内部原理，以便进行开发利用。&lt;/p&gt;</summary>
    
    
    
    
    <category term="R-CNN" scheme="https://heary.cn/tags/R-CNN/"/>
    
    <category term="Object Detection" scheme="https://heary.cn/tags/Object-Detection/"/>
    
    <category term="CV" scheme="https://heary.cn/tags/CV/"/>
    
    <category term="PyTorch" scheme="https://heary.cn/tags/PyTorch/"/>
    
    <category term="Deep Learning" scheme="https://heary.cn/tags/Deep-Learning/"/>
    
    <category term="Faster R-CNN" scheme="https://heary.cn/tags/Faster-R-CNN/"/>
    
    <category term="torchvision" scheme="https://heary.cn/tags/torchvision/"/>
    
  </entry>
  
  <entry>
    <title>Go笔记</title>
    <link href="https://heary.cn/posts/Go%E7%AC%94%E8%AE%B0/"/>
    <id>https://heary.cn/posts/Go%E7%AC%94%E8%AE%B0/</id>
    <published>2020-11-19T15:14:05.000Z</published>
    <updated>2022-08-07T04:02:09.604Z</updated>
    
    <content type="html"><![CDATA[<p>抽空学习Go语言。</p><span id="more"></span><h1 id="go笔记">Go笔记</h1><p>参考书籍：</p><blockquote><p>朱荣鑫，黄迪璇，张天. Go语言高并发与微服务实战[M].北京：中国铁道出版社有限公司，2020.</p></blockquote><p>代码同步更新在GitHub：<ahref="https://github.com/HearyShen/LearnGo">HearyShen/LearnGo</a></p><h2 id="基本变量">1 基本变量</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;math&quot;</span></span><br><span class="line"><span class="string">&quot;unicode/utf8&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run with command:</span></span><br><span class="line"><span class="comment">// go run src\1_variables.go</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// 3 ways to declare and initialize a variable</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;3 ways to declare and init a variable:\n&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a <span class="type">int</span> = <span class="number">100</span></span><br><span class="line"><span class="keyword">var</span> b = <span class="string">&quot;100&quot;</span></span><br><span class="line">c := <span class="number">0.17</span></span><br><span class="line"></span><br><span class="line">fmt.Printf(<span class="string">&quot;a value = %v (%T)\n&quot;</span>, a, a)</span><br><span class="line">fmt.Printf(<span class="string">&quot;b value = %v (%T)\n&quot;</span>, b, b)</span><br><span class="line">fmt.Printf(<span class="string">&quot;c value = %v (%T)\n&quot;</span>, c, c)</span><br><span class="line">fmt.Println()</span><br><span class="line"></span><br><span class="line"><span class="comment">// swap variables</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;Easy way to swap variables:\n&quot;</span>)</span><br><span class="line">v1 := <span class="number">1</span></span><br><span class="line">v2 := <span class="number">2</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;before swap: v1 = %v (%T), v2 = %v (%T)\n&quot;</span>, v1, v1, v2, v2)</span><br><span class="line"></span><br><span class="line">v1, v2 = v2, v1</span><br><span class="line">fmt.Printf(<span class="string">&quot;after swap: v1 = %v (%T), v2 = %v (%T)\n&quot;</span>, v1, v1, v2, v2)</span><br><span class="line">fmt.Println()</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Integer</span></span><br><span class="line"><span class="comment">signed: int8, int16, int32, int64</span></span><br><span class="line"><span class="comment">unsigned: uint8, uint16, uint32, uint64</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> vUint16 <span class="type">uint16</span> = math.MaxUint8 + <span class="number">1</span></span><br><span class="line"><span class="comment">// vUint16 = math.MaxUint16 + 1// src\1_variables.go:37:9: constant 256 overflows uint16</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;vUint16 = %v (%T)\n&quot;</span>, vUint16, vUint16)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> vUint8 <span class="type">uint8</span> = <span class="type">uint8</span>(vUint16)</span><br><span class="line">fmt.Printf(<span class="string">&quot;vUint8 = %v (%T)\n&quot;</span>, vUint8, vUint8) <span class="comment">// truncated: 0000 0001 (0000 0000)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Float</span></span><br><span class="line"><span class="comment">float32, float64</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> vFloat32 <span class="type">float32</span> = math.E</span><br><span class="line"><span class="keyword">var</span> vFloat64 <span class="type">float64</span> = math.E</span><br><span class="line">fmt.Printf(<span class="string">&quot;vFloat32 = %f (%T)\n&quot;</span>, vFloat32, vFloat32)</span><br><span class="line">fmt.Printf(<span class="string">&quot;vFloat64 = %.10f (%T)\n&quot;</span>, vFloat64, vFloat64)</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Bool</span></span><br><span class="line"><span class="comment">true/false, can not cast to integer types</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> vBool <span class="type">bool</span> = <span class="literal">true</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;vBool = %v (%T)\n&quot;</span>, vBool, vBool)</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">String</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> vStr <span class="type">string</span> = <span class="string">&quot;你好, Go!&quot;</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;vStr = \&quot;%s\&quot; (%T)\n&quot;</span>, vStr, vStr)</span><br><span class="line">fmt.Printf(<span class="string">&quot;byte len of vStr = %v\n&quot;</span>, <span class="built_in">len</span>(vStr))                    <span class="comment">// 3*2 + 5*1 = 11, chinese character uses 3 bytes</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;rune len of vStr = %v\n&quot;</span>, utf8.RuneCountInString(vStr)) <span class="comment">// 7</span></span><br><span class="line"><span class="comment">// traverse each unicode character</span></span><br><span class="line"><span class="keyword">for</span> i, h := <span class="keyword">range</span> vStr &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;[%v:%c]&quot;</span>, i, h)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println()</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Pointer</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> ptrStr *<span class="type">string</span> = &amp;vStr</span><br><span class="line">fmt.Printf(<span class="string">&quot;ptrStr = %v (%T)\n&quot;</span>, ptrStr, ptrStr)</span><br><span class="line">fmt.Printf(<span class="string">&quot;*ptrStr = %v (%T)\n&quot;</span>, *ptrStr, *ptrStr)</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Struct</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> vStruct <span class="keyword">struct</span> &#123;</span><br><span class="line">id     <span class="type">int</span></span><br><span class="line">name   <span class="type">string</span></span><br><span class="line">salary <span class="type">float32</span></span><br><span class="line">&#125;</span><br><span class="line">vStruct.id = <span class="number">1</span></span><br><span class="line">vStruct.name = <span class="string">&quot;Mike&quot;</span></span><br><span class="line">vStruct.salary = <span class="number">123.45</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;vStruct = %v (%T)\n&quot;</span>, vStruct, vStruct)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="命令行参数flag">2 命令行参数flag</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;flag&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run with command:</span></span><br><span class="line"><span class="comment">// go run src\2_flag.go --username mike --password 123456 --id 1</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// argument name, default value, tips</span></span><br><span class="line"><span class="keyword">var</span> username *<span class="type">string</span> = flag.String(<span class="string">&quot;username&quot;</span>, <span class="string">&quot;guest&quot;</span>, <span class="string">&quot;a string of username&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> password <span class="type">string</span></span><br><span class="line">flag.StringVar(&amp;password, <span class="string">&quot;password&quot;</span>, <span class="string">&quot;123&quot;</span>, <span class="string">&quot;a string of password&quot;</span>)</span><br><span class="line"></span><br><span class="line">id := flag.Int(<span class="string">&quot;id&quot;</span>, <span class="number">0</span>, <span class="string">&quot;an integer of id&quot;</span>)</span><br><span class="line"></span><br><span class="line">flag.Parse()</span><br><span class="line">fmt.Printf(<span class="string">&quot;username = %v, password = %v, id = %v\n&quot;</span>, *username, password, *id)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="常量const">3 常量const</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with command:</span></span><br><span class="line"><span class="comment">// go run src\3_const.go</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">const</span> helloStr = <span class="string">&quot;Hello, world!&quot;</span></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">name   = <span class="string">&quot;Mike&quot;</span></span><br><span class="line">salary = <span class="number">123.45</span></span><br><span class="line">)</span><br><span class="line">fmt.Printf(<span class="string">&quot;%v (%T)&quot;</span>, name, name)</span><br><span class="line"></span><br><span class="line">name = <span class="string">&quot;Tom&quot;</span> <span class="comment">// cannot assign to name</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;%v (%T)&quot;</span>, name, name)</span><br><span class="line"></span><br><span class="line">ptrName := &amp;name <span class="comment">// cannot take the address of name</span></span><br><span class="line">*ptrName = <span class="string">&quot;Tom&quot;</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;%v (%T)&quot;</span>, name, name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="类型type">4 类型type</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with command:</span></span><br><span class="line"><span class="comment">// go run src\4_type.go</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> aliasInt = <span class="type">int</span> <span class="comment">// declare an alias for int</span></span><br><span class="line"><span class="keyword">type</span> myInt <span class="type">int</span>      <span class="comment">// defines a new type</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Person is type of a struct</span></span><br><span class="line"><span class="keyword">type</span> BasicPerson <span class="keyword">struct</span> &#123;</span><br><span class="line">name <span class="type">string</span></span><br><span class="line">age  <span class="type">uint8</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> vAliasInt aliasInt</span><br><span class="line">fmt.Printf(<span class="string">&quot;vAliasInt = %v (%T)\n&quot;</span>, vAliasInt, vAliasInt)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> vMyInt myInt</span><br><span class="line">fmt.Printf(<span class="string">&quot;vMyint = %v (%T)\n&quot;</span>, vMyInt, vMyInt)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> person BasicPerson</span><br><span class="line">person.name = <span class="string">&quot;Mike&quot;</span></span><br><span class="line">person.age = <span class="number">20</span></span><br><span class="line">fmt.Printf(<span class="string">&quot;person = %v (%T)\n&quot;</span>, person, person)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="if-else条件">5 if-else条件</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;flag&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run with command:</span></span><br><span class="line"><span class="comment">// go run src\5_ifelse.go --score 100</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">score := flag.Int(<span class="string">&quot;score&quot;</span>, <span class="number">-1</span>, <span class="string">&quot;Score of a test&quot;</span>)</span><br><span class="line"></span><br><span class="line">flag.Parse()</span><br><span class="line">fmt.Printf(<span class="string">&quot;score = %v (%T)\n&quot;</span>, *score, *score)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> *score &lt; <span class="number">60</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Fail to pass&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> *score &lt; <span class="number">80</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Fine&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> *score &lt;= <span class="number">100</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Excellent&quot;</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;Wrong score&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><br></pre></td></tr></table></figure><h2 id="switch-case条件">6 switch-case条件</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><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="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;flag&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run with command:</span></span><br><span class="line"><span class="comment">// go run src\6_switchcase.go -score 100 -course CPP</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">score := flag.Int(<span class="string">&quot;score&quot;</span>, <span class="number">-1</span>, <span class="string">&quot;Score of a test&quot;</span>)</span><br><span class="line">course := flag.String(<span class="string">&quot;course&quot;</span>, <span class="string">&quot;CPP&quot;</span>, <span class="string">&quot;Course name&quot;</span>)</span><br><span class="line">flag.Parse()</span><br><span class="line"></span><br><span class="line">fmt.Printf(<span class="string">&quot;score = %v (%T)\n&quot;</span>, *score, *score)</span><br><span class="line">fmt.Printf(<span class="string">&quot;course = %v (%T)\n&quot;</span>, *course, *course)</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> &#123;</span><br><span class="line"><span class="keyword">case</span> *score &lt; <span class="number">60</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;fail to pass&quot;</span>)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line"><span class="keyword">case</span> *score &lt; <span class="number">80</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;fine&quot;</span>)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line"><span class="keyword">case</span> *score &lt;= <span class="number">100</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;excellent&quot;</span>)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;wrong score&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> *course &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;CPP&quot;</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;C plus plus&quot;</span>)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">&quot;PY&quot;</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;Python&quot;</span>)</span><br><span class="line"><span class="keyword">break</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">fmt.Println(<span class="string">&quot;Unknown&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="for-loop循环">7 for-loop循环</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\7_forloop.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Golang only has for loop</span></span><br><span class="line"><span class="comment">// Golang does not support while and do-while loop</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> i := <span class="number">0</span>; i &lt; <span class="number">10</span>; i++ &#123;</span><br><span class="line">fmt.Println(i)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="数组array">8 数组array</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// init array</span></span><br><span class="line"><span class="keyword">var</span> colors [<span class="number">3</span>]<span class="type">string</span></span><br><span class="line">colors[<span class="number">0</span>] = <span class="string">&quot;Red&quot;</span></span><br><span class="line">colors[<span class="number">1</span>] = <span class="string">&quot;Green&quot;</span></span><br><span class="line">colors[<span class="number">2</span>] = <span class="string">&quot;Blue&quot;</span></span><br><span class="line">fmt.Println(colors)</span><br><span class="line"></span><br><span class="line"><span class="comment">// init array with initial values</span></span><br><span class="line">languages := [...]<span class="type">string</span>&#123;<span class="string">&quot;C&quot;</span>, <span class="string">&quot;C++&quot;</span>, <span class="string">&quot;Java&quot;</span>&#125;</span><br><span class="line">fmt.Println(languages)</span><br><span class="line"></span><br><span class="line"><span class="comment">// init array with new(type), returns a pointer</span></span><br><span class="line">nations := <span class="built_in">new</span>([<span class="number">3</span>]<span class="type">string</span>)</span><br><span class="line">nations[<span class="number">0</span>] = <span class="string">&quot;China&quot;</span></span><br><span class="line">nations[<span class="number">1</span>] = <span class="string">&quot;India&quot;</span></span><br><span class="line">nations[<span class="number">2</span>] = <span class="string">&quot;Japan&quot;</span></span><br><span class="line">fmt.Println(*nations)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="切片slice">9 切片slice</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\9_slice.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// slice is a variable length sequence of data</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// [...]int&#123;&#125; marks a fixed length array,</span></span><br><span class="line"><span class="comment">// while []int&#123;&#125; declares a variable length slice</span></span><br><span class="line">slice := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>&#125;</span><br><span class="line">printSlice(<span class="string">&quot;slice&quot;</span>, slice)</span><br><span class="line">printSlice(<span class="string">&quot;subSlice&quot;</span>, slice[<span class="number">3</span>:])</span><br><span class="line"></span><br><span class="line"><span class="comment">// slice can be appended with one or more elements</span></span><br><span class="line"><span class="comment">// If it has sufficient capacity, the destination is resliced to accommodate the new elements.</span></span><br><span class="line"><span class="comment">// If it does not, a new underlying array will be allocated.</span></span><br><span class="line">slice = <span class="built_in">append</span>(slice, <span class="number">7</span>, <span class="number">8</span>)</span><br><span class="line">printSlice(<span class="string">&quot;slice&quot;</span>, slice)</span><br><span class="line">printSlice(<span class="string">&quot;subSlice&quot;</span>, slice[<span class="number">3</span>:])</span><br><span class="line"></span><br><span class="line"><span class="comment">// slice can also be created from an array</span></span><br><span class="line">arr := [...]<span class="type">int</span>&#123;<span class="number">11</span>, <span class="number">22</span>, <span class="number">33</span>, <span class="number">44</span>, <span class="number">55</span>&#125;</span><br><span class="line">arrSlice := arr[:<span class="number">3</span>]</span><br><span class="line">printSlice(<span class="string">&quot;arrSlice&quot;</span>, arrSlice)</span><br><span class="line"></span><br><span class="line"><span class="comment">// slice can also be dynamically created with make([]T, size, cap)</span></span><br><span class="line">madeSlice := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">8</span>, <span class="number">16</span>)</span><br><span class="line">printSlice(<span class="string">&quot;makeSlice&quot;</span>, madeSlice)</span><br><span class="line"></span><br><span class="line"><span class="comment">// slice can be copied from src slice to dest slice</span></span><br><span class="line"><span class="built_in">copy</span>(madeSlice, slice)</span><br><span class="line">printSlice(<span class="string">&quot;copiedSlice&quot;</span>, madeSlice)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printSlice</span><span class="params">(tag <span class="type">string</span>, slice []<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;%s: date = %v, len = %d, cap = %d, addr = %p\n&quot;</span>, tag, slice, <span class="built_in">len</span>(slice), <span class="built_in">cap</span>(slice), &amp;slice)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="列表list">10 列表list</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;container/list&quot;</span></span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\10_list.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// list.List in go is a doubly linked list</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// create a list</span></span><br><span class="line">numsList := list.New()</span><br><span class="line"></span><br><span class="line"><span class="comment">// append elements to list with PushBack</span></span><br><span class="line"><span class="keyword">for</span> i := <span class="number">1</span>; i &lt; <span class="number">10</span>; i++ &#123;</span><br><span class="line">numsList.PushBack(i)</span><br><span class="line">&#125;</span><br><span class="line">printList(numsList)</span><br><span class="line"></span><br><span class="line"><span class="comment">// add elements to front with PushFront</span></span><br><span class="line">first := numsList.PushFront(<span class="number">0</span>)</span><br><span class="line">printList(numsList)</span><br><span class="line"></span><br><span class="line"><span class="comment">// elements can be removed</span></span><br><span class="line">numsList.Remove(first)</span><br><span class="line">printList(numsList)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printList</span><span class="params">(srcList *list.List)</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> node := srcList.Front(); node != <span class="literal">nil</span>; node = node.Next() &#123;</span><br><span class="line">fmt.Print(node.Value, <span class="string">&quot; &quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="字典map">11 字典map</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\11_map.go</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// create a map</span></span><br><span class="line">emptyMap := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>&#123;&#125;</span><br><span class="line">printMap(emptyMap)</span><br><span class="line"></span><br><span class="line"><span class="comment">// create and init a map</span></span><br><span class="line">city2id := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>&#123;</span><br><span class="line"><span class="string">&quot;Suzhou&quot;</span>:   <span class="string">&quot;0512&quot;</span>,</span><br><span class="line"><span class="string">&quot;Beijing&quot;</span>:  <span class="string">&quot;010&quot;</span>,</span><br><span class="line"><span class="string">&quot;Shanghai&quot;</span>: <span class="string">&quot;021&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">printMap(city2id)</span><br><span class="line"></span><br><span class="line"><span class="comment">// create a map with make(type)</span></span><br><span class="line">id2city := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span><br><span class="line"></span><br><span class="line">id2city[<span class="string">&quot;0512&quot;</span>] = <span class="string">&quot;Suzhou&quot;</span></span><br><span class="line">id2city[<span class="string">&quot;010&quot;</span>] = <span class="string">&quot;Beijing&quot;</span></span><br><span class="line">id2city[<span class="string">&quot;021&quot;</span>] = <span class="string">&quot;Shanghai&quot;</span></span><br><span class="line"></span><br><span class="line">fmt.Printf(<span class="string">&quot;Query: 010, Result: %s\n&quot;</span>, id2city[<span class="string">&quot;010&quot;</span>])</span><br><span class="line">printMap(id2city)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printMap</span><span class="params">(srcMap <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;len = %d\n&quot;</span>, <span class="built_in">len</span>(srcMap))</span><br><span class="line"><span class="keyword">for</span> k, v := <span class="keyword">range</span> srcMap &#123;</span><br><span class="line">fmt.Println(k, v, <span class="string">&quot; &quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="函数func">12 函数func</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"><span class="string">&quot;fmt&quot;</span></span><br><span class="line"><span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\12_func.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">A standard paradigm of function in go:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">func func_name(inputParams) (returnParams) &#123;</span></span><br><span class="line"><span class="comment">func body</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// a func with multiple inputs and single output</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">add</span><span class="params">(x, y <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> x + y</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// a func with multiple inputs and multiple outputs</span></span><br><span class="line"><span class="comment">// return values can be named</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">div</span><span class="params">(dividend, divisor <span class="type">int</span>)</span></span> (quotient, remainder <span class="type">int</span>) &#123;</span><br><span class="line">quotient = dividend / divisor</span><br><span class="line">remainder = dividend % divisor</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">addMul</span><span class="params">(x, y <span class="type">int</span>)</span></span> (<span class="type">int</span>, <span class="type">int</span>) &#123;</span><br><span class="line">vAdd := x + y</span><br><span class="line">vMul := x * y</span><br><span class="line"><span class="keyword">return</span> vAdd, vMul</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// a function with input but no return value</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">echo</span><span class="params">(s <span class="type">string</span>)</span></span> &#123;</span><br><span class="line">fmt.Println(s)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// input a func as a callback func</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">traverse</span><span class="params">(arr []<span class="type">int</span>, handler <span class="keyword">func</span>(num <span class="type">int</span>)</span></span>) &#123;</span><br><span class="line"><span class="keyword">for</span> _, v := <span class="keyword">range</span> arr &#123;</span><br><span class="line">handler(v)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// pass a pointer to func</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">increase</span><span class="params">(x *<span class="type">int</span>)</span></span> &#123;</span><br><span class="line">*x = <span class="number">2</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// call a named function</span></span><br><span class="line">fmt.Println(add(<span class="number">1</span>, <span class="number">2</span>))</span><br><span class="line">fmt.Println(div(<span class="number">8</span>, <span class="number">5</span>))</span><br><span class="line">fmt.Println(addMul(<span class="number">2</span>, <span class="number">3</span>))</span><br><span class="line">echo(<span class="string">&quot;Hello, world!&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// define and call an anonymous func</span></span><br><span class="line">vMul := <span class="function"><span class="keyword">func</span><span class="params">(x, y <span class="type">int</span>)</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">return</span> x - y</span><br><span class="line">&#125;(<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line">fmt.Println(vMul)</span><br><span class="line"></span><br><span class="line">curTime := <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">fmt.Println(time.Now())</span><br><span class="line">&#125;</span><br><span class="line">curTime()</span><br><span class="line"></span><br><span class="line"><span class="comment">// use an anonymous func as callback function</span></span><br><span class="line">arr := []<span class="type">int</span>&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;</span><br><span class="line">traverse(arr, <span class="function"><span class="keyword">func</span><span class="params">(num <span class="type">int</span>)</span></span> &#123;</span><br><span class="line">fmt.Print(num*num, <span class="string">&quot; &quot;</span>)</span><br><span class="line">&#125;)</span><br><span class="line">fmt.Println()</span><br><span class="line"></span><br><span class="line"><span class="comment">// pass num to func by its pointer</span></span><br><span class="line">vNum := <span class="number">1</span></span><br><span class="line">fmt.Println(<span class="string">&quot;Before: &quot;</span>, vNum)</span><br><span class="line">increase(&amp;vNum)</span><br><span class="line">fmt.Println(<span class="string">&quot;After: &quot;</span>, vNum)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="闭包closure">13 闭包closure</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\13_closure.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// closure is a function carrying state</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createCounter</span><span class="params">(initial <span class="type">int</span>)</span></span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line"><span class="keyword">if</span> initial &lt; <span class="number">0</span> &#123;</span><br><span class="line">initial = <span class="number">0</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="type">int</span> &#123;</span><br><span class="line">initial++</span><br><span class="line"><span class="keyword">return</span> initial</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">counter1 := createCounter(<span class="number">0</span>)</span><br><span class="line">fmt.Println(counter1()) <span class="comment">// 1</span></span><br><span class="line">fmt.Println(counter1()) <span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line">counter2 := createCounter(<span class="number">10</span>)</span><br><span class="line">fmt.Println(counter2()) <span class="comment">// 11</span></span><br><span class="line"></span><br><span class="line">fmt.Println(counter1()) <span class="comment">// 3</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="结构体struct">14 结构体struct</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><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="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\14_struct.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">struct can be defined in paradigm:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">type structName struct &#123;</span></span><br><span class="line"><span class="comment">value1 valueType1</span></span><br><span class="line"><span class="comment">value2 valueType2</span></span><br><span class="line"><span class="comment">...</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Person <span class="keyword">struct</span> &#123;</span><br><span class="line">Name  <span class="type">string</span></span><br><span class="line">Birth <span class="type">string</span></span><br><span class="line">ID    <span class="type">uint64</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// declare a struct variable</span></span><br><span class="line"><span class="keyword">var</span> person1 Person</span><br><span class="line">person1.Name = <span class="string">&quot;Mike&quot;</span></span><br><span class="line">person1.Birth = <span class="string">&quot;1990-1-2&quot;</span></span><br><span class="line">person1.ID = <span class="number">1</span></span><br><span class="line">fmt.Println(person1)</span><br><span class="line"></span><br><span class="line"><span class="comment">// new a struct variable</span></span><br><span class="line">person2 := <span class="built_in">new</span>(Person) <span class="comment">// person2 is a pointer</span></span><br><span class="line">person2.Name = <span class="string">&quot;Tom&quot;</span></span><br><span class="line">person2.Birth = <span class="string">&quot;1991-2-3&quot;</span></span><br><span class="line">person2.ID = <span class="number">2</span></span><br><span class="line">fmt.Println(person2)</span><br><span class="line"></span><br><span class="line"><span class="comment">// another way to new a person with empty initial values</span></span><br><span class="line">person3 := &amp;Person&#123;&#125; <span class="comment">// person3 is a pointer</span></span><br><span class="line">person3.Name = <span class="string">&quot;Nancy&quot;</span></span><br><span class="line">person3.Birth = <span class="string">&quot;1992-3-4&quot;</span></span><br><span class="line">person3.ID = <span class="number">3</span></span><br><span class="line">fmt.Println(person3)</span><br><span class="line"></span><br><span class="line"><span class="comment">// create an object with initial values</span></span><br><span class="line">person4 := Person&#123;</span><br><span class="line">Name:  <span class="string">&quot;Jack&quot;</span>,</span><br><span class="line">Birth: <span class="string">&quot;1993-4-5&quot;</span>,</span><br><span class="line">ID:    <span class="number">4</span>,</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(person4)</span><br><span class="line"></span><br><span class="line">person5 := &amp;Person&#123; <span class="comment">// person5 is a pointer</span></span><br><span class="line">Name:  <span class="string">&quot;John&quot;</span>,</span><br><span class="line">Birth: <span class="string">&quot;1994-5-6&quot;</span>,</span><br><span class="line">ID:    <span class="number">5</span>,</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(person5)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="方法method">15 方法method</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><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="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\15_method.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">In go, method is a function with recipient.</span></span><br><span class="line"><span class="comment">Recipient can be any type, typically a struct, which means any type in</span></span><br><span class="line"><span class="comment">go can have its methods.</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">Method can be defined in paradigm:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">func (recipient RecipientType) methodName(inputParams) (returnParams) &#123;</span></span><br><span class="line"><span class="comment">func body</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Student <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">Age  <span class="type">uint8</span></span><br><span class="line">ID   <span class="type">uint64</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// modify student&#x27;s name with pointer to the instance</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(student *Student)</span></span> setName(name <span class="type">string</span>) &#123;</span><br><span class="line">student.Name = name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// non-pointer, unable to modify the original instance</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(student Student)</span></span> badSet(name <span class="type">string</span>) &#123;</span><br><span class="line">student.Name = name</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(student Student)</span></span> <span class="built_in">print</span>() &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;Student %s (ID: %v) is %v years old.\n&quot;</span>,</span><br><span class="line">student.Name, student.ID, student.Age)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">student1 := Student&#123;</span><br><span class="line">Name: <span class="string">&quot;Jack&quot;</span>,</span><br><span class="line">Age:  <span class="number">12</span>,</span><br><span class="line">ID:   <span class="number">1</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">student1.<span class="built_in">print</span>()</span><br><span class="line"></span><br><span class="line">student1.badSet(<span class="string">&quot;Little Jack&quot;</span>)</span><br><span class="line">student1.<span class="built_in">print</span>()</span><br><span class="line"></span><br><span class="line">student1.setName(<span class="string">&quot;Big Jack&quot;</span>)</span><br><span class="line">student1.<span class="built_in">print</span>()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="接口interface">16 接口interface</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\16_interface1.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">standard interface paradigm:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">type interfaceName interface &#123;</span></span><br><span class="line"><span class="comment">func1(inputParams) (returnParams)</span></span><br><span class="line"><span class="comment">func2(inputParams) (returnParams)</span></span><br><span class="line"><span class="comment">func3(inputParams) (returnParams)</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">If the interfaceName is in uppercase, its a public interface.</span></span><br><span class="line"><span class="comment">If the function name is in uppercase, its a public function.</span></span><br><span class="line"><span class="comment">A public function can be accessed outside of the package,</span></span><br><span class="line"><span class="comment">otherwise, non-public function can only be accessed inside of</span></span><br><span class="line"><span class="comment">the package.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Cat <span class="keyword">interface</span> &#123;</span><br><span class="line">CatchMouse()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Dog <span class="keyword">interface</span> &#123;</span><br><span class="line">Bark()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CatDog <span class="keyword">struct</span> &#123;</span><br><span class="line">Name <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// type CatDog implements functions in interface Cat</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(catDog *CatDog)</span></span> CatchMouse() &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;%s is catching mice!\n&quot;</span>, catDog.Name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// type CatDog implements functions in interface Dog</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(catDog *CatDog)</span></span> Bark() &#123;</span><br><span class="line">fmt.Printf(<span class="string">&quot;%s is barking!\n&quot;</span>, catDog.Name)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// catDog is a pointer to CatDog instance</span></span><br><span class="line">catDog := &amp;CatDog&#123;</span><br><span class="line">Name: <span class="string">&quot;Tom&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// declare Cat interface and point to CatDog type</span></span><br><span class="line"><span class="keyword">var</span> cat Cat</span><br><span class="line">cat = catDog</span><br><span class="line">cat.CatchMouse()</span><br><span class="line"></span><br><span class="line"><span class="comment">// declare Dog interface and point to CatDog type</span></span><br><span class="line"><span class="keyword">var</span> dog Dog</span><br><span class="line">dog = catDog</span><br><span class="line">dog.Bark()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>另</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><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="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\16_interface2.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">standard interface paradigm:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">type interfaceName interface &#123;</span></span><br><span class="line"><span class="comment">func1(inputParams) (returnParams)</span></span><br><span class="line"><span class="comment">func2(inputParams) (returnParams)</span></span><br><span class="line"><span class="comment">func3(inputParams) (returnParams)</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">If the interfaceName is in uppercase, its a public interface.</span></span><br><span class="line"><span class="comment">If the function name is in uppercase, its a public function.</span></span><br><span class="line"><span class="comment">A public function can be accessed outside of the package,</span></span><br><span class="line"><span class="comment">otherwise, non-public function can only be accessed inside of</span></span><br><span class="line"><span class="comment">the package.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Printer <span class="keyword">interface</span> &#123;</span><br><span class="line">Print(<span class="keyword">interface</span>&#123;&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> FuncCaller <span class="function"><span class="keyword">func</span><span class="params">(p <span class="keyword">interface</span>&#123;&#125;)</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(funcCaller FuncCaller)</span></span> Print(p <span class="keyword">interface</span>&#123;&#125;) &#123;</span><br><span class="line">funcCaller(p)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="comment">// Printer is the abstraction of printer</span></span><br><span class="line"><span class="comment">// FuncCaller func is the implementation of printer</span></span><br><span class="line"><span class="comment">// printer can call Printer.Print implemented by FuncCaller&#x27;s Print</span></span><br><span class="line"><span class="keyword">var</span> printer Printer</span><br><span class="line">printer = FuncCaller(<span class="function"><span class="keyword">func</span><span class="params">(p <span class="keyword">interface</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">fmt.Println(p)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">// cast an anonymous function to FuncCaller type</span></span><br><span class="line"><span class="comment">// then printer calls Print implemented by FuncCaller</span></span><br><span class="line">printer.Print(<span class="string">&quot;Golang is Good!&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="嵌入embedding">17 嵌入embedding</h2><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><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">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// run with commands:</span></span><br><span class="line"><span class="comment">// go run src\16_interface2.go</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">struct can embed anonymous attributes (type-only) to implement composition relation.</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">standard embedded struct type paradigm:</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">type A struct &#123;</span></span><br><span class="line"><span class="comment">typeB</span></span><br><span class="line"><span class="comment">typeC</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Swimming <span class="keyword">struct</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(swim *Swimming)</span></span> swim() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;swimming&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Flying <span class="keyword">struct</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(flying *Flying)</span></span> fly() &#123;</span><br><span class="line">fmt.Println(<span class="string">&quot;flying&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Wild Duck can swim and fly</span></span><br><span class="line"><span class="keyword">type</span> WildDuck <span class="keyword">struct</span> &#123;</span><br><span class="line">Swimming</span><br><span class="line">Flying</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Domestic Duck can only swim</span></span><br><span class="line"><span class="keyword">type</span> DomesticDuck <span class="keyword">struct</span> &#123;</span><br><span class="line">Swimming</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">wildDuck := WildDuck&#123;&#125;</span><br><span class="line">wildDuck.fly()</span><br><span class="line">wildDuck.swim()</span><br><span class="line"></span><br><span class="line">domesticDuck := DomesticDuck&#123;&#125;</span><br><span class="line">domesticDuck.swim()</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;抽空学习Go语言。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Golang" scheme="https://heary.cn/tags/Golang/"/>
    
  </entry>
  
  <entry>
    <title>Linux环境下重装NVIDIA驱动报错kernel module (nvidia_modeset) in use问题分析</title>
    <link href="https://heary.cn/posts/Linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E9%87%8D%E8%A3%85NVIDIA%E9%A9%B1%E5%8A%A8%E6%8A%A5%E9%94%99kernel-module-nvidia-modeset-in-use%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90/"/>
    <id>https://heary.cn/posts/Linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E9%87%8D%E8%A3%85NVIDIA%E9%A9%B1%E5%8A%A8%E6%8A%A5%E9%94%99kernel-module-nvidia-modeset-in-use%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90/</id>
    <published>2020-10-08T14:59:47.000Z</published>
    <updated>2022-08-07T04:02:09.609Z</updated>
    
    <content type="html"><![CDATA[<p>Linux环境下重装NVIDIA驱动时，遭遇报错kernel module (nvidia_modeset)inuse。本文排查问题原因，并由此给出了无需重启系统也可正常重装的解决方案。</p><span id="more"></span><h1id="linux环境下重装nvidia驱动报错kernel-module-nvidia_modeset-in-use问题分析">Linux环境下重装NVIDIA驱动报错kernelmodule (nvidia_modeset) in use问题分析</h1><h2 id="问题描述">1 问题描述</h2><p>在Linux环境下重装NVIDIA驱动时，出现报错，原因是内核模块正在使用中<code>kernel module (nvidia_modeset) in use</code>，导致无法安装新驱动。</p><p>NVIDIA驱动安装的报错页面给出的解决方案是重启一下（reboot）即可。但如果是服务器环境下，有其他用户的计算任务在执行，不希望打断，能否避免重启呢？</p><p>不知道原因的情况下，直接使用<code>rmmod nvidia_modeset</code>卸载该内核模块时，会遭遇报错，因为正在被占用而导致无法卸载。而<code>rmmod -f</code>的强制卸载又存在风险，可能造成系统崩溃（systemcrash）。</p><h2 id="原因分析">2 原因分析</h2><p>照理说，老驱动已经卸载，那么不应该存在驱动相关的内核模块仍被使用的情况。</p><p>根据提示，既然是内核模块被占用的问题，那首先通过<code>lsmod</code>检查内核模块的使用情况，可以查到类似的引用关系：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Module                  Size  Used by</span><br><span class="line">nvidia_modeset       1183744  1</span><br><span class="line">nvidia              19722240  1 nvidia_modeset</span><br></pre></td></tr></table></figure><p>从中可以发现，内核模块<code>nvidia_modeset</code>依赖于内核模块<code>nvidia</code>。</p><p>通过进一步检查nvidia相关进程<code>ps -aux | grep nvidia</code>，发现实际上是nvidia的persistencemode的守护进程占用了内核模块nvidia_modeset。而之所以有这样一个守护进程，是为了避免nvidia-smi每次唤起过慢的问题，即，通过设置<code>sudo nvidia-persistenced --persistence-mode</code>启用persistencemode，借助守护进程来维护记录GPU的状态，避免每次nvidia-smi都需要同步检查每一个GPU状态在阻塞等待上耗费太多时间。</p><h2 id="解决方案">3 解决方案</h2><p>查出了原因，再想办法解决就容易了。</p><p>首先，通过<code>ps -aux | grep nvidia</code>找出使用<code>nvidia_modeset</code>的进程。</p><p>随后，通过<code>sudo kill [pid]</code>结束该persistencemode的守护进程。</p><p>通过<code>ps</code>进行验证，等待进程结束后，再检查<code>lsmod</code>就可以发现<code>nvidia_modeset</code>不再被占用了。</p><p>此时，通过<code>rmmod</code>卸载残余的nvidia内核模块，就不会再有报错了。</p><p>如此清理完内核模块后，重新执行NVIDIA驱动安装程序，即一切正常了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Linux环境下重装NVIDIA驱动时，遭遇报错kernel module (nvidia_modeset)
in
use。本文排查问题原因，并由此给出了无需重启系统也可正常重装的解决方案。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Troubleshooting" scheme="https://heary.cn/tags/Troubleshooting/"/>
    
    <category term="Linux" scheme="https://heary.cn/tags/Linux/"/>
    
    <category term="NVIDIA" scheme="https://heary.cn/tags/NVIDIA/"/>
    
  </entry>
  
  <entry>
    <title>Redis笔记</title>
    <link href="https://heary.cn/posts/Redis%E7%AC%94%E8%AE%B0/"/>
    <id>https://heary.cn/posts/Redis%E7%AC%94%E8%AE%B0/</id>
    <published>2020-08-19T12:29:13.000Z</published>
    <updated>2022-08-07T04:02:09.616Z</updated>
    
    <content type="html"><![CDATA[<p>Redis作为一款高效的键值型内存数据库，值得学习。本文梳理学习过程中的笔记。</p><span id="more"></span><h1 id="redis笔记">Redis笔记</h1><blockquote><p>钱文品. Redis深度历险：核心原理与应用实践[M]. 北京: 电子工业出版社,2019.</p></blockquote><h2 id="概述">1 概述</h2><h3 id="了解">1.1 了解</h3><p>Redis主要可以用作：</p><ul><li>缓存：记录点赞数、缓存热帖、用户行为、榜单……；</li><li>分布式锁</li></ul><h3 id="安装">1.2 安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># ubuntu</span></span><br><span class="line">sudo apt install redis</span><br><span class="line"></span><br><span class="line"><span class="comment"># centos</span></span><br><span class="line">sudo yum install redis</span><br></pre></td></tr></table></figure><h3 id="运行">1.3 运行</h3><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="comment"># redis command-line interface</span></span><br><span class="line">redis-cli</span><br></pre></td></tr></table></figure><h2 id="数据结构">2 数据结构</h2><p>Redis提供5种基础数据结构，分别为：字符串string、列表list、字典hash、集合set、有序集合zset。</p><h3 id="字符串string">2.1 字符串string</h3><p>Redis所有数据结构都以唯一的key字符串作为名称，以此获得相应的value数据。</p><p>Redisstring内部数据结构类似Java的ArrayList，预分配冗余空间以免频繁分配内存。当字符串小于1MB时，扩容方法为加倍当前容量；当超过1MB时，每次扩充1MB空间。字符串最大长度为512MB。</p><h3 id="列表list">2.2 列表list</h3><p>Redislist类似Java中的LinkedList链表（实际上不完全是），双向链表，插入和删除时间复杂度O(1)，查询时间复杂度O(N)。</p><p>当list删除最后一个元素时，该数据结构被自动删除，内存回收。</p><p>双向链表可以被用来实现队列、栈。</p><h4 id="底层实现ziplist与quicklist">2.2.1底层实现——ziplist与quicklist</h4><p>Redis list底层实现是quicklist数据结构。</p><p>当list元素较少时，采用ziplist（压缩列表）。ziplist用连续内存将所有的元素连续存储。</p><p>当list元素较多时，采用quicklist（快速链表）。quicklist是将链表与ziplist结合的产物，每一个ziplist包含多个元素，却仅需两个前后指针，因此，quicklist避免了为每个元素配备prev/next双指针的空间消耗。quicklist既满足了快速的插入和删除，又避免了产生较大的空间冗余。</p><h3 id="字典hash">2.3 字典hash</h3><p>Redis hash类似Java中的HashMap，无序字典，存储键值对。</p><p>hash采用数组+链表的数据结构，但hash的值只能是字符串。</p><p>当hash删除最后一个元素时，该数据结构被自动删除，内存回收。</p><h4 id="渐进式rehash">2.3.1 渐进式rehash</h4><p>Java的HashMap每次rehash需要一次性全部rehash，而Redis的hash在rehash时，为了避免阻塞服务，采用渐进式rehash。</p><p>渐进式rehash在rehash时，保留新旧两个hash结构。旧的hashtable仍可用作查询，同时将旧的hashtable持续rehash到新的hashtable上。等rehash全部完成后，才以新的hashtable取代旧的hashtable。</p><h3 id="集合set">2.4 集合set</h3><p>Redis set相当于Java中的HashSet，内部的键值对时无序的、唯一的。</p><p>set的底层实现相当于是hash，只不过hash的value村的都是NULL。</p><p>当set删除最后一个元素时，该数据结构被自动删除，内存回收。</p><h3 id="有序集合zset">2.5 有序集合zset</h3><p>Redis zset类似Java的SortedSet和HashMap的结合体。</p><p>zset一方面是set，value元素是唯一的，另一方面其有序性是依靠为value赋予score作为排序权重实现的。</p><p>当zset删除最后一个元素时，该数据结构被自动删除，内存回收。</p><h4 id="跳跃列表skiplist">2.5.1 跳跃列表skiplist</h4><p>zset内部的排序功能采用skiplist实现。</p><p>skiplist中，高层（level）链表跨度大，连接比较大的跨度范围。越往底层跨度越小，表示比较小的跨度范围。通过skiplist，可以从大范围缩小到小范围，快速定位插入与查询的位置。</p><h3 id="小结">2.6 小结</h3><h4 id="通用性质">2.6.1 通用性质</h4><p>list, set, hash, zset这四种容器数据结构具有两个通用性质：</p><ol type="1"><li>create if not exists：操作时，如容器不存在，则新建；</li><li>drop if not elements：操作时，如容器为空，则删除。</li></ol><h4 id="过期时间">2.6.2 过期时间</h4><p>所有数据结构都可设置过期时间，过期则删除。</p><p>字符串设置过期时间后，如果字符串被修改，则过期时间失效。</p><h2 id="应用">3 应用</h2><h3 id="分布式锁">3.1 分布式锁</h3><p>setnx (set if not exists)指令做锁标记，del删除锁标记。</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">&gt; setnx lock:resource_a <span class="literal">true</span></span><br><span class="line">&gt; expire lock:resource_a 5</span><br><span class="line">&gt; del lock:resource_a</span><br></pre></td></tr></table></figure><ul><li>事务需要判单自己能够设置对争用资源的分布式锁，才能修改资源。</li><li>expire设置5秒过期时间，防止死锁。</li></ul><h3 id="延时队列">3.2 延时队列</h3><p>list可以作为异步消息队列。</p><p>rpush/lpush操作入队列，lpop/rpop操作出队列。</p><p>blpop/brpop可以阻塞式（blocking）地读取数据。</p><h3 id="位图">3.3 位图</h3><p>get/set处理整个位图的内容。</p><p>getbit/setbit处理各个位。</p><p>bitcount统计范围内1的位数。</p><p>bitpos查询第一个0或1的位置。</p><p>bitfield，包含get/set/incrby子指令，可以读取、设置和自增指定范围的位。bitfield可以混合多个子指令执行。</p><h3 id="hyperloglog">3.4 HyperLogLog</h3><p>统计PV量无需去重，incrby自增就可以。统计UV则需要去重，不是简单的自增，去重常用的set集合在数据量很大时会消耗巨大的内存空间。</p><p>HyperLogLog可以实现去重计数问题。</p><p>pfadd添加元素（增加对该元素的计数）；</p><p>pfcount统计元素的计数。</p><p>pfmerge用于合并多个pf计数元素为同一个元素，合并pf计数值。</p><p>pf指的是HyperLogLog的发明人Philippe Flajolet教授。</p><p>HyperLogLog数据结构在计数较小时采用稀疏矩阵存储，在计数超过阈值时，转变为稠密矩阵。</p><p>HyperLogLog占据12KB存储空间，在数据量很大时，比set小了太多。</p><p>HyperLogLog的原理是调整低位连续零位的最大长度K，若K越大，概率越低，则说明计数N越大，由此通过有限的连续零位K来估算计数N，K与N存在线性相关性。占用12KB则是因为Redis的HyperLogLog实现采用<spanclass="math inline">\(2^{14}=16384\)</span>个桶，每个桶maxbit为6bit，因此<span class="math inline">\(2^{14} \times 6bit \div 8bit/byte =12KB\)</span>。</p><h3 id="布隆过滤器bloom-filter">3.5 布隆过滤器（Bloom Filter）</h3><blockquote><p><a href="https://github.com/RedisBloom/RedisBloom">RedisBloom</a></p><p><strong>RedisBloom: Probabilistic Data Structures forRedis</strong></p><p>The RedisBloom module provides four data structures: a scalable<strong>Bloom filter</strong>, a <strong>cuckoo filter</strong>, a<strong>count-min sketch</strong>, and a <strong>top-k</strong>. Thesedata structures trade perfect accuracy for extreme memory efficiency, sothey're especially useful for big data and streaming applications.</p></blockquote><p>bf.add添加元素；</p><p>bf.exists检查元素是否存在。</p><p>bf.madd添加多个元素；</p><p>bf.mexists检查多个元素是否存在。（返回分别表示每个元素存在性的0/1）</p><p>bf.reverse在添加元素之前预设布隆过滤器的key,error_rate和initial_size。</p><p>布隆过滤器：</p><ul><li>添加时计算元素k个哈希，将对应的k个bit置为1；</li><li>检查存在时计算元素k个哈希，检查对应的k个bits是否都为1，如果是，则元素存在，否则不存在。</li></ul><p>对hash函数数量k，布隆过滤器bit数量m，预计元素数量n，错误率f，有公式：<span class="math display">\[k = \ln2 \times (m/n) =  0.7 \times (m / n) \\f = 2^{-k} =  0.6185^{m/n}\]</span></p><ul><li>此时错误率最低。</li></ul><h3 id="简单限流">3.6 简单限流</h3><p>以zset的score范围来划定滑动窗口。score存储timestamp，这样就可以计算得出时间窗口内的元素数量，判断访问计数是否超限。</p><p>zset不适合数量很大的限流，例如：60秒内限流100万次，100万个元素的zset会占用过大的空间。</p><h3 id="漏斗限流">3.7 漏斗限流</h3><blockquote><p><a href="https://github.com/brandur/redis-cell">redis-cell</a></p><p>A Redis module that provides rate limiting in Redis as a singlecommand. Implements the fairly sophisticated <ahref="https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm">genericcell rate algorithm</a> (GCRA) which provides a rolling time window anddoesn't depend on a background drip process.</p></blockquote><p>漏斗（funnel）容量有限，不满时可以装入液体，漏斗满时无法装入液体，需要等漏斗内的液体慢慢流走一部分，才能继续装入。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">CL.THROTTLE user123 15 30 60 1</span><br><span class="line">               ▲     ▲  ▲  ▲ ▲</span><br><span class="line">               |     |  |  | └───── apply 1 token (default if omitted)</span><br><span class="line">               |     |  └──┴─────── 30 tokens / 60 seconds</span><br><span class="line">               |     └───────────── 15 max_burst</span><br><span class="line">               └─────────────────── key &quot;user123&quot;</span><br></pre></td></tr></table></figure><h3 id="geohash">3.8 GeoHash</h3><p>通过GeoHash功能，可以快速找出指定经纬度周围的元素。</p><p>GeoHash将二维平面处理成网格，然后不断地行、列二分，对二维坐标进行编码，映射为一维整数。</p><p>Redis中，GeoHash将经纬度编码为52位整数，存入zset中，score是经纬度编码整数（zset的浮点数score可以无损存储52位整数），value是元素值。在zset中，借助skiplist来找出元素附近范围的其他元素是很容易的事情。使用坐标时，将编码整数解码还原为坐标即可。</p><p>geoadd添加经纬度坐标；</p><p>geodist计算元素之间的距离；</p><p>geopos读取元素的坐标；</p><p>geohash读取元素的经纬度编码字符串（base32编码的坐标值）。</p><p>georadiusbymember查询指定元素附近的其他元素。</p><p>注意：集群中，单个key下存储的坐标数量不宜过多（超过1MB），避免集群迁移出现卡顿。或者干脆采用独立实例，不做集群。</p><h3 id="scan">3.9 scan</h3><p>keys列出符合pattern的key，采用遍历算法，时间复杂度O(N)。</p><p>scan从指定cursor开始，匹配pattern，扫描count个槽位。相较于keys，scan可以避免每次遍历整个redis内存槽。</p><p>Redis本身就相当于是一个很大的HashMap。scan的遍历顺序采用高位进位加法，以此避免字典扩容和缩容时重复或遗漏遍历槽位。</p><p>zscan遍历zset元素；</p><p>sscan遍历set元素；</p><p>hscan遍历hash元素。</p><h3 id="避免bigkey">3.10 避免bigkey</h3><p>在业务开发中，避免大key的产生。</p><p>大key数据不论是在集群迁移时，还是在容器需要扩容时，哪怕是在回收时（因较大内存空间的分配和回收），都容易造成卡顿。</p><p>可以采用<code>--bigkeys</code>选项来检索大key。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">redis-cli --bigkeys</span><br></pre></td></tr></table></figure><h2 id="原理">4 原理</h2><h3 id="io模型">4.1 I/O模型</h3><p>Redis是单线程程序。</p><p>Redis通过非阻塞I/O多路复用技术来提高单线程I/O处理效率。</p><p>对于每一个客户端socket连接，Redis为其关联：</p><ol type="1"><li>一个指令队列，用于从客户端socket连接中读取指令。指令队列中的指令遵循FCFS；</li><li>一个响应队列，用于向客户端socket连接中写入指令。如果响应队列为空，说明暂无响应数据，则将该响应队列移出多路复用的write_fds以节省select代价。</li></ol><p>对于定时任务，Redis采用最小堆进行管理：</p><ul><li>最临近的任务放在堆顶；</li><li>取堆顶任务的距离时间作为select操作的timeout，这样在这段时间内就可以放心地select，不必担心错过定时任务。</li></ul><h3 id="通信协议">4.2 通信协议</h3><p>RESP (Redis SerializationProtocol)是Redis采用地通信协议，这是一种文本协议，实现简单，解析性能好。</p><p>RESP把数据分为5种最小单元类型，制定规则：</p><ol type="1"><li>单行字符串，以<code>+</code>开头；</li><li>多行字符串，以<code>$&lt;len&gt;</code>开头；</li><li>整数，以<code>:</code>开头；</li><li>错误消息，以<code>-</code>开头；</li><li>数组，以<code>*&lt;len&gt;</code>开头。</li></ol><h3 id="持久化">4.3 持久化</h3><h4 id="快照">4.3.1 快照</h4><p>Redis采用fork机制创建子进程来导出快照。</p><p>内存空间采用COW机制，因此，父进程照常处理事务，修改的数据会记录在新的空间中，而子进程看到的仍然是fork时的内存数据，不用担心导出时数据又被更新的情况。</p><h4 id="aof日志">4.3.2 AOF日志</h4><p>AOF日志记录Redis实例创建以来所有的修改性指令序列。</p><p>Redis收到客户端修改指令后，进行检查和处理，如果指令执行成功，则立即将该指令文本存储到AOF日志中。</p><p><strong>AOF重写</strong>：长时间修改会积累大量的AOF日志，Redis可以开辟一个子进程遍历生成新的AOF指令日志，替代旧的AOF日志，起到日志瘦身的效果。（对同一个key频繁修改，会产生大量AOF日志，但实际上存一项就可以了。）</p><p><strong>fsync</strong>：Redis定期调用fsync确保AOF日志实实在在写入磁盘，避免突然断电造成内存缓冲数据丢失。</p><p><strong>混合持久化</strong>：快照 +AOF日志（增量）。提高重启效率，避免重做全部的AOF日志操作。</p><h3 id="管道">4.4 管道</h3><p>Redis客户端重排指令。将读指令连续归在一起，写指令连续归在一起。这样客户端只需要向操作系统网络写缓冲区写一次，读缓冲区读一次即可，服务器端同理。节省了网络读写的次数。</p><h3 id="事务">4.5 事务</h3><p>Redis可以实现begin, commit和rollback的事务功能。</p><h3 id="pubsub">4.6 PubSub</h3><p>PubSub, Publisher Subscriber.</p><p>消息多播，一个Publisher可以向多个Subscriber提供消息。</p><p>Subscriber需要先订阅若干个channel，随后，Publisher向channel中发布数据，Redis会将数据提供给订阅该channel的所有Subscriber。</p><p>但是，如果subscriber掉线了，过后再上线，就不会再收到掉线时错过的消息了。Redis宕机时，就相当于时没有任何subscriber的情况，会造成所有的消息都被直接丢失的情况。</p><p>Redis在5.0版本开始提供新的Stream数据结构，实现了持久化的消息队列。</p><h3 id="节省空间">4.7 节省空间</h3><h4 id="bit-vs-64bit">4.7.1 32bit vs 64bit</h4><p>32bit编译的Redis比64bit编译的版本节省一半的指针内存消耗。如使用内存不超过4GB，采用32bit即可。</p><h4 id="基于ziplist的小对象压缩存储">4.7.2基于ziplist的小对象压缩存储</h4><p>相较于传统的链表，每个entry作为一个节点，都需要配备prev/next两个指针，ziplist则将多个entry以数组的形式存为一个节点，减少所需的指针空间。</p><p>每个ziplits节点存储：</p><ol type="1"><li>zlbytes，4字节，ziplist占用的字节数；</li><li>zltail，4字节，最后一个entry的偏移地址；</li><li>zllen，2字节，ziplist的entry数量；</li><li>entry数组，存储若干entry；</li><li>zlend，1字节，幻数255标记结尾。</li></ol><h4 id="基于intset的紧凑整数数组">4.7.3 基于intset的紧凑整数数组</h4><p>inset数据结构包含：</p><ol type="1"><li>encoding，表示value的位宽；</li><li>length，表示元素的个数；</li><li>value数组，存储若干value。</li></ol><p>若整数用uint16表示即可，intset就用uint16；需要升级到uint32或uint64时再动态升级。</p><h4 id="内存回收机制">4.7.4 内存回收机制</h4><p>删除key时，内存不会立即全部回收释放交给操作系统，而是会预留部分内存给未来的使用需求。</p><h4 id="内存分配算法">4.7.5 内存分配算法</h4><p>Redis有多种内存分配算法：</p><ol type="1"><li>jemalloc，facebook；</li><li>tcmalloc，google。</li></ol><p>Redis默认使用jemalloc，该库性能稍好。</p><p>通过<code>info memory</code>可以查到当前使用的内存分配库。</p><h2 id="集群">5 集群</h2><p>多个Redis节点组成Redis集群。</p><h3 id="redis集群与cap定理">5.1 Redis集群与CAP定理</h3><h4 id="cap定理">5.1.1 CAP定理</h4><p>CAP定理指的是分布式系统的一致性（Consistency）、可用性（Availability）和分区容忍性（Partitiontolerance）不能三者兼得，最多只能满足两项。</p><p>当网络异常时，分布式节点之间无法连接，形成网络分区现象，如果要容忍分区情况，此时有两种选择：</p><ol type="1"><li><strong>保证可用性</strong>：即允许对每个节点的读和写，这样一来，节点之间就会因为无法立即同步而出现数据不一致的问题，<strong>放弃了强一致性</strong>，即AP；</li><li><strong>放弃可用性</strong>：只允许对每个节点的读，禁止写，这样一来，能保证节点之间的数据<strong>一致性</strong>，但用户无法更新数据，<strong>损失了可用性</strong>，即CP[；</li></ol><p>也就是说，网络分区发生时，一致性和可用性无法两全。</p><h4 id="最终一致性">5.1.2 最终一致性</h4><p>Redis的主从节点之间异步同步，不能保证严格的强一致性，因此Redis的选择是放弃一致性，转而满足可用性和分区容忍性。</p><p>Redis提供的是<strong>最终一致性</strong>（Eventuallyconsistent），网络断开时，主从节点之间会出现不一致，但网络恢复后，会多策略地尽快同步，最终主从节点保持一致。</p><h3 id="集群同步技术">5.2 集群同步技术</h3><h4 id="主从同步与从从同步">5.2.1 主从同步与从从同步</h4><p>主从同步（master-slavesync）：主节点与从节点之间同步，主节点把数据复制（replicate）到从节点。</p><p>从从同步（slave-slave sync）：从节点把数据复制到另一个从节点。</p><p>通过引入从从同步，可以降低主节点的同步负担。</p><h4 id="增量同步">5.2.2 增量同步</h4><p>Redis同步指令流。</p><p>Redis主节点把写指令记录在本地的指令缓存（buffer）中，异步地将缓存中地指令同步到从节点，即增量同步。</p><p>指令缓存采用的是定长环形数组，因此，如果数组写满了，就会重新从头写入，也就覆盖掉了原有内容。如果网络分区发生时，有节点上产生大量写指令，为了避免指令缓存被覆盖导致写入记录丢失，不能只依赖指令缓存来保存未同步的指令。</p><h4 id="快照同步">5.2.3 快照同步</h4><p>快照同步：执行bgsave操作，把内存中的数据全部快照存储到硬盘文件中。</p><p>增加从节点：增加新的从节点时，通过快照同步为从节点全量加载数据，随后再做增量同步。</p><p>快照同步死循环问题：当快照同步太慢，或者指令缓存太小时，就会出现快照同步还没结束，指令缓存就写满的情况。这样一来，指令就不得不直接写入，那快照就过期了，又得重新做一遍快照，而重新做快照可能又太慢，指令缓存又写满了……。为避免死循环，需要设置一个合适的指令缓存大小。</p><h4 id="无盘复制">5.2.4 无盘复制</h4><p>快照同步需要写入磁盘，有不小的文件IO代价。而且Redis执行AOF时需要做fsync，如果此时快照同步，就不得不延后fsync，这样AOF就延后了，指令执行就延后了。</p><p>为此，Redis2.8.18开始支持无盘复制，主节点可以通过socket通信直接把快照发给从节点，避免磁盘上的文件IO代价。</p><h4 id="wait指令实现同步复制">5.2.5 wait指令实现同步复制</h4><p>Redis的复制本身时异步执行的，因此不具备强一致性。</p><p>通过wait指令，可以实现Redis的同步复制，保证系统的（在没有网络分区情况下的）强一致性。</p><p>wait可以有限等待，也可以无限等待N个从节点同步完成，再执行后续指令。</p><p>如果无限等待时，Redis出现网络分区，那么同步无法完成，就会一直阻塞，导致Redis失去可用性。</p><h3 id="sentinel自动主从切换技术">5.3 Sentinel：自动主从切换技术</h3><p>RedisSentinel集群通常包含3~5个Sentinel节点，保证Sentinel的可用性。</p><p>Sentinel集群持续监控主节点和从节点的状态，一旦出现问题，就自动提升一个可用的从节点为主节点，取代故障的不可用的主节点。</p><p>Sentinel的具体工作流程：</p><ol type="1"><li>Client首先向Sentinel请求主节点的地址；</li><li>Sentinel将最新的主节点地址返回给Client；</li><li>Client访问主节点。</li></ol><h3 id="codis中心化集群方案">5.4 Codis：中心化集群方案</h3><p>Codis是Redis集群方案之一，在Codis基础之上，开发出了TiDB。</p><p>单个Redis节点如果存储太多数据，会使得快照文件rdb特别大，导致同步起来很耗时，而且全量恢复也变得很慢。</p><p>Codis通过把数据分散到众多Redis节点上，来避免每个节点的数据量过大。</p><p>Codis对key做哈希，映射到1024个槽位（slots），以此求模，取得数据应该映射到的节点序号。分配完成后，Codis节点会存储槽位与Redis节点的映射关系。</p><p>Codis的扩容：可以通过增加Redis节点来扩容集群的容量。</p><p>Codis通过mget指令可以从分散的节点上取数据并汇总给用户。</p><h3 id="cluster去中心化集群方案">5.5 Cluster：去中心化集群方案</h3><p>Redis Cluster是去中心化的集群方案，每个节点都是对等的。</p><p>Redis Cluster把数据分为16384个槽位（<spanclass="math inline">\(2^{14}\)</span>），每个节点负责一部分的槽位。客户端根据key来确定槽位，进而确定目标节点。如果客户端向错误的节点发送请求，该节点会计算key对应的槽位，向客户端发送重定向的响应，告知客户端目标节点。</p><p><strong>节点迁移</strong>：迁移的最小单位是槽位，流程是从源节点获取内容，然后存到目标节点，最后从源节点中删除内容。</p><p><strong>容错</strong>：RedisCluster可以为每个主节点设置若干从节点，自动实现故障时从节点提升为主节点。</p><p><strong>可能下线与确定下线</strong>：集群节点采用Gossip协议来广播自己的状态。一个节点发现某个节点失联，则进入可能下线（PFail,PossiblyFail）状态。集群中大多数节点都收到该节点失联的消息，则标记该节点为确定下线（Fail）状态。</p><h2 id="扩展">6 扩展</h2><h3 id="streamredis5.0的消息队列">6.1 Stream：Redis5.0的消息队列</h3><p>Redis Stream是Redis5.0中退出的一款新的支持多播的可持久化消息队列，极大地借鉴了Kafka的设计。</p><p>RedisStream通过消息链表将所有加入的消息串起来，每个消息包含唯一ID（timestampInMillis-sequence）和消息内容（形如hash结构的键值对）。</p><p>消费组：每个Stream可以挂载多个消费组（ConsumerGroup），不同消费组互相独立，互不影响，每个消费组都有一个游标last_delivered_id在Stream数组上向前移动，表示当前已经消费到哪条消息了。</p><p>消费者：每个消费组中可以包含多个消费者（Consumer），消费者之间为竞争关系，任意一个消费者读取消息都会使消费组的游标last_delivered_id向前移动。</p><p>PEL：每个消费者有一个的PEL（Pending EntriesList），PEL是一个状态列表pending_ids，记录已经被客户端读取，但尚未收到ACK的消息ID。通过PEL可以确保客户端至少消费了消息1次，而不会在网络传输中途丢失了消息。客户端重连时，可以根据PEL重新获取一遍接收失败的消息。</p><p>分区：Redis没有原生支持分区，分区Stream可以通过在客户端设计哈希策略来实现。Kafka原生支持Partition也是通过客户端的HashStrategy来决定将不同的消息加入不同的分区的。</p><p>xgroup create：创建消费组，创建时需要指定从哪个消息ID开始消费。</p><p>xadd：加入消息；</p><p>xdel：删除消息，只设置标志位，不实际删除消息；</p><p>xrange：获取消息列表，自动过滤标记为删除的消息；</p><p>xlen：获取消息长度；</p><p>del：删除整个消息列表的所有消息。</p><h3 id="info状态诊断">6.2 info：状态诊断</h3><p>info指令可查询：</p><ol type="1"><li>server：服务器信息；</li><li>clients：客户端信息；</li><li>memory：运行内存统计数据；</li><li>persistence：持久化信息；</li><li>stats：通用统计数据；</li><li>replication：主从复制；</li><li>cpu：cpu使用情况；</li><li>cluster：集群信息；</li><li>keyspace：键值对统计信息。</li></ol><p>查询方式如：</p><p>Redis内：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&gt; info memory</span><br></pre></td></tr></table></figure><p>Redis外：</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">redis-cli info memory</span><br></pre></td></tr></table></figure><h3 id="redlock分布式锁">6.3 Redlock分布式锁</h3><p>Sentinel集群中，主节点挂掉后，从节点取而代之，但主节点的分布式锁没有同步到从节点，新升任主节点的从节点中没有这个分布式锁，就会造成不安全性。</p><p>对多个对等的Redis实例，Redlock基于“大多数机制”，加锁时，向过半的节点发送set指令，过半的节点加锁成功，则本次加锁成功；解锁时，向所有节点发送del指令。因为Redlock需要向多个节点进行读写，考虑出错重试、时钟漂移等问题，相对单实例Redis的性能会下降一点。</p><h3 id="过期策略">6.4 过期策略</h3><p>设置了expire时间的key放在一个独立的字典里。</p><p>Redis的过期策略既有定期扫描，也有惰性策略。</p><p>定期扫描，Redis默认每秒10次过期扫描，扫描算法为：</p><ol type="1"><li>从过期字典中随机选出20个key；</li><li>删除这20个key中已经过期的key；</li><li>如果过期key的比例超过1/4，则重复步骤1.。</li></ol><p>为避免循环过度造成线程卡死，默认设置扫描时间上限为25ms。这个25ms的依据是，1秒10次，每次25ms，总共最多占用250ms，即1/4的CPU时间。Redis实际上限制的是CPU时间，避免过期扫描耗费超过1/4的CPU时间。</p><p>如果大量key同时过期，Redis就会循环扫描字典，删除key，直到过期字典中的过期key比例变低。当过期的key数量很多的时候，扫描时间是完全可能撞到25ms的上限的。再加上内存回收的代价，就会产生比较多的CPU消耗。如果此时新来的请求设置的超时时间很短，例如10ms，就会导致刚设置数据，就开始扫描，等25ms扫描完，才来得及处理客户端的读取操作时，key早就过期了。客户端就发现自己刚设置的值，立即去修改就会超时过期，实际上是因为Redis的过期策略在间隔中消耗了时间。</p><p>为了避免以上问题，一方面，考虑到过期策略扫描耗时，过期时间不宜设置的过短；另一方面，避免大量key同时过期，哪怕对统一的过期时间加上一个随机量也好。</p><p>惰性策略：访问key时对key的过期时间进行检查，如果过期了就删除。</p><p>从节点不会主动执行过期策略，主节点删除节点并同步del给从节点，从节点收到后写入AOF，跟着主节点照做就是。不过因为同步是异步的，所以主从节点之间强一致性无法保证。</p><h3 id="内存淘汰算法">6.5 内存淘汰算法</h3><p>Redis不允许发生swap，因为会造成性能急剧下降。</p><p>当Redis实际内存超过maxmemory时，有几种maxmemory-policy：</p><ol type="1"><li>noeviction：可读不可写；</li><li>volatile-lru：淘汰过期集合中最少使用的（LRU）；</li><li>volatile-ttl：淘汰过期集合中剩余寿命TTL最小的key；</li><li>volatile-random：淘汰过期集合中随机key；</li><li>allkeys-lru：全体key中淘汰LRU；</li><li>allkeys-random：全体key中淘汰随机的key。</li></ol><h3 id="懒惰删除">6.6 懒惰删除</h3><p>del直接删除，通常非常快，但对象非常大时，删除操作会造成单线程卡顿。</p><p>Redis4.0引入的unlink可以解决卡顿问题，unlink卸下待删除对象，然后交给后台线程去异步地回收内存。</p><p>Redis4.0为flushdb和flushall都引入了异步化，加上async选项即可，如：<code>flushall async</code>。</p><p>异步删除借助异步队列实现，MainThread通过submitTask将待删除对象放入ConcurrentQueue，懒惰删除线程LazyFreeThread从中fetchTask并执行异步删除。</p><p>Redis的AOFSync需要将AOF日志同步到磁盘，需要调用sync函数，因为sync比较耗时，因此采用异步线程去调用，该异步线程也有自己的任务队列，存放AOFSync任务。</p><p>Redis在del和flush以外，也会在key过期、LRU淘汰、rename指令执行时回收内存。节点接受全量同步rdb文件后也会清空内存以载入数据。这些删除场景涉及额外的选项：</p><ol type="1"><li>slave-lazy-flush：从节点接受rdb文件后的flush操作；</li><li>lazyfree-lazy-eviction：内存达到maxmemory时进行淘汰；</li><li>lazyfree-lazy-expire-key：过期删除；</li><li>lazyfree-lazy-server-del rename：指令删除destKey。</li></ol><h3 id="jedis">6.7 Jedis</h3><p>Jedis是Java的Redis开源客户端。</p><p>因为Jedis对象不是线程安全的，所以使用Jedis是从Jedis连接池JedisPool中取出一个Jedis对象归该线程独占，用完了再还给连接池。</p><p>Jedis默认没有重试机制，网络抖动造成连接断开，再发送指令就会报错。需要手动捕获JedisConnectionException进行重连处理。</p><h3 id="redis安全">6.8 Redis安全</h3><h4 id="指令安全">6.8.1 指令安全</h4><p>rename-command既可以将已有命令更名，也可以更名为空字符串，从而屏蔽该命令被调用。</p><h4 id="端口安全">6.8.2 端口安全</h4><p>bind指令规定监听的IP地址。</p><p>requirepass设置密码访问限制，从节点masterauth设置于主节点同步连接密码。</p><h4 id="脚本安全">6.8.3 脚本安全</h4><p>避免UGC的Lua脚本。</p><p>避免以root权限启动Redis。</p><h4 id="ssl代理">6.8.4 SSL代理</h4><p>使用SSH保护Redis连接。</p><p>使用官方推荐的spiped工具对SSH通道进行二次加密。spiped是一款加密代理软件。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Redis作为一款高效的键值型内存数据库，值得学习。本文梳理学习过程中的笔记。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Redis" scheme="https://heary.cn/tags/Redis/"/>
    
  </entry>
  
  <entry>
    <title>初识分布式系统：CAP定理与BASE理论</title>
    <link href="https://heary.cn/posts/%E5%88%9D%E8%AF%86%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%EF%BC%9ACAP%E5%AE%9A%E7%90%86%E4%B8%8EBASE%E7%90%86%E8%AE%BA/"/>
    <id>https://heary.cn/posts/%E5%88%9D%E8%AF%86%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%EF%BC%9ACAP%E5%AE%9A%E7%90%86%E4%B8%8EBASE%E7%90%86%E8%AE%BA/</id>
    <published>2020-08-19T08:04:38.000Z</published>
    <updated>2022-08-07T04:02:09.878Z</updated>
    
    <content type="html"><![CDATA[<p>初步学习分布式系统，理解CAP定理与BASE理论。</p><span id="more"></span><h1id="初识分布式系统cap定理与base理论">初识分布式系统：CAP定理与BASE理论</h1><p>传统单机事务模型难以应对分布式事务的处理需求，需要分布式系统。分布式系统的节点分布在网络中，难以像传统的集中式事务处理系统那样实现严格的ACID特性。</p><h2 id="cap定理">1 CAP定理</h2><h3 id="背景">1.1 背景</h3><p>2000年7月，加州大学伯克利分校Eric Brewer教授在ACM PODC (Principles ofDistributed Computing)会议上提出了CAP猜想。</p><p>2年后，麻省理工学院的Seth Gilbert和NancyLynch从理论上证明了CAP猜想的可行性，从此CAP定理成为分布式计算领域的公认定理。</p><h3 id="定理">1.2 定理</h3><p><strong>CAP定理</strong>：一个分布式系统不可能同时满足一致性（Consistency）、可用性（Availiability）与分区容错性（Partitiontorlence）这三个基本需求，最多智能同时满足其中两项。</p><img src="/posts/%E5%88%9D%E8%AF%86%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%EF%BC%9ACAP%E5%AE%9A%E7%90%86%E4%B8%8EBASE%E7%90%86%E8%AE%BA/CAP_Theorem.png" class="" title="CAP Theorem"><h4 id="一致性consistency">1.2.1 一致性（Consistency）</h4><p>一致性指的是多副本之间的一致性。分布式系统场景下，一个副本更新后，其他副本如果没有及时更新，那从其他副本上读取到的数据仍然是老数据，即，副本之间的数据出现不一致。</p><p>所有节点在同一时间具有相同的数据。</p><h4 id="可用性availibity">1.2.2 可用性（Availibity）</h4><p>可用性指的是系统提供的服务必须一直处于可用状态，即，对用户请求总是在有限的时间内返回结果。</p><p>每个请求不关成功或是失败都有响应。</p><h4 id="分区容错性partition-torlence">1.2.3 分区容错性（Partitiontorlence）</h4><p>分区容错性指的是分布式系统遇到任何网络分区故障时，仍然能够对外提供满足一致性和可用性的服务，除非整个网络环境都发生了故障。</p><p>系统中任意信息的丢失或失败不影响系统的继续运作。</p><p>注：</p><ul><li>分布式系统中，不同节点分布在不同的子网络，可能出现子网络之间网络断连，但子网络内部正常，使得分布式系统被分割为若干孤立区域。</li><li>组成一个分布式系统的每个节点的加入和退出，都可以看成时一个特殊的网络分区。</li></ul><h3 id="应用">1.3 应用</h3><p>根据CAP定理，分布式系统在应用中必须作出取舍，只能满足最多两个性质，意味着必须选择放弃一个性质。</p><table><colgroup><col style="width: 20%" /><col style="width: 50%" /><col style="width: 28%" /></colgroup><thead><tr class="header"><th>放弃性质</th><th>说明</th><th>应用</th></tr></thead><tbody><tr class="odd"><td>CA：放弃分区容错性（-P）</td><td>单点集群系统，放弃分区容错性意味着放弃系统的可扩展性。实现分区容错性，简单的方法是将所有的数据（至少是事务相关的数据）放在一个分布式节点上，这样网络分区问题时，每个子网络都有依赖数据的可用副本。</td><td>RDBMS</td></tr><tr class="even"><td>CP：放弃可用性（-A）</td><td>一旦分布式系统遭遇网络分区或其他故障，受影响的服务需要等待一定时间才能恢复对外服务，在这段时间内不可用。满足一致性，分区容忍性的系统，通常性能不是特别高。</td><td>MongoDB, HBase, Redis</td></tr><tr class="odd"><td>AP：放弃一致性（-C）</td><td>放弃分布式系统的强一致性，保证分布式系统的最终一致性。引入时间窗口的概念，隔一段时间在不同节点之间复制数据副本。</td><td>CouchDB, Cassandra, DynamoDB, Riak</td></tr></tbody></table><p>具体地，</p><ul><li>CA：放弃分区容错性<ul><li>RDBMS：关系型数据库管理系统（Relational Database ManagementSystem），不具备可扩展性。</li></ul></li><li>CP：放弃可用性<ul><li>MongoDB：NoSQL，面向文档（document-oriented）；</li><li>HBase：Hadoop Database，面向列（column-oriented）；</li><li>Redis：Remote Dictionary Server，键值存储；</li></ul></li><li>AP：放弃一致性<ul><li>CouchDB：面向文档；</li><li>Cassandra：面向列；</li><li>DynamoDB：面向文档；</li><li>Riak：键值存储；</li></ul></li></ul><h2 id="base理论">2 BASE理论</h2><p>BASE名字取自缩写：</p><ul><li>Basically Available</li><li>Soft state</li><li>Eventually consistent</li></ul><h3 id="背景-1">2.1 背景</h3><p>BASE理论由eBay架构师Dan Prichett在文章BASE: An AcidAlternative中首次提出，是对CAP中一致性和可用性权衡的结果。</p><h3 id="理论">2.2 理论</h3><p>BASE理论的核心思想是：即使无法做到强一致性（Strongconsistency），但每个应用都可以根据自身的业务特点，采用适当的方式来使系统达到最终一致性（Eventualconsistency）。</p><p>牺牲强一致性来获得可用性。</p><h4 id="基本可用basically-available">2.2.1 基本可用（BasicallyAvailable）</h4><p>分布式系统在出现不可预知故障时，允许损失部分可用性。</p><p>如：</p><ul><li>响应时间上的损失：出现故障时，响应时间一定程度增加；</li><li>功能上的损失：购物节高峰时，部分用户被引导到一个降级页面。</li></ul><h4 id="软状态soft-state">2.2.2 软状态（Soft state）</h4><p>允许系统中的数据存在中间状态，并认为该中间状态的存在不会影响系统的整体可用性，即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。</p><h4 id="最终一致性eventually-consistent">2.2.3 最终一致性（Eventuallyconsistent）</h4><p>系统中所有的数据副本，经过一段时间同步后，最终能达到一个一致的状态。</p><p>不需要实时一致，达到一致所需的时间延迟，取决于网络延迟、系统负载和数据复制方案设计等因素。</p><p>实际工程实践中，最终一致性存在五类变种：</p><ol type="1"><li>因果一致性（Causalconsistency）：进程A修改数据后通知进程B，进程B读取的数据应该是新值。</li><li>读己之所写（Read yourwrites）：进程A修改后再读取，得到的应该是新值。</li><li>会话一致性（Sessionconsistency）；系统保证再同一个有效的会话中实现读己之所写。</li><li>单调读一致性（Monotonic readconsistency）：进程读到新值后，后续不应该反而读出旧值。</li><li>单调写一致性（Monotonic writeconsistency）：同一个进程的写操作应该顺序执行。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;初步学习分布式系统，理解CAP定理与BASE理论。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Distributed System" scheme="https://heary.cn/tags/Distributed-System/"/>
    
    <category term="CAP Theorem" scheme="https://heary.cn/tags/CAP-Theorem/"/>
    
    <category term="BASE Theory" scheme="https://heary.cn/tags/BASE-Theory/"/>
    
  </entry>
  
  <entry>
    <title>Selector - 从JDK11源码理解Java I/O复用原理</title>
    <link href="https://heary.cn/posts/Selector-%E4%BB%8EJDK11%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Java-I-O%E5%A4%8D%E7%94%A8%E5%8E%9F%E7%90%86/"/>
    <id>https://heary.cn/posts/Selector-%E4%BB%8EJDK11%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Java-I-O%E5%A4%8D%E7%94%A8%E5%8E%9F%E7%90%86/</id>
    <published>2020-08-09T09:07:47.000Z</published>
    <updated>2022-08-07T04:02:09.619Z</updated>
    
    <content type="html"><![CDATA[<p>阅读JDK11源码实现的过程中，发现同为<code>java.nio.channels.Selector</code>，是Windows和Linux平台的<code>Selector.open()</code>所构造的Selector的底层实现完全不一样。</p><span id="more"></span><h1 id="selector---从jdk11源码理解java-io复用原理">Selector -从JDK11源码理解Java I/O复用原理</h1><p>Selector是JavaNIO中核心的多路复用选择器。线程可以将SocketChannel与选择键注册到Selector上，而Selector会选出I/O状态符合选择键条件的SocketChannel实例。</p><h2 id="应用层">应用层</h2><p>线程将SocketChannel实例与选择键注册到Selector上：</p><figure class="highlight java"><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">try</span> &#123;</span><br><span class="line">    socketChannel.register(<span class="built_in">this</span>.selector, SelectionKey.OP_READ);    <span class="comment">// socketChannel is always Writable</span></span><br><span class="line">    <span class="comment">// socketChannel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);</span></span><br><span class="line">    <span class="built_in">this</span>.selector.wakeup();</span><br><span class="line">&#125; <span class="keyword">catch</span> (ClosedChannelException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Selector可以取出I/O状态符合选择键的SocketChannel集合，遍历处理：</p><figure class="highlight java"><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">try</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.selector.select();</span><br><span class="line"></span><br><span class="line">    Set&lt;SelectionKey&gt; selectionKeySet = <span class="built_in">this</span>.selector.selectedKeys();</span><br><span class="line">    Iterator&lt;SelectionKey&gt; selectionKeys = selectionKeySet.iterator();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (selectionKeys.hasNext()) &#123;</span><br><span class="line">        <span class="type">SelectionKey</span> <span class="variable">selectionKey</span> <span class="operator">=</span> selectionKeys.next();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (selectionKey.isReadable()) &#123;</span><br><span class="line">            selectionKey.cancel();      <span class="comment">// avoid repeating selecting the same channel</span></span><br><span class="line">            <span class="type">SocketChannel</span> <span class="variable">socketChannel</span> <span class="operator">=</span> (SocketChannel) selectionKey.channel();</span><br><span class="line"></span><br><span class="line">            <span class="type">HttpWorker</span> <span class="variable">httpWorker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HttpWorker</span>(webRoot, socketChannel);</span><br><span class="line">            <span class="built_in">this</span>.executorService.submit(httpWorker);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        selectionKeys.remove();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="抽象层">抽象层</h2><h3 id="selector">Selector</h3><h4 id="selector.open">Selector.open()</h4><p>外部通过<code>Selector.open()</code>方法就可以</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.nio.channels.Selector;</span><br><span class="line"></span><br><span class="line"><span class="type">Selector</span> <span class="variable">selector</span> <span class="operator">=</span> Selector.open();</span><br></pre></td></tr></table></figure><p>在<code>Selector</code>抽象类中实现为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Selector</span> <span class="keyword">implements</span> <span class="title class_">Closeable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Opens a selector.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;p&gt; The new selector is created by invoking the &#123;<span class="doctag">@link</span></span></span><br><span class="line"><span class="comment">     * java.nio.channels.spi.SelectorProvider#openSelector openSelector&#125; method</span></span><br><span class="line"><span class="comment">     * of the system-wide default &#123;<span class="doctag">@link</span></span></span><br><span class="line"><span class="comment">     * java.nio.channels.spi.SelectorProvider&#125; object.  &lt;/p&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>  A new selector</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span>  IOException</span></span><br><span class="line"><span class="comment">     *          If an I/O error occurs</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Selector <span class="title function_">open</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> SelectorProvider.provider().openSelector();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实际上只是一层抽象，具体调用了<code>SelectorProvider</code>来提供和打开<code>Selector</code>实例。</p><h4 id="selector.select">Selector.select()</h4><p>多路复用的核心功能，选出可进行I/O的通道们的键集。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Selector</span> <span class="keyword">implements</span> <span class="title class_">Closeable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Selects a set of keys whose corresponding channels are ready for I/O</span></span><br><span class="line"><span class="comment">     * operations.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;p&gt; This method performs a blocking &lt;a href=&quot;#selop&quot;&gt;selection</span></span><br><span class="line"><span class="comment">     * operation&lt;/a&gt;.  It returns only after at least one channel is selected,</span></span><br><span class="line"><span class="comment">     * this selector&#x27;s &#123;<span class="doctag">@link</span> #wakeup wakeup&#125; method is invoked, the current</span></span><br><span class="line"><span class="comment">     * thread is interrupted, or the given timeout period expires, whichever</span></span><br><span class="line"><span class="comment">     * comes first.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;p&gt; This method does not offer real-time guarantees: It schedules the</span></span><br><span class="line"><span class="comment">     * timeout as if by invoking the &#123;<span class="doctag">@link</span> Object#wait(long)&#125; method. &lt;/p&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>  timeout  If positive, block for up to &#123;<span class="doctag">@code</span> timeout&#125;</span></span><br><span class="line"><span class="comment">     *                  milliseconds, more or less, while waiting for a</span></span><br><span class="line"><span class="comment">     *                  channel to become ready; if zero, block indefinitely;</span></span><br><span class="line"><span class="comment">     *                  must not be negative</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>  The number of keys, possibly zero,</span></span><br><span class="line"><span class="comment">     *          whose ready-operation sets were updated</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span>  IOException</span></span><br><span class="line"><span class="comment">     *          If an I/O error occurs</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span>  ClosedSelectorException</span></span><br><span class="line"><span class="comment">     *          If this selector is closed</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span>  IllegalArgumentException</span></span><br><span class="line"><span class="comment">     *          If the value of the timeout argument is negative</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="type">int</span> <span class="title function_">select</span><span class="params">(<span class="type">long</span> timeout)</span> <span class="keyword">throws</span> IOException;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法在Selector抽象类定义，但具体实现位于作为其子类的<code>SelectorImpl</code>实现类中。</p><h5 id="selectorimpl">SelectorImpl</h5><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * Base Selector implementation class.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">SelectorImpl</span></span><br><span class="line">    <span class="keyword">extends</span> <span class="title class_">AbstractSelector</span></span><br><span class="line">&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Selects the keys for channels that are ready for I/O operations.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> action  the action to perform, can be null</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeout timeout in milliseconds to wait, 0 to not wait, -1 to</span></span><br><span class="line"><span class="comment">     *                wait indefinitely</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">abstract</span> <span class="type">int</span> <span class="title function_">doSelect</span><span class="params">(Consumer&lt;SelectionKey&gt; action, <span class="type">long</span> timeout)</span></span><br><span class="line">        <span class="keyword">throws</span> IOException;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">lockAndDoSelect</span><span class="params">(Consumer&lt;SelectionKey&gt; action, <span class="type">long</span> timeout)</span></span><br><span class="line">        <span class="keyword">throws</span> IOException</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">            ensureOpen();</span><br><span class="line">            <span class="keyword">if</span> (inSelect)</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;select in progress&quot;</span>);</span><br><span class="line">            inSelect = <span class="literal">true</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">synchronized</span> (publicSelectedKeys) &#123;</span><br><span class="line">                    <span class="keyword">return</span> doSelect(action, timeout);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                inSelect = <span class="literal">false</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="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">select</span><span class="params">(<span class="type">long</span> timeout)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">if</span> (timeout &lt; <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Negative timeout&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> lockAndDoSelect(<span class="literal">null</span>, (timeout == <span class="number">0</span>) ? -<span class="number">1</span> : timeout);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">select</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> lockAndDoSelect(<span class="literal">null</span>, -<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>SelectorImpl的<code>select</code>方法调用了<code>lockAndDoSelect</code>方法。传入的参数表示不执行任何操作，且默认持续等待。</p><p>在<code>lockAndDoSelect</code>方法中，用synchronized关键字保护当前Selector对象，实现并发同步。内部也通过isSelect标记来防止并发select操作，实际执行的方法是<code>doSelect</code>方法，该方法在SelectorImpl类中被定义，但没有实现。具体实现取决于其子类，即实现层的实现。</p><h3 id="selectorprovider">SelectorProvider</h3><p>SelectorPrivider是一个抽象类。</p><h4 id="selectorprovider.provider">SelectorProvider.provider()</h4><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">SelectorProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the system-wide default selector provider for this invocation of</span></span><br><span class="line"><span class="comment">     * the Java virtual machine.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;p&gt; The first invocation of this method locates the default provider</span></span><br><span class="line"><span class="comment">     * object as follows: &lt;/p&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;ol&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     *   &lt;li&gt;&lt;p&gt; If the system property</span></span><br><span class="line"><span class="comment">     *   &#123;<span class="doctag">@code</span> java.nio.channels.spi.SelectorProvider&#125; is defined then it is</span></span><br><span class="line"><span class="comment">     *   taken to be the fully-qualified name of a concrete provider class.</span></span><br><span class="line"><span class="comment">     *   The class is loaded and instantiated; if this process fails then an</span></span><br><span class="line"><span class="comment">     *   unspecified error is thrown.  &lt;/p&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     *   &lt;li&gt;&lt;p&gt; If a provider class has been installed in a jar file that is</span></span><br><span class="line"><span class="comment">     *   visible to the system class loader, and that jar file contains a</span></span><br><span class="line"><span class="comment">     *   provider-configuration file named</span></span><br><span class="line"><span class="comment">     *   &#123;<span class="doctag">@code</span> java.nio.channels.spi.SelectorProvider&#125; in the resource</span></span><br><span class="line"><span class="comment">     *   directory &#123;<span class="doctag">@code</span> META-INF/services&#125;, then the first class name</span></span><br><span class="line"><span class="comment">     *   specified in that file is taken.  The class is loaded and</span></span><br><span class="line"><span class="comment">     *   instantiated; if this process fails then an unspecified error is</span></span><br><span class="line"><span class="comment">     *   thrown.  &lt;/p&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     *   &lt;li&gt;&lt;p&gt; Finally, if no provider has been specified by any of the above</span></span><br><span class="line"><span class="comment">     *   means then the system-default provider class is instantiated and the</span></span><br><span class="line"><span class="comment">     *   result is returned.  &lt;/p&gt;&lt;/li&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;/ol&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * &lt;p&gt; Subsequent invocations of this method return the provider that was</span></span><br><span class="line"><span class="comment">     * returned by the first invocation.  &lt;/p&gt;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span>  The system-wide default selector provider</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SelectorProvider <span class="title function_">provider</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (lock) &#123;</span><br><span class="line">            <span class="keyword">if</span> (provider != <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> provider;</span><br><span class="line">            <span class="keyword">return</span> AccessController.doPrivileged(</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">PrivilegedAction</span>&lt;&gt;() &#123;</span><br><span class="line">                    <span class="keyword">public</span> SelectorProvider <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                            <span class="keyword">if</span> (loadProviderFromProperty())</span><br><span class="line">                                <span class="keyword">return</span> provider;</span><br><span class="line">                            <span class="keyword">if</span> (loadProviderAsService())</span><br><span class="line">                                <span class="keyword">return</span> provider;</span><br><span class="line">                            provider = sun.nio.ch.DefaultSelectorProvider.create();</span><br><span class="line">                            <span class="keyword">return</span> provider;</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><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个<code>provider()</code>方法是一个synchronized同步锁保护的单例模式，返回SelectorProvider类型的实例。</p><p>具体地，当没有实例时，需要创建实例。</p><p>创建实例通过AccessController来执行特权行为。</p><p>根据官方文档，AccessController被用于控制操作的权限和决策。</p><blockquote><p>The AccessController class is used for access control operations anddecisions. More specifically, the AccessController class is used forthree purposes:</p><ul><li>to decide whether an access to a critical system resource is to beallowed or denied, based on the security policy currently ineffect,</li><li>to mark code as being "privileged", thus affecting subsequent accessdeterminations, and</li><li>to obtain a "snapshot" of the current calling context soaccess-control decisions from a different context can be made withrespect to the saved context.</li></ul></blockquote><p>这个AccessController起到三种作用：</p><ol type="1"><li>检查权限：决定对关键系统资源的访问是否应该批准；</li><li>授予权限：把代码标记为特权代码，以便执行后续操作；</li><li>保存快照：保存当前调用上下文，以便做来自其它上下文的访问控制决策的时候能够考虑到已保存的上下文。</li></ol><p>具体地，在此处，<code>AccessController.doPrivileged(...)</code>方法起到的是第二个作用，授予权限，执行特权代码：</p><blockquote><p>Performs the specified PrivilegedAction with privileges enabled.</p><p>The action is performed with all of the permissions possessed by thecaller's protection domain.</p></blockquote><p>该方法的输入参数是一个实现了<code>PrivilegedAction</code>接口的匿名类，该匿名类实现了接口的<code>run()</code>方法。该方法依靠外层提供的特权权限，来实例化一个<code>SelectorProvider</code>。实例化的过程分三种优先级：</p><ol type="1"><li><code>loadProviderFromProperty()</code></li><li><code>loadProviderAsService()</code></li><li><code>provider = sun.nio.ch.DefaultSelectorProvider.create()</code></li></ol><h5 id="第一优先级-loadproviderfromproperty">第一优先级<code>loadProviderFromProperty</code></h5><p>第一优先级通过系统检查<code>java.nio.channels.spi.SelectorProvider</code>是否存在，如果存在则加载，反之，则返回<code>false</code>。</p><blockquote><p>Service-provider classes for the java.nio.channels package.</p></blockquote><p><code>java.nio.channels.spi</code>包提供了一批ServiceProvider的类。</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">SelectorProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">loadProviderFromProperty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">cn</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;java.nio.channels.spi.SelectorProvider&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (cn == <span class="literal">null</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;deprecation&quot;)</span></span><br><span class="line">            <span class="type">Object</span> <span class="variable">tmp</span> <span class="operator">=</span> Class.forName(cn, <span class="literal">true</span>,</span><br><span class="line">                                       ClassLoader.getSystemClassLoader()).newInstance();</span><br><span class="line">            provider = (SelectorProvider)tmp;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException x) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ServiceConfigurationError</span>(<span class="literal">null</span>, x);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalAccessException x) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ServiceConfigurationError</span>(<span class="literal">null</span>, x);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InstantiationException x) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ServiceConfigurationError</span>(<span class="literal">null</span>, x);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SecurityException x) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ServiceConfigurationError</span>(<span class="literal">null</span>, x);</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">/* more */</span> </span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法首先读取检查系统属性中，键<code>java.nio.channels.spi.SelectorProvider</code>是否有设置值。如果没有，则返回false，如果有，用这个值加载SelectorProvider类。</p><p>该方法通过<code>Class.forName</code>方法，指定通过系统类加载器在运行时动态加载系统属性中设置的SelectorProvider类（如指定）。</p><h5 id="第二优先级-loadproviderasservice">第二优先级<code>loadProviderAsService</code></h5><p>如果第一优先级所需的<code>java.nio.channels.spi.SelectorProvider</code>不存在，则需要启动第二优先级的加载工作。</p><p>如果<code>META-INF/services</code>中，存放了<code>java.nio.channels.spi.SelectorProvider</code>的jar文件，则通过系统类加载器加载该服务。</p><figure class="highlight java"><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="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">SelectorProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">loadProviderAsService</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        ServiceLoader&lt;SelectorProvider&gt; sl =</span><br><span class="line">            ServiceLoader.load(SelectorProvider.class,</span><br><span class="line">                               ClassLoader.getSystemClassLoader());</span><br><span class="line">        Iterator&lt;SelectorProvider&gt; i = sl.iterator();</span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (!i.hasNext())</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                provider = i.next();</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (ServiceConfigurationError sce) &#123;</span><br><span class="line">                <span class="keyword">if</span> (sce.getCause() <span class="keyword">instanceof</span> SecurityException) &#123;</span><br><span class="line">                    <span class="comment">// Ignore the security exception, try the next provider</span></span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">throw</span> sce;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span> </span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该方法通过<code>ServiceLoader</code>来加载服务，选取可见的第一个<code>SelectorProvider</code>实例。</p><h5 id="最终优先级-sun.nio.ch.defaultselectorprovider">最终优先级<code>sun.nio.ch.DefaultSelectorProvider</code></h5><p>如果上述SelectorProvider都不存在，就会加载<code>sun.nio.ch.DefaultSelectorProvider</code>作为最终选择。</p><p><strong>实际运行中，如果没有实现和配置前两种，默认会启用该最终优先级。</strong></p><p>DefaultSelectorProvider的对外提供统一的接口，内部仅仅是完成对实现类的实例化，而具体实例化什么类，取决于JDK的操作系统版本。</p><h3 id="defaultselectorprovider">DefaultSelectorProvider</h3><h4 id="defaultselector.create">DefaultSelector.create()</h4><p>具体地，该<code>sun.nio.ch.DefaultSelectorProvider</code>对外提供一致接口，其<code>create</code>方法实际上仅仅是一层封装，只是实现了一个<code>new</code>实例化操作，但不同操作系统平台的JDK的内部实现不同：</p><ul><li>在WindowsJDK11中，其实例化的是<code>sun.nio.ch.WindowsSelectorProvider</code>类。</li><li>在LinuxJDK11中，其实例化的是<code>sun.nio.ch.EPollSelectorProvider</code>类。</li></ul><p>Windows JDK11：</p><figure class="highlight java"><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="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates this platform&#x27;s default SelectorProvider</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultSelectorProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Prevent instantiation.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">DefaultSelectorProvider</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the default SelectorProvider.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SelectorProvider <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">sun</span>.nio.ch.WindowsSelectorProvider();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Linux JDK11:</p><figure class="highlight java"><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="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates this platform&#x27;s default SelectorProvider</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultSelectorProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Prevent instantiation.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">DefaultSelectorProvider</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Returns the default SelectorProvider.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> SelectorProvider <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">EPollSelectorProvider</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3id="windowsselectorprovider与epollselectorprovider">WindowsSelectorProvider与EPollSelectorProvider</h3><p>这一层都继承自SelectorProviderImpl抽象类，实际上也没有实现什么特别的功能逻辑，只是调用对应的SelectorImpl实现类。</p><p>这一层实现了从SelectorProvider到SelectorImpl的交互。</p><p>具体到每种SelectorImpl是如何实现的，在下一节实现层具体分析。</p><h4id="windowsselectorprovider.openselector">WindowsSelectorProvider.openSelector()</h4><figure class="highlight java"><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">/*</span></span><br><span class="line"><span class="comment"> * SelectorProvider for sun.nio.ch.WindowsSelectorImpl.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * @author Konstantin Kladko</span></span><br><span class="line"><span class="comment"> * @since 1.4</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WindowsSelectorProvider</span> <span class="keyword">extends</span> <span class="title class_">SelectorProviderImpl</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> AbstractSelector <span class="title function_">openSelector</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">WindowsSelectorImpl</span>(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4id="epollselectorprovider.openselector">EPollSelectorProvider.openSelector()</h4><figure class="highlight java"><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EPollSelectorProvider</span></span><br><span class="line">    <span class="keyword">extends</span> <span class="title class_">SelectorProviderImpl</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> AbstractSelector <span class="title function_">openSelector</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">EPollSelectorImpl</span>(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> Channel <span class="title function_">inheritedChannel</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> InheritedChannel.getChannel();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="继承关系小结">继承关系小结</h3><h4 id="selector系列">Selector系列</h4><ul><li><code>abstract class Selector</code><ul><li><code>abstract class AbstractSelector extends Selector</code><ul><li><code>abstract class SelectorImpl exntends AbstractSelector</code><ul><li><code>class WindowsSelectorImpl extends SelectorImpl</code></li><li><code>class EPollSelectorImpl extends SelectorImpl</code></li></ul></li></ul></li></ul></li></ul><h4 id="selectorprovider系列">SelectorProvider系列</h4><ul><li><code>abstract class SelectorProvider</code><ul><li><code>abstract class SelectorProviderImpl extends SelectorProvider</code><ul><li><code>class WindowsSelectorProvider extends SelectorProviderImpl</code></li><li><code>class EPollSelectorProvider extends SelectorProviderImpl</code></li></ul></li></ul></li><li><code>class DefaultSelectorProvider</code></li></ul><h2 id="实现层">实现层</h2><h3 id="windows-jdk11的实现">Windows JDK11的实现</h3><p>在WindowsJDK11中，其实例化的是<code>sun.nio.ch.WindowsSelectorProvider</code>类返回给上层使用。</p><h4 id="windowsselectorprovider">WindowsSelectorProvider</h4><p>简单回顾一下，WindowsSelectorProvider实现了从对外的SelectorProvider到具体的WindowsSelectorImpl实现类的转接。</p><p>该类继承自<code>SelectorProviderImpl</code>抽象类，是对其的具体实现，供外部抽象层调用，实现的只是转接调用，调用<code>WindowsSelectorImpl</code>这一个实现类。</p><figure class="highlight java"><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">public</span> <span class="keyword">class</span> <span class="title class_">WindowsSelectorProvider</span> <span class="keyword">extends</span> <span class="title class_">SelectorProviderImpl</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> AbstractSelector <span class="title function_">openSelector</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">WindowsSelectorImpl</span>(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="windowsselectorimpl">WindowsSelectorImpl</h4><p>概括地来讲，WindowsSelectorImpl的底层实现是通过JNI接口调用地本地poll方法，但是不是简单调用，而是进行了多线程的改进。</p><p>为什么要采用多线程呢？因为poll方法本身可以处理的文件描述符（filedescriptor）数量是有限的，一般和select方法类似，不超过1024个。实际的应用场景中，需要并发处理的文件描述符是完全有可能超过这个上限的。WindowsJDK11中的实现则采用多线程对poll进行改进，一个线程能处理的文件描述符数量是有限的，那么如果文件描述符数量很多，用多个线程分摊处理不就好了么。</p><h5 id="主要数据结构">主要数据结构</h5><table><colgroup><col style="width: 25%" /><col style="width: 13%" /><col style="width: 61%" /></colgroup><thead><tr class="header"><th>类型</th><th>变量</th><th>说明</th></tr></thead><tbody><tr class="odd"><td><code>SelectionKeyImpl[]</code></td><td>channelArray</td><td>The list of SelectableChannels serviced by this Selector. Every modMAX_SELECTABLE_FDS entry is bogus, to align this array with the pollarray, where the corresponding entry is occupied by thewakeupSocket</td></tr><tr class="even"><td><code>PollArrayWrapper</code></td><td>pollWrapper</td><td>The global native poll array holds file decriptors and eventmasks</td></tr><tr class="odd"><td><code>List&lt;SelectThread&gt;</code></td><td>threads</td><td>A list of helper threads for select.</td></tr><tr class="even"><td><code>Pipe</code></td><td>wakeupPipe</td><td>Pipe used as a wakeup object.</td></tr><tr class="odd"><td><code>FdMap</code></td><td>fdMap</td><td>Maps file descriptors to their indices in pollArray</td></tr><tr class="even"><td><code>SubSelector</code></td><td>subSelector</td><td>SubSelector for the main thread</td></tr><tr class="odd"><td><code>Object</code></td><td>interruptLock</td><td>Lock for interrupt triggering and clearing</td></tr><tr class="even"><td><code>Object</code></td><td>updateLock</td><td>pending new registrations/updates, queued by implRegister andsetEventOps</td></tr><tr class="odd"><td><code>Deque&lt;SelectionKeyImpl&gt;</code></td><td>newKeys</td><td></td></tr><tr class="even"><td><code>Deque&lt;SelectionKeyImpl&gt;</code></td><td>updateKeys</td><td></td></tr></tbody></table><h5id="windowsselectorimpl.doselect">WindowsSelectorImpl.doSelect()</h5><p>Windows平台JDK11是如何select出对应状态的SocketChannel的呢？</p><p>抽象层的<code>Selector.select()</code>调用由<code>SelectorImpl.select()</code>实现，而该实现主要是调用了<code>SelectorImpl.lockAndDoSelect()</code>，其中调用<code>SelectorImpl.doSelect()</code>，该方法在Windows平台的JDK11中由<code>WindowsSelectorImpl.doSelect()</code>具体实现。</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * A multi-threaded implementation of Selector for Windows.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Konstantin Kladko</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Mark Reinhold</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WindowsSelectorImpl</span> <span class="keyword">extends</span> <span class="title class_">SelectorImpl</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">doSelect</span><span class="params">(Consumer&lt;SelectionKey&gt; action, <span class="type">long</span> timeout)</span></span><br><span class="line">        <span class="keyword">throws</span> IOException</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">assert</span> Thread.holdsLock(<span class="built_in">this</span>);</span><br><span class="line">        <span class="built_in">this</span>.timeout = timeout; <span class="comment">// set selector timeout</span></span><br><span class="line">        processUpdateQueue();</span><br><span class="line">        processDeregisterQueue();</span><br><span class="line">        <span class="keyword">if</span> (interruptTriggered) &#123;</span><br><span class="line">            resetWakeupSocket();</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">// Calculate number of helper threads needed for poll. If necessary</span></span><br><span class="line">        <span class="comment">// threads are created here and start waiting on startLock</span></span><br><span class="line">        adjustThreadsCount();</span><br><span class="line">        finishLock.reset(); <span class="comment">// reset finishLock</span></span><br><span class="line">        <span class="comment">// Wakeup helper threads, waiting on startLock, so they start polling.</span></span><br><span class="line">        <span class="comment">// Redundant threads will exit here after wakeup.</span></span><br><span class="line">        startLock.startThreads();</span><br><span class="line">        <span class="comment">// do polling in the main thread. Main thread is responsible for</span></span><br><span class="line">        <span class="comment">// first MAX_SELECTABLE_FDS entries in pollArray.</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            begin();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                subSelector.poll();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                finishLock.setException(e); <span class="comment">// Save this exception</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// Main thread is out of poll(). Wakeup others and wait for them</span></span><br><span class="line">            <span class="keyword">if</span> (threads.size() &gt; <span class="number">0</span>)</span><br><span class="line">                finishLock.waitForHelperThreads();</span><br><span class="line">          &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">              end();</span><br><span class="line">          &#125;</span><br><span class="line">        <span class="comment">// Done with poll(). Set wakeupSocket to nonsignaled  for the next run.</span></span><br><span class="line">        finishLock.checkForException();</span><br><span class="line">        processDeregisterQueue();</span><br><span class="line">        <span class="type">int</span> <span class="variable">updated</span> <span class="operator">=</span> updateSelectedKeys(action);</span><br><span class="line">        <span class="comment">// Done with poll(). Set wakeupSocket to nonsignaled  for the next run.</span></span><br><span class="line">        resetWakeupSocket();</span><br><span class="line">        <span class="keyword">return</span> updated;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>调用<code>WindowsSelectorImpl.doSelect()</code>方法，执行的流程主要为：</p><ol type="1"><li>首先进行了一些状态更新，处理新的注册和修改、被取消的键集。</li><li>随后计算所需线程数量，准备多线程poll操作所需的辅助线程（helperthreads）。<ol type="1"><li>如果主线程就足够处理当前这么多的描述符了，那就不需要再启动辅助线程了；</li><li>如果主线程没法独自处理大量的描述符，那就需要创建并启动辅助线程来帮忙。</li></ol></li><li>主线程本身当然是要承担poll的工作的，即<code>subSelector.poll()</code>，这是主线程自己调用自己的subSelector在执行poll操作。</li><li>如果有辅助线程帮忙，即<code>threads.size()&gt;0</code>的情况，那么就需要通过<code>finishLock.waitForHelperThreads()</code>的同步操作来等待辅助线程们完成他们的工作。</li><li>至此，poll的处理就完成了，此后进行一些收尾的检查，状态的更新，即可返回本次<code>doSelect</code>操作更新过的键的数量。</li></ol><h4id="windowsselectorimpl.selectthread">WindowsSelectorImpl.SelectThread</h4><h5id="windowsselectorimpl.selectthread.run">WindowsSelectorImpl.SelectThread.run()</h5><p>辅助线程是<code>WindowsSelectorImpl.SelectThread</code>类的实例，线程类最核心的内容就是其实现的<code>run</code>方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Represents a helper thread used for select.</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">SelectThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> index; <span class="comment">// index of this thread</span></span><br><span class="line">    <span class="keyword">final</span> SubSelector subSelector;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">long</span> <span class="variable">lastRun</span> <span class="operator">=</span> <span class="number">0</span>; <span class="comment">// last run number</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> zombie;</span><br><span class="line">    <span class="comment">// Creates a new thread</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">SelectThread</span><span class="params">(<span class="type">int</span> i)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(<span class="literal">null</span>, <span class="literal">null</span>, <span class="string">&quot;SelectorHelper&quot;</span>, <span class="number">0</span>, <span class="literal">false</span>);</span><br><span class="line">        <span class="built_in">this</span>.index = i;</span><br><span class="line">        <span class="built_in">this</span>.subSelector = <span class="keyword">new</span> <span class="title class_">SubSelector</span>(i);</span><br><span class="line">        <span class="comment">//make sure we wait for next round of poll</span></span><br><span class="line">        <span class="built_in">this</span>.lastRun = startLock.runsCounter;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">makeZombie</span><span class="params">()</span> &#123;</span><br><span class="line">        zombie = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">isZombie</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> zombie;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123; <span class="comment">// poll loop</span></span><br><span class="line">            <span class="comment">// wait for the start of poll. If this thread has become</span></span><br><span class="line">            <span class="comment">// redundant, then exit.</span></span><br><span class="line">            <span class="keyword">if</span> (startLock.waitForStart(<span class="built_in">this</span>)) &#123;</span><br><span class="line">                subSelector.freeFDSetBuffer();</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// call poll()</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                subSelector.poll(index);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="comment">// Save this exception and let other threads finish.</span></span><br><span class="line">                finishLock.setException(e);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// notify main thread, that this thread has finished, and</span></span><br><span class="line">            <span class="comment">// wakeup others, if this thread is the first to finish.</span></span><br><span class="line">            finishLock.threadFinished();</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>subSelector.poll(index)</code>，以此对本线程负责的文件描述符进行poll操作。</p><p>那这个<code>subSelector</code>又是怎么做的呢？</p><h4id="windowsselectorimpl.subselector">WindowsSelectorImpl.SubSelector</h4><p>前面介绍了主线程和辅助线程，两者都有一个subSelector实例，他们在执行poll操作的时候都是调用的<code>subSelector.poll()</code>。</p><h5id="windowsselectorimpl.subselector.poll">WindowsSelectorImpl.SubSelector.poll()</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">SubSelector</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> pollArrayIndex; <span class="comment">// starting index in pollArray to poll</span></span><br><span class="line">    <span class="comment">// These arrays will hold result of native select().</span></span><br><span class="line">    <span class="comment">// The first element of each array is the number of selected sockets.</span></span><br><span class="line">    <span class="comment">// Other elements are file descriptors of selected sockets.</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span>[] readFds = <span class="keyword">new</span> <span class="title class_">int</span> [MAX_SELECTABLE_FDS + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span>[] writeFds = <span class="keyword">new</span> <span class="title class_">int</span> [MAX_SELECTABLE_FDS + <span class="number">1</span>];</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span>[] exceptFds = <span class="keyword">new</span> <span class="title class_">int</span> [MAX_SELECTABLE_FDS + <span class="number">1</span>];</span><br><span class="line">    <span class="comment">// Buffer for readfds, writefds and exceptfds structs that are passed</span></span><br><span class="line">    <span class="comment">// to native select().</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">fdsBuffer</span> <span class="operator">=</span> unsafe.allocateMemory(SIZEOF_FD_SET * <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">SubSelector</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.pollArrayIndex = <span class="number">0</span>; <span class="comment">// main thread</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">SubSelector</span><span class="params">(<span class="type">int</span> threadIndex)</span> &#123; <span class="comment">// helper threads</span></span><br><span class="line">        <span class="built_in">this</span>.pollArrayIndex = (threadIndex + <span class="number">1</span>) * MAX_SELECTABLE_FDS;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">poll</span><span class="params">()</span> <span class="keyword">throws</span> IOException&#123; <span class="comment">// poll for the main thread</span></span><br><span class="line">        <span class="keyword">return</span> poll0(pollWrapper.pollArrayAddress,</span><br><span class="line">                     Math.min(totalChannels, MAX_SELECTABLE_FDS),</span><br><span class="line">                     readFds, writeFds, exceptFds, timeout, fdsBuffer);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">poll</span><span class="params">(<span class="type">int</span> index)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="comment">// poll for helper threads</span></span><br><span class="line">        <span class="keyword">return</span>  poll0(pollWrapper.pollArrayAddress +</span><br><span class="line">                 (pollArrayIndex * PollArrayWrapper.SIZE_POLLFD),</span><br><span class="line">                 Math.min(MAX_SELECTABLE_FDS,</span><br><span class="line">                         totalChannels - (index + <span class="number">1</span>) * MAX_SELECTABLE_FDS),</span><br><span class="line">                 readFds, writeFds, exceptFds, timeout, fdsBuffer);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">poll0</span><span class="params">(<span class="type">long</span> pollAddress, <span class="type">int</span> numfds,</span></span><br><span class="line"><span class="params">         <span class="type">int</span>[] readFds, <span class="type">int</span>[] writeFds, <span class="type">int</span>[] exceptFds, <span class="type">long</span> timeout, <span class="type">long</span> fdsBuffer)</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>查看源码可知，SubSelector的<code>poll()</code>和<code>poll(index)</code>方法实际上都是对<code>poll0()</code>方法的一层适配封装，实际上调用的就是<code>poll0()</code>。</p><h5id="windowsselectorimpl.subselector.poll0">WindowsSelectorImpl.SubSelector.poll0()</h5><p>从上面的源码可以看到，<code>poll0</code>方法并不是在Java中实现的，而是通过JNI调用的本地实现。</p><h3 id="linux-jdk11的实现">Linux JDK11的实现</h3><p>在LinuxJDK11中，其实例化的是<code>sun.nio.ch.EPollSelectorProvider</code>类返回给上层使用。</p><h4 id="epollselectorprovider">EPollSelectorProvider</h4><p>类似的，LinuxJDK11是通过EPollSelectorProvider提供外部访问接口的。</p><figure class="highlight java"><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EPollSelectorProvider</span></span><br><span class="line">    <span class="keyword">extends</span> <span class="title class_">SelectorProviderImpl</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">public</span> AbstractSelector <span class="title function_">openSelector</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">EPollSelectorImpl</span>(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Channel <span class="title function_">inheritedChannel</span><span class="params">()</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="keyword">return</span> InheritedChannel.getChannel();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该<code>openSelector</code>方法主要是通过EPollSelectorImpl实现类来实例化一个EPollSelector并返回。</p><h4 id="epollselectorimpl">EPollSelectorImpl</h4><h5 id="epollselectorimpl.doselector">EPollSelectorImpl.doSelector</h5><p>Linux平台JDK11是如何select出对应状态的SocketChannel的呢？</p><p>实质上是调用的<code>EPoll.wait</code>方法来返回已经就绪的文件描述符数量。</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Linux epoll based Selector implementation</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EPollSelectorImpl</span> <span class="keyword">extends</span> <span class="title class_">SelectorImpl</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">    </span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">int</span> <span class="title function_">doSelect</span><span class="params">(Consumer&lt;SelectionKey&gt; action, <span class="type">long</span> timeout)</span></span><br><span class="line">        <span class="keyword">throws</span> IOException</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">assert</span> Thread.holdsLock(<span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// epoll_wait timeout is int</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">to</span> <span class="operator">=</span> (<span class="type">int</span>) Math.min(timeout, Integer.MAX_VALUE);</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">blocking</span> <span class="operator">=</span> (to != <span class="number">0</span>);</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">timedPoll</span> <span class="operator">=</span> (to &gt; <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">int</span> numEntries;</span><br><span class="line">        processUpdateQueue();</span><br><span class="line">        processDeregisterQueue();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            begin(blocking);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">do</span> &#123;</span><br><span class="line">                <span class="type">long</span> <span class="variable">startTime</span> <span class="operator">=</span> timedPoll ? System.nanoTime() : <span class="number">0</span>;</span><br><span class="line">                numEntries = EPoll.wait(epfd, pollArrayAddress, NUM_EPOLLEVENTS, to);</span><br><span class="line">                <span class="keyword">if</span> (numEntries == IOStatus.INTERRUPTED &amp;&amp; timedPoll) &#123;</span><br><span class="line">                    <span class="comment">// timed poll interrupted so need to adjust timeout</span></span><br><span class="line">                    <span class="type">long</span> <span class="variable">adjust</span> <span class="operator">=</span> System.nanoTime() - startTime;</span><br><span class="line">                    to -= TimeUnit.MILLISECONDS.convert(adjust, TimeUnit.NANOSECONDS);</span><br><span class="line">                    <span class="keyword">if</span> (to &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                        <span class="comment">// timeout expired so no retry</span></span><br><span class="line">                        numEntries = <span class="number">0</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">while</span> (numEntries == IOStatus.INTERRUPTED);</span><br><span class="line">            <span class="keyword">assert</span> IOStatus.check(numEntries);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            end(blocking);</span><br><span class="line">        &#125;</span><br><span class="line">        processDeregisterQueue();</span><br><span class="line">        <span class="keyword">return</span> processEvents(numEntries, action);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">/* more */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>具体地，在<code>EPollSelectorImpl.doSelect</code>方法中，和WindowsSelectorImpl中的实现类似：</p><ol type="1"><li>首先都有必要检查和更新状态，处理修改队列和取消注册队列；</li><li>通过<code>EPoll.wait</code>方法来获取处于就绪状态的I/O文件描述符数量；</li><li>最后更新状态，返回本次<code>doSelect</code>更新过的键的数量。</li></ol><h4 id="epoll">EPoll</h4><p>EPoll作为Linux内核提供的多路复用器，JDK11选择通过JNI接口来调用其功能。</p><p>JDK11中<code>EPoll</code>类是一个简易的包装类，epoll的实现不由JDK负责。</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Provides access to the Linux epoll facility.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">EPoll</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">EPoll</span><span class="params">()</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Unsafe</span> <span class="variable">unsafe</span> <span class="operator">=</span> Unsafe.getUnsafe();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * typedef union epoll_data &#123;</span></span><br><span class="line"><span class="comment">     *     void *ptr;</span></span><br><span class="line"><span class="comment">     *     int fd;</span></span><br><span class="line"><span class="comment">     *     __uint32_t u32;</span></span><br><span class="line"><span class="comment">     *     __uint64_t u64;</span></span><br><span class="line"><span class="comment">     *  &#125; epoll_data_t;</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * struct epoll_event &#123;</span></span><br><span class="line"><span class="comment">     *     __uint32_t events;</span></span><br><span class="line"><span class="comment">     *     epoll_data_t data;</span></span><br><span class="line"><span class="comment">     * &#125;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">SIZEOF_EPOLLEVENT</span>   <span class="operator">=</span> eventSize();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OFFSETOF_EVENTS</span>     <span class="operator">=</span> eventsOffset();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">OFFSETOF_FD</span>         <span class="operator">=</span> dataOffset();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// opcodes</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPOLL_CTL_ADD</span>  <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPOLL_CTL_DEL</span>  <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPOLL_CTL_MOD</span>  <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// events</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPOLLIN</span>   <span class="operator">=</span> <span class="number">0x1</span>;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPOLLOUT</span>  <span class="operator">=</span> <span class="number">0x4</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// flags</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">EPOLLONESHOT</span>   <span class="operator">=</span> (<span class="number">1</span> &lt;&lt; <span class="number">30</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Allocates a poll array to handle up to &#123;<span class="doctag">@code</span> count&#125; events.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="type">long</span> <span class="title function_">allocatePollArray</span><span class="params">(<span class="type">int</span> count)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> unsafe.allocateMemory(count * SIZEOF_EPOLLEVENT);</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="comment">     * Free a poll array</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">freePollArray</span><span class="params">(<span class="type">long</span> address)</span> &#123;</span><br><span class="line">        unsafe.freeMemory(address);</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="comment">     * Returns event[i];</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="type">long</span> <span class="title function_">getEvent</span><span class="params">(<span class="type">long</span> address, <span class="type">int</span> i)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> address + (SIZEOF_EPOLLEVENT*i);</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="comment">     * Returns event-&gt;data.fd</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">getDescriptor</span><span class="params">(<span class="type">long</span> eventAddress)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> unsafe.getInt(eventAddress + OFFSETOF_FD);</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="comment">     * Returns event-&gt;events</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">getEvents</span><span class="params">(<span class="type">long</span> eventAddress)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> unsafe.getInt(eventAddress + OFFSETOF_EVENTS);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// -- Native methods --</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">eventSize</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">eventsOffset</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">dataOffset</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">create</span><span class="params">()</span> <span class="keyword">throws</span> IOException;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">ctl</span><span class="params">(<span class="type">int</span> epfd, <span class="type">int</span> opcode, <span class="type">int</span> fd, <span class="type">int</span> events)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">wait</span><span class="params">(<span class="type">int</span> epfd, <span class="type">long</span> pollAddress, <span class="type">int</span> numfds, <span class="type">int</span> timeout)</span></span><br><span class="line">        <span class="keyword">throws</span> IOException;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        IOUtil.load();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="epoll.wait">EPoll.wait</h5><figure class="highlight java"><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="keyword">static</span> <span class="keyword">native</span> <span class="type">int</span> <span class="title function_">wait</span><span class="params">(<span class="type">int</span> epfd, <span class="type">long</span> pollAddress, <span class="type">int</span> numfds, <span class="type">int</span> timeout)</span></span><br><span class="line">        <span class="keyword">throws</span> IOException;</span><br></pre></td></tr></table></figure><p>该方法调用的应该是Linux中的<code>epoll_wait</code>系统调用。根据<code>man epoll_wait</code>查阅的Linux手册，具体说明：</p><blockquote><p>The epoll_wait() system call waits for events on the epoll(7)instance referred to by the file descriptor epfd. The memory areapointed to by events will contain the events that will be available forthe caller. Up to maxevents are returned by epoll_wait(). The maxeventsargument must be greater than zero.</p><p>The timeout argument specifies the number of milliseconds thatepoll_wait() will block.</p></blockquote><p>也就是说，JDK调用的<code>EPoll.wait</code>方法会在timeout时间内阻塞等待epoll的文件描述符epfd所引用的事件发生，发生后，其返回的结果是代表事件数量的整数。</p><h2 id="总结">总结</h2><p>从表层的<code>Selector</code>查到底层的<code>WindowsSelectorImpl</code>与<code>EPollImpl</code>，经过一层层抽丝剥茧，可以看到JDK在设计上清晰地体现着将抽象与实现分离的“依赖倒置原则”——顶层调用不应该依赖于底层实现，底层实现也不应该针对于顶层调用，双方都应该依赖于抽象。</p><p>考虑到Linux内核已经提供了好用的epoll多路复用，足以处理大规模的并发连接，JDK11通过JNI接口对epoll相关的系统调用进行本地调用即可，其实现也显得相对简单。Windows并未提供Epoll这样的多路复用模型，为解决poll存在的并发连接数量有限的问题，JDK11通过分而治之的分治思想，拉辅助线程来分担任务，通过实现动态多线程poll巧妙地实现了处理大量并发连接的能力。</p><p>最终，无论是Windows还是Linux，要想研究多路复用机制的更深层的实现原理，还是需要研究操作系统层级的实现原理。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;阅读JDK11源码实现的过程中，发现同为&lt;code&gt;java.nio.channels.Selector&lt;/code&gt;，是Windows和Linux平台的&lt;code&gt;Selector.open()&lt;/code&gt;所构造的Selector的底层实现完全不一样。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Java" scheme="https://heary.cn/tags/Java/"/>
    
    <category term="NIO" scheme="https://heary.cn/tags/NIO/"/>
    
    <category term="JDK" scheme="https://heary.cn/tags/JDK/"/>
    
    <category term="I/O Multiplexing" scheme="https://heary.cn/tags/I-O-Multiplexing/"/>
    
    <category term="Poll" scheme="https://heary.cn/tags/Poll/"/>
    
    <category term="EPoll" scheme="https://heary.cn/tags/EPoll/"/>
    
  </entry>
  
  <entry>
    <title>HTTP服务器压力测试</title>
    <link href="https://heary.cn/posts/HTTP%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/"/>
    <id>https://heary.cn/posts/HTTP%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95/</id>
    <published>2020-07-30T03:40:50.000Z</published>
    <updated>2022-08-07T04:02:09.606Z</updated>
    
    <content type="html"><![CDATA[<p>对<ahref="https://github.com/HearyShen/HearyHTTPd">HearyHTTPd</a>进行压力测试。网上的压力测试工具很多，我测试了<ahref="https://httpd.apache.org/docs/2.4/programs/ab.html">Apachebenchmark</a>和<ahref="https://github.com/EZLippi/WebBench">WebBench</a>两款压力测试工具，并在台式机和实验室计算服务器上分别进行了压力测试，QPS分别约6400+、21000+和30000+。</p><span id="more"></span><h1 id="http服务器压力测试">HTTP服务器压力测试</h1><h2 id="apache-benchmark">1 Apache benchmark</h2><h3 id="简介">1.1 简介</h3><blockquote><p><a href="https://httpd.apache.org/docs/2.4/programs/ab.html">Apachebenchmark</a></p><p><strong>ab - Apache HTTP server benchmarking tool</strong></p><p><code>ab</code> is a tool for benchmarking your Apache HypertextTransfer Protocol (HTTP) server. It is designed to give you animpression of how your current Apache installation performs. Thisespecially shows you how many requests per second your Apacheinstallation is capable of serving.</p></blockquote><p>Apachebenchmark是一款Apache提供的HTTP服务器压力测试工具，随Apache安装。</p><h3 id="安装">1.2 安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install apache2-utils</span><br></pre></td></tr></table></figure><h3 id="测试">1.3 测试</h3><p>我测试了我的<ahref="https://github.com/HearyShen/HearyHTTPd">HearyHTTPd</a>，用1000个客户机并发请求10万次。</p><p>在我的实验室台式机上进行了测试：</p><ul><li>CPU：Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz</li><li>内存：单通道 8GB DDR4 2400Mhz</li><li>通过WSL中的Ubuntu来测试Windows环境JRE下运行的hhttpd。</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">jyshen@JYSHEN-WORKPC:~$ ab -n 100000 -c 1000 http://localhost:8080/</span><br><span class="line">This is ApacheBench, Version 2.3 &lt;<span class="variable">$Revision</span>: 1807734 $&gt;</span><br><span class="line">Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/</span><br><span class="line">Licensed to The Apache Software Foundation, http://www.apache.org/</span><br><span class="line"></span><br><span class="line">Benchmarking localhost (be patient)</span><br><span class="line">Completed 10000 requests</span><br><span class="line">Completed 20000 requests</span><br><span class="line">Completed 30000 requests</span><br><span class="line">Completed 40000 requests</span><br><span class="line">Completed 50000 requests</span><br><span class="line">Completed 60000 requests</span><br><span class="line">Completed 70000 requests</span><br><span class="line">Completed 80000 requests</span><br><span class="line">Completed 90000 requests</span><br><span class="line">Completed 100000 requests</span><br><span class="line">Finished 100000 requests</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Server Software:</span><br><span class="line">Server Hostname:        localhost</span><br><span class="line">Server Port:            8080</span><br><span class="line"></span><br><span class="line">Document Path:          /</span><br><span class="line">Document Length:        0 bytes</span><br><span class="line"></span><br><span class="line">Concurrency Level:      1000</span><br><span class="line">Time taken <span class="keyword">for</span> tests:   15.595 seconds</span><br><span class="line">Complete requests:      100000</span><br><span class="line">Failed requests:        0</span><br><span class="line">Total transferred:      1300000 bytes</span><br><span class="line">HTML transferred:       0 bytes</span><br><span class="line">Requests per second:    6412.45 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">Time per request:       155.947 [ms] (mean)</span><br><span class="line">Time per request:       0.156 [ms] (mean, across all concurrent requests)</span><br><span class="line">Transfer rate:          81.41 [Kbytes/sec] received</span><br><span class="line"></span><br><span class="line">Connection Times (ms)</span><br><span class="line">              min  mean[+/-sd] median   max</span><br><span class="line">Connect:       33   78  20.6     78     143</span><br><span class="line">Processing:    29   77  20.8     77     147</span><br><span class="line">Waiting:        4   45  16.8     41     146</span><br><span class="line">Total:         72  155   7.7    154     195</span><br><span class="line"></span><br><span class="line">Percentage of the requests served within a certain time (ms)</span><br><span class="line">  50%    154</span><br><span class="line">  66%    155</span><br><span class="line">  75%    156</span><br><span class="line">  80%    156</span><br><span class="line">  90%    163</span><br><span class="line">  95%    169</span><br><span class="line">  98%    173</span><br><span class="line">  99%    184</span><br><span class="line"> 100%    195 (longest request)</span><br></pre></td></tr></table></figure><ul><li>测得结果平均每秒能处理6412个请求。</li><li>多次测试能稳定在6000以上。</li><li>测试中观察内存和CPU消耗无异常情况。</li><li>其他影响因素：实验过程中，Windows Defender的Antimalware ServiceExecutable进程的CPU使用率明显上升，约7~10%。</li></ul><p>另外，我在实验室的新服务器上进行了测试：</p><ul><li>CPU：2颗Intel(R) Xeon(R) Gold 6132 CPU @ 2.60GHz</li><li>内存：512G（16根32G） ECC DDR4 2666Mhz</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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">(base) sjy@h3c-UniServer-R5200-G3:~$ ab -n 100000 -c 1000 http://localhost:8080/</span><br><span class="line">This is ApacheBench, Version 2.3 &lt;<span class="variable">$Revision</span>: 1807734 $&gt;</span><br><span class="line">Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/</span><br><span class="line">Licensed to The Apache Software Foundation, http://www.apache.org/</span><br><span class="line"></span><br><span class="line">Benchmarking localhost (be patient)</span><br><span class="line">Completed 10000 requests</span><br><span class="line">Completed 20000 requests</span><br><span class="line">Completed 30000 requests</span><br><span class="line">Completed 40000 requests</span><br><span class="line">Completed 50000 requests</span><br><span class="line">Completed 60000 requests</span><br><span class="line">Completed 70000 requests</span><br><span class="line">Completed 80000 requests</span><br><span class="line">Completed 90000 requests</span><br><span class="line">Completed 100000 requests</span><br><span class="line">Finished 100000 requests</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Server Software:        </span><br><span class="line">Server Hostname:        localhost</span><br><span class="line">Server Port:            8080</span><br><span class="line"></span><br><span class="line">Document Path:          /</span><br><span class="line">Document Length:        0 bytes</span><br><span class="line"></span><br><span class="line">Concurrency Level:      1000</span><br><span class="line">Time taken <span class="keyword">for</span> tests:   4.753 seconds</span><br><span class="line">Complete requests:      100000</span><br><span class="line">Failed requests:        0</span><br><span class="line">Total transferred:      1300000 bytes</span><br><span class="line">HTML transferred:       0 bytes</span><br><span class="line">Requests per second:    21041.56 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">Time per request:       47.525 [ms] (mean)</span><br><span class="line">Time per request:       0.048 [ms] (mean, across all concurrent requests)</span><br><span class="line">Transfer rate:          267.13 [Kbytes/sec] received</span><br><span class="line"></span><br><span class="line">Connection Times (ms)</span><br><span class="line">              min  mean[+/-sd] median   max</span><br><span class="line">Connect:        7   24  63.7     20    1052</span><br><span class="line">Processing:     6   23   6.1     24      49</span><br><span class="line">Waiting:        5   17   5.1     16      44</span><br><span class="line">Total:         21   47  64.3     46    1084</span><br><span class="line"></span><br><span class="line">Percentage of the requests served within a certain time (ms)</span><br><span class="line">  50%     46</span><br><span class="line">  66%     47</span><br><span class="line">  75%     48</span><br><span class="line">  80%     49</span><br><span class="line">  90%     51</span><br><span class="line">  95%     53</span><br><span class="line">  98%     56</span><br><span class="line">  99%     62</span><br><span class="line"> 100%   1084 (longest request)</span><br></pre></td></tr></table></figure><ul><li>测得结果平均每秒能处理21041个请求。</li></ul><p>我还在另一台计算服务器上进行了测试：</p><ul><li>CPU：2颗Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz</li><li>内存：128G（4根32G）ECC DDR4 2666Mhz</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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></pre></td><td class="code"><pre><span class="line">jyshen@ubuntu:~$ ab -c 1000 -n 100000 http://127.0.0.1:8080/</span><br><span class="line">This is ApacheBench, Version 2.3 &lt;<span class="variable">$Revision</span>: 1706008 $&gt;</span><br><span class="line">Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/</span><br><span class="line">Licensed to The Apache Software Foundation, http://www.apache.org/</span><br><span class="line"></span><br><span class="line">Benchmarking 127.0.0.1 (be patient)</span><br><span class="line">Completed 10000 requests</span><br><span class="line">Completed 20000 requests</span><br><span class="line">Completed 30000 requests</span><br><span class="line">Completed 40000 requests</span><br><span class="line">Completed 50000 requests</span><br><span class="line">Completed 60000 requests</span><br><span class="line">Completed 70000 requests</span><br><span class="line">Completed 80000 requests</span><br><span class="line">Completed 90000 requests</span><br><span class="line">Completed 100000 requests</span><br><span class="line">Finished 100000 requests</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Server Software:        </span><br><span class="line">Server Hostname:        127.0.0.1</span><br><span class="line">Server Port:            8080</span><br><span class="line"></span><br><span class="line">Document Path:          /</span><br><span class="line">Document Length:        152 bytes</span><br><span class="line"></span><br><span class="line">Concurrency Level:      1000</span><br><span class="line">Time taken <span class="keyword">for</span> tests:   3.296 seconds</span><br><span class="line">Complete requests:      100000</span><br><span class="line">Failed requests:        0</span><br><span class="line">Total transferred:      21500000 bytes</span><br><span class="line">HTML transferred:       15200000 bytes</span><br><span class="line">Requests per second:    30338.50 [<span class="comment">#/sec] (mean)</span></span><br><span class="line">Time per request:       32.961 [ms] (mean)</span><br><span class="line">Time per request:       0.033 [ms] (mean, across all concurrent requests)</span><br><span class="line">Transfer rate:          6369.90 [Kbytes/sec] received</span><br><span class="line"></span><br><span class="line">Connection Times (ms)</span><br><span class="line">              min  mean[+/-sd] median   max</span><br><span class="line">Connect:        0   16  63.0     13    1015</span><br><span class="line">Processing:     3   17   5.1     16      56</span><br><span class="line">Waiting:        3   13   4.9     11      55</span><br><span class="line">Total:          6   33  63.5     31    1042</span><br><span class="line"></span><br><span class="line">Percentage of the requests served within a certain time (ms)</span><br><span class="line">  50%     31</span><br><span class="line">  66%     32</span><br><span class="line">  75%     33</span><br><span class="line">  80%     34</span><br><span class="line">  90%     35</span><br><span class="line">  95%     36</span><br><span class="line">  98%     40</span><br><span class="line">  99%     44</span><br><span class="line"> 100%   1042 (longest request)</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>测的结果平均每秒能处理30338个请求。</li></ul><h2 id="webbench">2 webBench</h2><h3 id="简介-1">2.1 简介</h3><p>GitHub地址：<ahref="https://github.com/EZLippi/WebBench">WebBench</a></p><blockquote><p>Webbench是RadimKolar在1997年写的一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL，测试网站在压力下工作的性能，最多可以模拟3万个并发连接去测试网站的负载能力。官网地址:http://home.tiscali.cz/~cz210552/webbench.html</p></blockquote><h3 id="安装-1">2.2 安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># prerequisite</span></span><br><span class="line">sudo apt update</span><br><span class="line">sudo apt install build-essential</span><br><span class="line"></span><br><span class="line"><span class="comment"># install webbench</span></span><br><span class="line">wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz</span><br><span class="line">tar -zxvf webbench-1.5.tar.gz</span><br><span class="line"><span class="built_in">cd</span> webbench-1.5/</span><br><span class="line">sudo make</span><br><span class="line">sudo make install</span><br></pre></td></tr></table></figure><h3 id="测试-1">2.3 测试</h3><p>运行1万个并发client，1秒钟。</p><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><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">jyshen@JYSHEN-WORKPC:~$ webbench -c 10000 -t 1 http://localhost:8080/</span><br><span class="line">Webbench - Simple Web Benchmark 1.5</span><br><span class="line">Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.</span><br><span class="line"></span><br><span class="line">Benchmarking: GET http://localhost:8080/</span><br><span class="line">10000 clients, running 1 sec.</span><br><span class="line"></span><br><span class="line">Speed=39851336 pages/min, 8545485 bytes/sec.</span><br><span class="line">Requests: 664189 susceed, 0 failed.</span><br></pre></td></tr></table></figure><ul><li>测得66万多个并发请求均成功处理</li></ul><p>实验室的服务器无法fork创建出相同数量的1万子进程，提示：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">(base) sjy@h3c-UniServer-R5200-G3:~/HearyHTTPd/webbench-1.5$ webbench -c 10000 -t 1 http://localhost:8080/</span><br><span class="line">Webbench - Simple Web Benchmark 1.5</span><br><span class="line">Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.</span><br><span class="line"></span><br><span class="line">Benchmarking: GET http://localhost:8080/</span><br><span class="line">10000 clients, running 1 sec.</span><br><span class="line">problems forking worker no. 8841</span><br><span class="line">fork failed.: Resource temporarily unavailable</span><br></pre></td></tr></table></figure><p>通过<code>htop</code>查了下，服务器上还有其他同学的不少计算程序在进行。暂时无法对比。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;对&lt;a
href=&quot;https://github.com/HearyShen/HearyHTTPd&quot;&gt;HearyHTTPd&lt;/a&gt;进行压力测试。网上的压力测试工具很多，我测试了&lt;a
href=&quot;https://httpd.apache.org/docs/2.4/programs/ab.html&quot;&gt;Apache
benchmark&lt;/a&gt;和&lt;a
href=&quot;https://github.com/EZLippi/WebBench&quot;&gt;WebBench&lt;/a&gt;两款压力测试工具，并在台式机和实验室计算服务器上分别进行了压力测试，QPS分别约6400+、21000+和30000+。&lt;/p&gt;</summary>
    
    
    
    
    <category term="HTTP" scheme="https://heary.cn/tags/HTTP/"/>
    
    <category term="Benchmark" scheme="https://heary.cn/tags/Benchmark/"/>
    
  </entry>
  
  <entry>
    <title>ThreadPoolExecutor - 从JDK11源码理解Java线程池原理</title>
    <link href="https://heary.cn/posts/ThreadPoolExecutor-%E4%BB%8EJDK11%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%8E%9F%E7%90%86/"/>
    <id>https://heary.cn/posts/ThreadPoolExecutor-%E4%BB%8EJDK11%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3Java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%8E%9F%E7%90%86/</id>
    <published>2020-07-28T13:10:29.000Z</published>
    <updated>2022-08-07T04:02:09.621Z</updated>
    
    <content type="html"><![CDATA[<p>在开发<ahref="https://github.com/HearyShen/HearyHTTPd">HearyHTTPd</a>的过程中，为了有效利用多线程处理并发请求，我使用了Java的线程池机制。我查阅了JDK11中的线程池实现源码，本文对其原理进行进一步的梳理。</p><span id="more"></span><h1id="threadpoolexecutor---从jdk11源码理解java线程池原理">ThreadPoolExecutor- 从JDK11源码理解Java线程池原理</h1><h2 id="表层executors">1 表层——Executors</h2><p>JDK对外提供<code>Executors</code>类的三个静态方法供调用，可以快速生成线程池：</p><h3 id="newsinglethreadexecutor">1.1 newSingleThreadExecutor</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newSingleThreadExecutor</span><span class="params">()</span></span><br></pre></td></tr></table></figure><p>退化为只包含一个线程的“线程池”。</p><h3 id="newfixedthreadpool">1.2 newFixedThreadPool</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newFixedThreadPool</span> <span class="params">(<span class="type">int</span> nThreads)</span></span><br></pre></td></tr></table></figure><p>包含固定数量线程的线程池。</p><h3 id="newcachedthreadpool">1.3 newCachedThreadPool</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newCachedThreadPool</span><span class="params">()</span></span><br></pre></td></tr></table></figure><p>按需创建线程的线程池。</p><h2 id="深一层threadpoolexecutor">2 深一层——ThreadPoolExecutor</h2><p>实质上，以上三个对外的静态方法，本质上都实例化了同一个类型，即：<code>ThreadPoolExecutor</code>，该类继承自抽象类<code>java.util.concurrent.AbstractExecutorService</code>，该抽象类实现了<code>ExecutorService</code>接口，该接口又继承自<code>Executor</code>接口。其中，<code>ExecutorService</code>就是一般外部调用线程池实例的抽象接口。</p><p><code>ThreadPoolExecutor</code>提供构造函数：</p><figure class="highlight java"><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="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates a new &#123;<span class="doctag">@code</span> ThreadPoolExecutor&#125; with the given initial</span></span><br><span class="line"><span class="comment"> * parameters, the default thread factory and the default rejected</span></span><br><span class="line"><span class="comment"> * execution handler.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * &lt;p&gt;It may be more convenient to use one of the &#123;<span class="doctag">@link</span> Executors&#125;</span></span><br><span class="line"><span class="comment"> * factory methods instead of this general purpose constructor.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> corePoolSize the number of threads to keep in the pool, even</span></span><br><span class="line"><span class="comment"> *        if they are idle, unless &#123;<span class="doctag">@code</span> allowCoreThreadTimeOut&#125; is set</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> maximumPoolSize the maximum number of threads to allow in the</span></span><br><span class="line"><span class="comment"> *        pool</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> keepAliveTime when the number of threads is greater than</span></span><br><span class="line"><span class="comment"> *        the core, this is the maximum time that excess idle threads</span></span><br><span class="line"><span class="comment"> *        will wait for new tasks before terminating.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> unit the time unit for the &#123;<span class="doctag">@code</span> keepAliveTime&#125; argument</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> workQueue the queue to use for holding tasks before they are</span></span><br><span class="line"><span class="comment"> *        executed.  This queue will hold only the &#123;<span class="doctag">@code</span> Runnable&#125;</span></span><br><span class="line"><span class="comment"> *        tasks submitted by the &#123;<span class="doctag">@code</span> execute&#125; method.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IllegalArgumentException if one of the following holds:&lt;br&gt;</span></span><br><span class="line"><span class="comment"> *         &#123;<span class="doctag">@code</span> corePoolSize &lt; 0&#125;&lt;br&gt;</span></span><br><span class="line"><span class="comment"> *         &#123;<span class="doctag">@code</span> keepAliveTime &lt; 0&#125;&lt;br&gt;</span></span><br><span class="line"><span class="comment"> *         &#123;<span class="doctag">@code</span> maximumPoolSize &lt;= 0&#125;&lt;br&gt;</span></span><br><span class="line"><span class="comment"> *         &#123;<span class="doctag">@code</span> maximumPoolSize &lt; corePoolSize&#125;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> NullPointerException if &#123;<span class="doctag">@code</span> workQueue&#125; is null</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params">                          <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">                          <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params">                          TimeUnit unit,</span></span><br><span class="line"><span class="params">                          BlockingQueue&lt;Runnable&gt; workQueue)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,</span><br><span class="line">         Executors.defaultThreadFactory(), defaultHandler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以设置线程池的一系列参数：</p><ul><li>核心线程池尺寸：线程池至少保有多少线程；</li><li>最大线程池尺寸：线程池最多能创建多少线程；</li><li>保活时间：线程数量如果超过核心线程数了，最多允许空闲多久，超过即终止线程；</li><li>时间单位：保活时间的时间单位，可以是纳秒、微秒、毫秒、秒、分钟、小时、天；</li><li>工作队列：提交给线程池的任务在执行前，会先放到工作队列中。</li></ul><h3 id="newsinglethreadexecutor-1">2.1 newSingleThreadExecutor</h3><figure class="highlight java"><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="comment">/**</span></span><br><span class="line"><span class="comment"> * Creates an Executor that uses a single worker thread operating</span></span><br><span class="line"><span class="comment"> * off an unbounded queue. (Note however that if this single</span></span><br><span class="line"><span class="comment"> * thread terminates due to a failure during execution prior to</span></span><br><span class="line"><span class="comment"> * shutdown, a new one will take its place if needed to execute</span></span><br><span class="line"><span class="comment"> * subsequent tasks.)  Tasks are guaranteed to execute</span></span><br><span class="line"><span class="comment"> * sequentially, and no more than one task will be active at any</span></span><br><span class="line"><span class="comment"> * given time. Unlike the otherwise equivalent</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> newFixedThreadPool(1)&#125; the returned executor is</span></span><br><span class="line"><span class="comment"> * guaranteed not to be reconfigurable to use additional threads.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the newly created single-threaded Executor</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newSingleThreadExecutor</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">FinalizableDelegatedExecutorService</span></span><br><span class="line">        (<span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">1</span>, <span class="number">1</span>,</span><br><span class="line">                                <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line">                                <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;()));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>newSingleThreadExecutor</code>具体设置参数为：</p><ul><li>核心线程数和最大线程数都是1，这保证了线程池中有且只有一个线程。</li><li>因为只有1个线程，所以无所谓超时终止，因此保活时间为0。</li><li>提交给线程池的线程会放到一个<code>LinkedBlockingQueue</code>的实例中。<ul><li>这是一个默认无界的阻塞队列（可选有界以控制内存消耗）。</li></ul></li></ul><p>外面套了一层<code>FinalizableDelegatedExecutorService</code>实际上是该<code>Executors</code>类定义的一个内部静态类：</p><figure class="highlight java"><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">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">FinalizableDelegatedExecutorService</span></span><br><span class="line">        <span class="keyword">extends</span> <span class="title class_">DelegatedExecutorService</span> &#123;</span><br><span class="line">    FinalizableDelegatedExecutorService(ExecutorService executor) &#123;</span><br><span class="line">        <span class="built_in">super</span>(executor);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;deprecation&quot;)</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">finalize</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>只是实现了<code>finalize</code>方法，负责关闭线程池。</li></ul><p>该类进一步继承自另一个内部静态类<code>DelegatedExecutorService</code>，这是一个包装类，用于控制对外的提供的方法：</p><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * A wrapper class that exposes only the ExecutorService methods</span></span><br><span class="line"><span class="comment"> * of an ExecutorService implementation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">DelegatedExecutorService</span></span><br><span class="line">        <span class="keyword">implements</span> <span class="title class_">ExecutorService</span> &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="newfixedthreadpool-1">2.2 newFixedThreadPool</h3><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * Creates a thread pool that reuses a fixed number of threads</span></span><br><span class="line"><span class="comment"> * operating off a shared unbounded queue.  At any point, at most</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@code</span> nThreads&#125; threads will be active processing tasks.</span></span><br><span class="line"><span class="comment"> * If additional tasks are submitted when all threads are active,</span></span><br><span class="line"><span class="comment"> * they will wait in the queue until a thread is available.</span></span><br><span class="line"><span class="comment"> * If any thread terminates due to a failure during execution</span></span><br><span class="line"><span class="comment"> * prior to shutdown, a new one will take its place if needed to</span></span><br><span class="line"><span class="comment"> * execute subsequent tasks.  The threads in the pool will exist</span></span><br><span class="line"><span class="comment"> * until it is explicitly &#123;<span class="doctag">@link</span> ExecutorService#shutdown shutdown&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> nThreads the number of threads in the pool</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the newly created thread pool</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IllegalArgumentException if &#123;<span class="doctag">@code</span> nThreads &lt;= 0&#125;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newFixedThreadPool</span><span class="params">(<span class="type">int</span> nThreads)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(nThreads, nThreads,</span><br><span class="line">                                  <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line">                                  <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span>&lt;Runnable&gt;());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>newFixedThreadPool</code>具体设置参数为：</p><ul><li>核心线程数和最大线程数都是输入参数<code>nThreads</code>，这保证了线程池中有且只有<code>nThreads</code>个线程。</li><li>因为只有<code>nThreads</code>个线程，所以无所谓超时终止，因此保活时间为0。</li><li>提交给线程池的线程会放到一个<code>LinkedBlockingQueue</code>的实例中。</li></ul><p>除了可以设置多个线程，其他参数与上一个单线程的线程池非常相似。</p><h3 id="newcachedthreadpool-1">2.3 newCachedThreadPool</h3><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * Creates a thread pool that creates new threads as needed, but</span></span><br><span class="line"><span class="comment"> * will reuse previously constructed threads when they are</span></span><br><span class="line"><span class="comment"> * available.  These pools will typically improve the performance</span></span><br><span class="line"><span class="comment"> * of programs that execute many short-lived asynchronous tasks.</span></span><br><span class="line"><span class="comment"> * Calls to &#123;<span class="doctag">@code</span> execute&#125; will reuse previously constructed</span></span><br><span class="line"><span class="comment"> * threads if available. If no existing thread is available, a new</span></span><br><span class="line"><span class="comment"> * thread will be created and added to the pool. Threads that have</span></span><br><span class="line"><span class="comment"> * not been used for sixty seconds are terminated and removed from</span></span><br><span class="line"><span class="comment"> * the cache. Thus, a pool that remains idle for long enough will</span></span><br><span class="line"><span class="comment"> * not consume any resources. Note that pools with similar</span></span><br><span class="line"><span class="comment"> * properties but different details (for example, timeout parameters)</span></span><br><span class="line"><span class="comment"> * may be created using &#123;<span class="doctag">@link</span> ThreadPoolExecutor&#125; constructors.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the newly created thread pool</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newCachedThreadPool</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">0</span>, Integer.MAX_VALUE,</span><br><span class="line">                                  <span class="number">60L</span>, TimeUnit.SECONDS,</span><br><span class="line">                                  <span class="keyword">new</span> <span class="title class_">SynchronousQueue</span>&lt;Runnable&gt;());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>newCachedThreadPool</code>具体设置参数为：</p><ul><li>核心线程数为0，意味着如果线程池闲置后，不会保留任何线程。</li><li>最大线程数为最大整数，意味着如果需要，线程数量可以变得非常大。</li><li>缓存线程池的保活时间是60秒，线程不会立即被销毁，空闲60内如果有新任务，可以直接复用空闲线程。</li><li>提交给线程池的线程会放到一个<code>SynchronousQueue</code>的实例中。</li></ul><h2 id="再深一层从abstractexecutorservice到threadpoolexecutor">3再深一层——从AbstractExecutorService到ThreadPoolExecutor</h2><h3 id="任务提交伊始submit">3.1 任务提交伊始——submit</h3><p>外部使用线程池时，调用的是<code>submit</code>方法。该方法在接口<code>ExecutorService</code>中定义，在抽象类<code>AbstractExecutorService</code>中实现。</p><p>具体地，在抽象类<code>AbstractExecutorService</code>中，<code>submit</code>的实现为：</p><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> RejectedExecutionException &#123;<span class="doctag">@inheritDoc</span>&#125;</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> NullPointerException       &#123;<span class="doctag">@inheritDoc</span>&#125;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> Future&lt;?&gt; submit(Runnable task) &#123;</span><br><span class="line">    <span class="keyword">if</span> (task == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    RunnableFuture&lt;Void&gt; ftask = newTaskFor(task, <span class="literal">null</span>);</span><br><span class="line">    execute(ftask);</span><br><span class="line">    <span class="keyword">return</span> ftask;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中，<code>FutureTask</code>负责统一把输入的无论是<code>Runnable</code>还是<code>Callable</code>都统一转换为<code>FutureTask</code>实例，并以<code>RunnableFuture</code>接口的抽象形式返回。</p><p>随后，调用<code>execute(ftask)</code>方法来执行新提交的任务。该方法在抽象类的子类——<code>ThreadPoolExecutor</code>中具体实现。</p><h3 id="开始执行任务execute">3.2 开始执行任务——execute</h3><p>任务提交后需要执行起来，<code>submit</code>中执行的方法<code>execute(ftask)</code>在抽象类的子类——<code>ThreadPoolExecutor.execute</code>中具体实现：</p><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * Executes the given task sometime in the future.  The task</span></span><br><span class="line"><span class="comment"> * may execute in a new thread or in an existing pooled thread.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * If the task cannot be submitted for execution, either because this</span></span><br><span class="line"><span class="comment"> * executor has been shutdown or because its capacity has been reached,</span></span><br><span class="line"><span class="comment"> * the task is handled by the current &#123;<span class="doctag">@link</span> RejectedExecutionHandler&#125;.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> command the task to execute</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> RejectedExecutionException at discretion of</span></span><br><span class="line"><span class="comment"> *         &#123;<span class="doctag">@code</span> RejectedExecutionHandler&#125;, if the task</span></span><br><span class="line"><span class="comment"> *         cannot be accepted for execution</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> NullPointerException if &#123;<span class="doctag">@code</span> command&#125; is null</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable command)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (command == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Proceed in 3 steps:</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * 1. If fewer than corePoolSize threads are running, try to</span></span><br><span class="line"><span class="comment">     * start a new thread with the given command as its first</span></span><br><span class="line"><span class="comment">     * task.  The call to addWorker atomically checks runState and</span></span><br><span class="line"><span class="comment">     * workerCount, and so prevents false alarms that would add</span></span><br><span class="line"><span class="comment">     * threads when it shouldn&#x27;t, by returning false.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * 2. If a task can be successfully queued, then we still need</span></span><br><span class="line"><span class="comment">     * to double-check whether we should have added a thread</span></span><br><span class="line"><span class="comment">     * (because existing ones died since last checking) or that</span></span><br><span class="line"><span class="comment">     * the pool shut down since entry into this method. So we</span></span><br><span class="line"><span class="comment">     * recheck state and if necessary roll back the enqueuing if</span></span><br><span class="line"><span class="comment">     * stopped, or start a new thread if there are none.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * 3. If we cannot queue task, then we try to add a new</span></span><br><span class="line"><span class="comment">     * thread.  If it fails, we know we are shut down or saturated</span></span><br><span class="line"><span class="comment">     * and so reject the task.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">    <span class="keyword">if</span> (workerCountOf(c) &lt; corePoolSize) &#123;</span><br><span class="line">        <span class="keyword">if</span> (addWorker(command, <span class="literal">true</span>))</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        c = ctl.get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (isRunning(c) &amp;&amp; workQueue.offer(command)) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">recheck</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="keyword">if</span> (! isRunning(recheck) &amp;&amp; remove(command))</span><br><span class="line">            reject(command);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line">            addWorker(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="literal">false</span>))</span><br><span class="line">        reject(command);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，当工作线程较少，还不到核心线程数时，该方法会添加一个新线程，并把输入的<code>Runnable command</code>交给新线程执行。</p><p>如果已经达到核心线程数，该方法进行了一系列谨慎的检查工作，并且把输入的<code>Runnable command</code>加入到了<code>workQueue</code>中，具体地：</p><p>检查线程池控制字判断线程池是否在工作，如果是，就把任务加入工作队列<code>workQueue.offer(command)</code>：</p><ul><li>如果任务能够顺利加入工作队列（true），那么会有工作线程去处理它。典型的工作队列<code>LinkedBlockingQueue</code>就属于这一类，这种情况下，线程数量不会超过<code>corePoolSize</code>的核心线程数。<ul><li>当然，因为避免用重型锁，这里采用了CAS锁的形式，来避免加入任务后，线程池关闭、零线程的情况。<ul><li>如果第二次检查发现线程池不运行了，就移除刚刚加入的任务，并<code>reject</code>（<code>reject</code>方法会进一步调用<code>RejectedExecutionHandler</code>实例的<code>handler.rejectedExecution(command, this);</code>方法以便处理这种任务被线程池拒绝的情况）；</li><li>如果线程池还在运行，但是没有工作线程，就新建一个线程来处理工作队列中新加入的任务。</li></ul></li></ul></li><li>如果任务不能顺利加入工作队列（false），那么就需要启动新的工作线程。典型的工作队列<code>SynchronousQueue</code>就属于这一类，如果没有线程阻塞在读取上，就无法插入新的任务，即会返回false。这么一来，就会启动新的线程，毕竟有阻塞在读取上的线程，才能加入新的任务。这意味着线程数量完全有可能超过<code>corePoolSize</code>规定的核心线程数。</li></ul><h3 id="工作队列blockingqueue">3.3 工作队列——BlockingQueue</h3><p>工作队列是一个阻塞队列，用于解决生产者-消费者问题。也就是说，<code>execute</code>扮演的是一个生产者的角色，它负责把检查过的任务加入到工作队列中，供线程池中的工作线程取出并执行。</p><p>在<code>ThreadPoolExecutor</code>中，工作队列<code>workQueue</code>是一个<code>BlockingQueue&lt;Runnable&gt;</code>：</p><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * The queue used for holding tasks and handing off to worker</span></span><br><span class="line"><span class="comment"> * threads.  We do not require that workQueue.poll() returning</span></span><br><span class="line"><span class="comment"> * null necessarily means that workQueue.isEmpty(), so rely</span></span><br><span class="line"><span class="comment"> * solely on isEmpty to see if the queue is empty (which we must</span></span><br><span class="line"><span class="comment"> * do for example when deciding whether to transition from</span></span><br><span class="line"><span class="comment"> * SHUTDOWN to TIDYING).  This accommodates special-purpose</span></span><br><span class="line"><span class="comment"> * queues such as DelayQueues for which poll() is allowed to</span></span><br><span class="line"><span class="comment"> * return null even if it may later return non-null when delays</span></span><br><span class="line"><span class="comment"> * expire.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> BlockingQueue&lt;Runnable&gt; workQueue;</span><br></pre></td></tr></table></figure><h3 id="添加工作线程addworker">3.4 添加工作线程——addWorker</h3><p>在上述<code>execute</code>方法中，要执行<code>Runnable</code>任务，需要线程池中有工作线程，是通过调用<code>addWorker</code>实现的。</p><p>具体地，线程池工作线程的创建和添加操作在<code>ThreadPoolExecutor.addWorker</code>方法中具体实现：</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Checks if a new worker can be added with respect to current</span></span><br><span class="line"><span class="comment"> * pool state and the given bound (either core or maximum). If so,</span></span><br><span class="line"><span class="comment"> * the worker count is adjusted accordingly, and, if possible, a</span></span><br><span class="line"><span class="comment"> * new worker is created and started, running firstTask as its</span></span><br><span class="line"><span class="comment"> * first task. This method returns false if the pool is stopped or</span></span><br><span class="line"><span class="comment"> * eligible to shut down. It also returns false if the thread</span></span><br><span class="line"><span class="comment"> * factory fails to create a thread when asked.  If the thread</span></span><br><span class="line"><span class="comment"> * creation fails, either due to the thread factory returning</span></span><br><span class="line"><span class="comment"> * null, or due to an exception (typically OutOfMemoryError in</span></span><br><span class="line"><span class="comment"> * Thread.start()), we roll back cleanly.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> firstTask the task the new thread should run first (or</span></span><br><span class="line"><span class="comment"> * null if none). Workers are created with an initial first task</span></span><br><span class="line"><span class="comment"> * (in method execute()) to bypass queuing when there are fewer</span></span><br><span class="line"><span class="comment"> * than corePoolSize threads (in which case we always start one),</span></span><br><span class="line"><span class="comment"> * or when the queue is full (in which case we must bypass queue).</span></span><br><span class="line"><span class="comment"> * Initially idle threads are usually created via</span></span><br><span class="line"><span class="comment"> * prestartCoreThread or to replace other dying workers.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> core if true use corePoolSize as bound, else</span></span><br><span class="line"><span class="comment"> * maximumPoolSize. (A boolean indicator is used here rather than a</span></span><br><span class="line"><span class="comment"> * value to ensure reads of fresh values after checking other pool</span></span><br><span class="line"><span class="comment"> * state).</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> true if successful</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">addWorker</span><span class="params">(Runnable firstTask, <span class="type">boolean</span> core)</span> &#123;</span><br><span class="line">    retry:</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();;) &#123;</span><br><span class="line">        <span class="comment">// Check if queue empty only if necessary.</span></span><br><span class="line">        <span class="keyword">if</span> (runStateAtLeast(c, SHUTDOWN)</span><br><span class="line">            &amp;&amp; (runStateAtLeast(c, STOP)</span><br><span class="line">                || firstTask != <span class="literal">null</span></span><br><span class="line">                || workQueue.isEmpty()))</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">            <span class="keyword">if</span> (workerCountOf(c)</span><br><span class="line">                &gt;= ((core ? corePoolSize : maximumPoolSize) &amp; COUNT_MASK))</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">if</span> (compareAndIncrementWorkerCount(c))</span><br><span class="line">                <span class="keyword">break</span> retry;</span><br><span class="line">            c = ctl.get();  <span class="comment">// Re-read ctl</span></span><br><span class="line">            <span class="keyword">if</span> (runStateAtLeast(c, SHUTDOWN))</span><br><span class="line">                <span class="keyword">continue</span> retry;</span><br><span class="line">            <span class="comment">// else CAS failed due to workerCount change; retry inner loop</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">boolean</span> <span class="variable">workerStarted</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">workerAdded</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="type">Worker</span> <span class="variable">w</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        w = <span class="keyword">new</span> <span class="title class_">Worker</span>(firstTask);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> w.thread;</span><br><span class="line">        <span class="keyword">if</span> (t != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">mainLock</span> <span class="operator">=</span> <span class="built_in">this</span>.mainLock;</span><br><span class="line">            mainLock.lock();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// Recheck while holding lock.</span></span><br><span class="line">                <span class="comment">// Back out on ThreadFactory failure or if</span></span><br><span class="line">                <span class="comment">// shut down before lock acquired.</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (isRunning(c) ||</span><br><span class="line">                    (runStateLessThan(c, STOP) &amp;&amp; firstTask == <span class="literal">null</span>)) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (t.getState() != Thread.State.NEW)</span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalThreadStateException</span>();</span><br><span class="line">                    workers.add(w);</span><br><span class="line">                    workerAdded = <span class="literal">true</span>;</span><br><span class="line">                    <span class="type">int</span> <span class="variable">s</span> <span class="operator">=</span> workers.size();</span><br><span class="line">                    <span class="keyword">if</span> (s &gt; largestPoolSize)</span><br><span class="line">                        largestPoolSize = s;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                mainLock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (workerAdded) &#123;</span><br><span class="line">                t.start();</span><br><span class="line">                workerStarted = <span class="literal">true</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (! workerStarted)</span><br><span class="line">            addWorkerFailed(w);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> workerStarted;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中，会根据输入参数<code>boolean core</code>来约束工作线程数的上限。如果<code>core==true</code>，则线程数不超过<code>corePoolSize</code>；否则线程数上限为<code>maxPoolSize</code>。</p><h3 id="线程池工作线程runworker">3.5 线程池工作线程——runWorker</h3><p>线程池中保有的工作线程作为消费者的一方，要从工作队列中取出任务并执行。</p><p>具体地，线程池工作线程的主循环在<code>ThreadPoolExecutor.runWorker</code>方法中具体实现：</p><figure class="highlight java"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Main worker run loop.  Repeatedly gets tasks from queue and</span></span><br><span class="line"><span class="comment"> * executes them, while coping with a number of issues:</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 1. We may start out with an initial task, in which case we</span></span><br><span class="line"><span class="comment"> * don&#x27;t need to get the first one. Otherwise, as long as pool is</span></span><br><span class="line"><span class="comment"> * running, we get tasks from getTask. If it returns null then the</span></span><br><span class="line"><span class="comment"> * worker exits due to changed pool state or configuration</span></span><br><span class="line"><span class="comment"> * parameters.  Other exits result from exception throws in</span></span><br><span class="line"><span class="comment"> * external code, in which case completedAbruptly holds, which</span></span><br><span class="line"><span class="comment"> * usually leads processWorkerExit to replace this thread.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 2. Before running any task, the lock is acquired to prevent</span></span><br><span class="line"><span class="comment"> * other pool interrupts while the task is executing, and then we</span></span><br><span class="line"><span class="comment"> * ensure that unless pool is stopping, this thread does not have</span></span><br><span class="line"><span class="comment"> * its interrupt set.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 3. Each task run is preceded by a call to beforeExecute, which</span></span><br><span class="line"><span class="comment"> * might throw an exception, in which case we cause thread to die</span></span><br><span class="line"><span class="comment"> * (breaking loop with completedAbruptly true) without processing</span></span><br><span class="line"><span class="comment"> * the task.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 4. Assuming beforeExecute completes normally, we run the task,</span></span><br><span class="line"><span class="comment"> * gathering any of its thrown exceptions to send to afterExecute.</span></span><br><span class="line"><span class="comment"> * We separately handle RuntimeException, Error (both of which the</span></span><br><span class="line"><span class="comment"> * specs guarantee that we trap) and arbitrary Throwables.</span></span><br><span class="line"><span class="comment"> * Because we cannot rethrow Throwables within Runnable.run, we</span></span><br><span class="line"><span class="comment"> * wrap them within Errors on the way out (to the thread&#x27;s</span></span><br><span class="line"><span class="comment"> * UncaughtExceptionHandler).  Any thrown exception also</span></span><br><span class="line"><span class="comment"> * conservatively causes thread to die.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * 5. After task.run completes, we call afterExecute, which may</span></span><br><span class="line"><span class="comment"> * also throw an exception, which will also cause thread to</span></span><br><span class="line"><span class="comment"> * die. According to JLS Sec 14.20, this exception is the one that</span></span><br><span class="line"><span class="comment"> * will be in effect even if task.run throws.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * The net effect of the exception mechanics is that afterExecute</span></span><br><span class="line"><span class="comment"> * and the thread&#x27;s UncaughtExceptionHandler have as accurate</span></span><br><span class="line"><span class="comment"> * information as we can provide about any problems encountered by</span></span><br><span class="line"><span class="comment"> * user code.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> w the worker</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">runWorker</span><span class="params">(Worker w)</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">wt</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> w.firstTask;</span><br><span class="line">    w.firstTask = <span class="literal">null</span>;</span><br><span class="line">    w.unlock(); <span class="comment">// allow interrupts</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">completedAbruptly</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (task != <span class="literal">null</span> || (task = getTask()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            w.lock();</span><br><span class="line">            <span class="comment">// If pool is stopping, ensure thread is interrupted;</span></span><br><span class="line">            <span class="comment">// if not, ensure thread is not interrupted.  This</span></span><br><span class="line">            <span class="comment">// requires a recheck in second case to deal with</span></span><br><span class="line">            <span class="comment">// shutdownNow race while clearing interrupt</span></span><br><span class="line">            <span class="keyword">if</span> ((runStateAtLeast(ctl.get(), STOP) ||</span><br><span class="line">                 (Thread.interrupted() &amp;&amp;</span><br><span class="line">                  runStateAtLeast(ctl.get(), STOP))) &amp;&amp;</span><br><span class="line">                !wt.isInterrupted())</span><br><span class="line">                wt.interrupt();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                beforeExecute(wt, task);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    task.run();</span><br><span class="line">                    afterExecute(task, <span class="literal">null</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">                    afterExecute(task, ex);</span><br><span class="line">                    <span class="keyword">throw</span> ex;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                task = <span class="literal">null</span>;</span><br><span class="line">                w.completedTasks++;</span><br><span class="line">                w.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        completedAbruptly = <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        processWorkerExit(w, completedAbruptly);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>开头<code>task</code>初始值为<code>firstTask</code>，是线程池的成员变量，用于引用初始任务，通常为空。因此，线程池的主线程作为<code>workQueue</code>的消费者，通常情况下都是通过<code>getTask()</code>方法来取出任务。</p><p>取出任务后，谨慎地进行线程池的状态检查，并在运行任务的前后，分别调用<code>beforeExecute</code>和<code>afterExecute</code>方法。这两个方法在<code>ThreadPoolExecutor</code>中实现内容为空，也就是说不做任何事情。这两个方法是预留的，可以被继承实现，以增加额外的检查和功能（如：记录日志）。</p><p>运行任务显得很简单，线程池的工作线程执行<code>Runnable</code>任务实例的<code>task,run()</code>方法即可。</p><h3 id="从工作队列中取任务gettask">3.6 从工作队列中取任务——getTask</h3><p><code>ThreadPoolExecutor.getTask()</code>的实现：</p><figure class="highlight java"><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">/**</span></span><br><span class="line"><span class="comment"> * Performs blocking or timed wait for a task, depending on</span></span><br><span class="line"><span class="comment"> * current configuration settings, or returns null if this worker</span></span><br><span class="line"><span class="comment"> * must exit because of any of:</span></span><br><span class="line"><span class="comment"> * 1. There are more than maximumPoolSize workers (due to</span></span><br><span class="line"><span class="comment"> *    a call to setMaximumPoolSize).</span></span><br><span class="line"><span class="comment"> * 2. The pool is stopped.</span></span><br><span class="line"><span class="comment"> * 3. The pool is shutdown and the queue is empty.</span></span><br><span class="line"><span class="comment"> * 4. This worker timed out waiting for a task, and timed-out</span></span><br><span class="line"><span class="comment"> *    workers are subject to termination (that is,</span></span><br><span class="line"><span class="comment"> *    &#123;<span class="doctag">@code</span> allowCoreThreadTimeOut || workerCount &gt; corePoolSize&#125;)</span></span><br><span class="line"><span class="comment"> *    both before and after the timed wait, and if the queue is</span></span><br><span class="line"><span class="comment"> *    non-empty, this worker is not the last thread in the pool.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> task, or null if the worker must exit, in which case</span></span><br><span class="line"><span class="comment"> *         workerCount is decremented</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> Runnable <span class="title function_">getTask</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">timedOut</span> <span class="operator">=</span> <span class="literal">false</span>; <span class="comment">// Did the last poll() time out?</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Check if queue empty only if necessary.</span></span><br><span class="line">        <span class="keyword">if</span> (runStateAtLeast(c, SHUTDOWN)</span><br><span class="line">            &amp;&amp; (runStateAtLeast(c, STOP) || workQueue.isEmpty())) &#123;</span><br><span class="line">            decrementWorkerCount();</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">int</span> <span class="variable">wc</span> <span class="operator">=</span> workerCountOf(c);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Are workers subject to culling?</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">timed</span> <span class="operator">=</span> allowCoreThreadTimeOut || wc &gt; corePoolSize;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> ((wc &gt; maximumPoolSize || (timed &amp;&amp; timedOut))</span><br><span class="line">            &amp;&amp; (wc &gt; <span class="number">1</span> || workQueue.isEmpty())) &#123;</span><br><span class="line">            <span class="keyword">if</span> (compareAndDecrementWorkerCount(c))</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Runnable</span> <span class="variable">r</span> <span class="operator">=</span> timed ?</span><br><span class="line">                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :</span><br><span class="line">                workQueue.take();</span><br><span class="line">            <span class="keyword">if</span> (r != <span class="literal">null</span>)</span><br><span class="line">                <span class="keyword">return</span> r;</span><br><span class="line">            timedOut = <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException retry) &#123;</span><br><span class="line">            timedOut = <span class="literal">false</span>;</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>timed</code>变量，决定是限时等待型读取或是阻塞型读取。</p><ul><li>如果<code>timed==true</code>，则在主循环中限时等待提取工作队列中的任务，即<code>workQueue.poll</code>方法：<ul><li>如果工作队列中有任务，则立即返回；</li><li>如果没有，则等待指定时间，如果仍然没有，则返回<code>null</code>。</li><li>（Retrieves and removes the head of this queue, waiting up to thespecified wait time if necessary for an element to becomeavailable.）</li></ul></li><li>如果<code>timed==false</code>，则在主循环中阻塞式提取工作队列中任务，即<code>workQueue.take</code>方法：<ul><li>如果工作队列中有任务，则立即返回；</li><li>如果没有，则线程阻塞，直到生产者加入任务后，有任务实例再返回。</li></ul></li></ul><h2 id="j.u.c阻塞队列">4 J.U.C阻塞队列</h2><p>上述“3.2开始执行任务——execute”中解释了LinkedBlockingQueue与SynchronousQueue为什么会分别用于不同的ThreadPoolExecutor。尤其是同步队列，<code>CachedThreadPool</code>依靠其插入失败就可以检测到没有数量匹配的读线程，由此增加线程池的线程数。</p><h3 id="无容量的同步队列synchronousqueue">4.1无容量的同步队列——SynchronousQueue</h3><p>这个容器就比较特别了，虽然名字是队列，但实际上没有任何容量。</p><blockquote><p>A blocking queue in which each insert operation must wait for acorresponding remove operation by another thread, and vice versa. Asynchronous queue does not have any internal capacity, not even acapacity of one. You cannot peek at a synchronous queue because anelement is only present when you try to remove it; you cannot insert anelement (using any method) unless another thread is trying to remove it;you cannot iterate as there is nothing to iterate. The head of the queueis the element that the first queued inserting thread is trying to addto the queue; if there is no such queued thread then no element isavailable for removal and poll() will return null. For purposes of otherCollection methods (for example contains), a SynchronousQueue acts as anempty collection. This queue does not permit null elements.</p></blockquote><p>使用该容器时，必须先取再插。也就是说，对于一个同步队列，如果没有任何线程在读取它，别的线程就无法对其插入新数据。通常，需要先启动一个线程读取同步队列，此时同步队列尚无数据，则该读线程会处于阻塞等待地状态。随后，启动一个线程向同步队列中插入数据，此时，阻塞等待数据的读线程会唤醒并读取插入数据。</p><p>因为其插入时必须要有读线程的特性，该容器被应用于检测读线程少于插入任务数量的情况，引导线程池增加新线程。</p><h3 id="链表阻塞队列linkedblockingqueue">4.2链表阻塞队列——LinkedBlockingQueue</h3><p>不难理解，这是一个基于链表实现的阻塞队列。</p><blockquote><p>An optionally-bounded blocking queue based on linked nodes. Thisqueue orders elements FIFO (first-in-first-out). The head of the queueis that element that has been on the queue the longest time. The tail ofthe queue is that element that has been on the queue the shortest time.New elements are inserted at the tail of the queue, and the queueretrieval operations obtain elements at the head of the queue. Linkedqueues typically have higher throughput than array-based queues but lesspredictable performance in most concurrent applications.</p></blockquote><p>既然是链表实现，那一般理解是可以无界的，当然也可以指定大小限定为有界。</p><p>链表阻塞队列采用FIFO模式，队列头是最早插入的，队列尾是最新插入的，取出时依据FIFO顺序。</p><p>基于链表阻塞队列在并发应用中吞吐量通常比基于数组的阻塞队列更大，因为基于链表的阻塞队列不至于同步锁定整个数组容器。基于链表的阻塞队列实际上在读写时，锁定入队和出队的位置就可以了。</p><h3 id="数组阻塞队列arrayblockingqueue">4.3数组阻塞队列——ArrayBlockingQueue</h3><p>不难理解，这是一个基于数组实现的阻塞队列。</p><blockquote><p>A bounded blocking queue backed by an array. This queue orderselements FIFO (first-in-first-out). The head of the queue is thatelement that has been on the queue the longest time. The tail of thequeue is that element that has been on the queue the shortest time. Newelements are inserted at the tail of the queue, and the queue retrievaloperations obtain elements at the head of the queue. This is a classic"bounded buffer", in which a fixed-sized array holds elements insertedby producers and extracted by consumers. Once created, the capacitycannot be changed. Attempts to put an element into a full queue willresult in the operation blocking; attempts to take an element from anempty queue will similarly block.</p><p>This class supports an optional fairness policy for ordering waitingproducer and consumer threads. By default, this ordering is notguaranteed. However, a queue constructed with fairness set to truegrants threads access in FIFO order. Fairness generally decreasesthroughput but reduces variability and avoids starvation.</p></blockquote><p>既然是基于数组实现的，那容器肯定是有界的，创建时就确定的，无法动态变化。这样一来，如果数组中存满了，再插入新的数据就需要阻塞至有元素被取走，同样地，如果数组中没有元素，读取操作需要阻塞至有元素被插入。</p><p>另外，数组阻塞队列还存在一个公平策略，如果严格要求保障FIFO的出入队列顺序，需要启用公平策略，这样可以避免饥饿问题（因为否则的话，可能有些元素长时间都不会被轮到取出来），但是也会减少吞吐量。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在开发&lt;a
href=&quot;https://github.com/HearyShen/HearyHTTPd&quot;&gt;HearyHTTPd&lt;/a&gt;的过程中，为了有效利用多线程处理并发请求，我使用了Java的线程池机制。我查阅了JDK11中的线程池实现源码，本文对其原理进行进一步的梳理。&lt;/p&gt;</summary>
    
    
    
    
    <category term="Java" scheme="https://heary.cn/tags/Java/"/>
    
    <category term="JDK" scheme="https://heary.cn/tags/JDK/"/>
    
    <category term="ThreadPool" scheme="https://heary.cn/tags/ThreadPool/"/>
    
  </entry>
  
</feed>
