<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>旅行手记</title>
  
  <subtitle>逐风之旅</subtitle>
  <link href="https://victorwong171.github.io/atom.xml" rel="self"/>
  
  <link href="https://victorwong171.github.io/"/>
  <updated>2025-10-09T10:20:14.866Z</updated>
  <id>https://victorwong171.github.io/</id>
  
  <author>
    <name>衣云乘风</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>标准库context:协作式goroutine管理工具</title>
    <link href="https://victorwong171.github.io/2025/09/21/%E6%A0%87%E5%87%86%E5%BA%93context%E5%8D%8F%E4%BD%9C%E5%BC%8Fgoroutine%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7/"/>
    <id>https://victorwong171.github.io/2025/09/21/%E6%A0%87%E5%87%86%E5%BA%93context%E5%8D%8F%E4%BD%9C%E5%BC%8Fgoroutine%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7/</id>
    <published>2025-09-20T20:48:23.000Z</published>
    <updated>2025-10-09T10:20:14.866Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2></li></ul><p>在goroutine的应用中，有些经典场景如：<strong>超时控制、派生goroutine管理、通用上下文传递</strong>等，因此官方在1.7版本引入了context库来提供便捷的解决方案。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在goroutine的应用中，有些经典场景如：&lt;strong&gt;超时控制、派生goroutine管理</summary>
      
    
    
    
    <category term="标准库" scheme="https://victorwong171.github.io/categories/%E6%A0%87%E5%87%86%E5%BA%93/"/>
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E6%A0%87%E5%87%86%E5%BA%93/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="go" scheme="https://victorwong171.github.io/tags/go/"/>
    
    <category term="context" scheme="https://victorwong171.github.io/tags/context/"/>
    
    <category term="goroutine" scheme="https://victorwong171.github.io/tags/goroutine/"/>
    
  </entry>
  
  <entry>
    <title>defer的执行逻辑</title>
    <link href="https://victorwong171.github.io/2025/09/21/defer%E7%9A%84%E6%89%A7%E8%A1%8C%E9%80%BB%E8%BE%91/"/>
    <id>https://victorwong171.github.io/2025/09/21/defer%E7%9A%84%E6%89%A7%E8%A1%8C%E9%80%BB%E8%BE%91/</id>
    <published>2025-09-20T19:47:05.000Z</published>
    <updated>2025-09-20T12:31:44.452Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2></li></ul><p>根据官网对<a href="https://go.dev/ref/spec#Defer_statements">defer</a>的描述，</p><blockquote><p>defer func()的存储结构是栈，即LIFO，有多次defer调用时遵循<strong>后进先出</strong>的逻辑进行执行</p></blockquote><p>而go中return的逻辑如下</p><blockquote><ol><li>先将返回值赋值</li><li>执行 defer 栈</li><li>返回调用函数</li></ol></blockquote><ul><li><h2 id="举个栗子"><a href="#举个栗子" class="headerlink" title="举个栗子"></a>举个栗子</h2></li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">demo</span><span class="hljs-params">()</span></span> (res <span class="hljs-type">int</span>) &#123;<br><span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">4</span>; i++ &#123;<br><span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br>res = i * i<br>fmt.Printf(<span class="hljs-string">&quot;res:\t%d\n&quot;</span>, res)<br>&#125;()<br>&#125;<br><span class="hljs-keyword">return</span><br>&#125;<br></code></pre></td></tr></table></figure><p>函数返回值为 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></pre></td><td class="code"><pre><code class="hljs bash">res:9<br>res:4<br>res:1<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据官网对&lt;a href=&quot;https://go.dev/ref/spec#Defer_state</summary>
      
    
    
    
    <category term="基础知识" scheme="https://victorwong171.github.io/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>viper包加载蛇形字段配置失败</title>
    <link href="https://victorwong171.github.io/2025/09/07/viper%E5%8C%85%E5%8A%A0%E8%BD%BD%E8%9B%87%E5%BD%A2%E5%AD%97%E6%AE%B5%E9%85%8D%E7%BD%AE%E5%A4%B1%E8%B4%A5/"/>
    <id>https://victorwong171.github.io/2025/09/07/viper%E5%8C%85%E5%8A%A0%E8%BD%BD%E8%9B%87%E5%BD%A2%E5%AD%97%E6%AE%B5%E9%85%8D%E7%BD%AE%E5%A4%B1%E8%B4%A5/</id>
    <published>2025-09-07T06:08:17.000Z</published>
    <updated>2025-09-06T22:50:48.506Z</updated>
    
    <content type="html"><![CDATA[<h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>今天在使用 <code>github.com/spf13/viper</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></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Config <span class="hljs-keyword">struct</span> &#123;<br>Name   <span class="hljs-type">string</span> <span class="hljs-string">`yaml:&quot;name&quot;`</span><br>UserId <span class="hljs-type">int</span>    <span class="hljs-string">`yaml:&quot;user_id&quot;`</span><br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">GetSnakeValue</span><span class="hljs-params">()</span></span> Config &#123;<br>v := viper.New()<br>v.Set(<span class="hljs-string">&quot;user_id&quot;</span>, <span class="hljs-number">1</span>)<br>v.Set(<span class="hljs-string">&quot;name&quot;</span>, <span class="hljs-string">&quot;victor&quot;</span>)<br><span class="hljs-keyword">var</span> cfg Config<br>err := v.Unmarshal(&amp;cfg)<br><span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> &#123;<br><span class="hljs-built_in">panic</span>(err)<br>&#125;<br><span class="hljs-keyword">return</span> cfg<br>&#125;<br></code></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></pre></td><td class="code"><pre><code class="hljs go">Config&#123;<br>Name:   <span class="hljs-string">&quot;victor&quot;</span>,<br>UserId: <span class="hljs-number">0</span><br>&#125;<br></code></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></pre></td><td class="code"><pre><code class="hljs go">Config&#123;<br>Name:   <span class="hljs-string">&quot;victor&quot;</span>,<br>UserId: <span class="hljs-number">1</span><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="现象分析"><a href="#现象分析" class="headerlink" title="现象分析"></a>现象分析</h2><ol><li><code>Config.Name</code> 可以正常加载，那说明 <code>viper</code> 正常工作了</li><li>在断点中可以看到 <code>user_id</code> 正常加载，继续断点下去是一些反射代码，没法直观分析<img src="/img/2025090700-01.png"></li><li><code>Config.UserId</code> 无法正常加载，那么说明在decode时失败</li></ol><p>根据网上资料得知，<code>viper.Unmarshal</code> 底层其实是用 <code>mapstructure.Decode</code>，但它对 tag、key 的处理有自己的默认行为：</p><ul><li>会把所有 key 转换成 <strong>小写</strong>。</li><li>默认不会启用 <code>WeaklyTypedInput</code>、<code>Squash</code> 等高级选项。</li><li>不会自动启用驼峰 → 下划线的映射。</li></ul><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>知道了原因，那么就可以按图索骥。</p><ol><li><p>将tag的value改为驼峰写法，<code>userId</code>这样就可以正常解析</p></li><li><p>添加<strong>mapstructure</strong>  tag 让<code>mapstructure.Decode</code>知道 <code>user_id</code>对应的 struct field，如：</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></pre></td><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Config <span class="hljs-keyword">struct</span> &#123;<br>Name   <span class="hljs-type">string</span> <span class="hljs-string">`yaml:&quot;name&quot;`</span><br>UserId <span class="hljs-type">int</span>    <span class="hljs-string">`yaml:&quot;user_id&quot; mapstructure:&quot;user_id&quot;`</span><br>&#125;<br></code></pre></td></tr></table></figure></li><li><p>在调用<code>func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error</code>时，设置<code>DecoderConfigOption</code></p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h2&gt;&lt;p&gt;今天在使用 &lt;code&gt;github.com/spf13/viper&lt;/code&gt; 加载配置文件时突然发现有几个字段的值始终无法正常加载，大</summary>
      
    
    
    
    <category term="异常" scheme="https://victorwong171.github.io/categories/%E5%BC%82%E5%B8%B8/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>对象构造方法</title>
    <link href="https://victorwong171.github.io/2024/12/27/%E5%AF%B9%E8%B1%A1%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95/"/>
    <id>https://victorwong171.github.io/2024/12/27/%E5%AF%B9%E8%B1%A1%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95/</id>
    <published>2024-12-27T05:49:31.000Z</published>
    <updated>2024-12-26T22:14:22.386Z</updated>
    
    <content type="html"><![CDATA[<p>有以下名为 Server 的类型。</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs golang"><span class="hljs-keyword">type</span> Server <span class="hljs-keyword">struct</span> &#123;<br>Port     <span class="hljs-type">int</span><br>Protocol <span class="hljs-type">string</span><br>&#125;<br></code></pre></td></tr></table></figure><ul><li><h2 id="构造器"><a href="#构造器" class="headerlink" title="构造器"></a>构造器</h2></li></ul><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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><code class="hljs golang"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewServer</span><span class="hljs-params">(port <span class="hljs-type">int</span>, protocol <span class="hljs-type">string</span>)</span></span> *Server&#123;<br><span class="hljs-keyword">return</span> &amp;Server&#123;<br>Port: port,<br>Protocol: protocol,<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>简单直接，参数少的时候很常用。</p><ul><li><h2 id="functional-option"><a href="#functional-option" class="headerlink" title="functional-option"></a>functional-option</h2></li></ul><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-keyword">type</span> Option <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(server *Server)</span></span><br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">WithPort</span><span class="hljs-params">(port <span class="hljs-type">int</span>)</span></span> Option &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(server *Server)</span></span> &#123;<br>server.Port = port<br>&#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">WithProtocol</span><span class="hljs-params">(protocol <span class="hljs-type">string</span>)</span></span> Option &#123;<br><span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(server *Server)</span></span> &#123;<br>server.Protocol = protocol<br>&#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewServerWithOption</span><span class="hljs-params">(options ...Option)</span></span> *Server &#123;<br>server := &amp;Server&#123;&#125;<br><span class="hljs-keyword">for</span> _, option := <span class="hljs-keyword">range</span> options &#123;<br>option(server)<br>&#125;<br><span class="hljs-keyword">return</span> server<br>&#125;<br></code></pre></td></tr></table></figure><p>直观，拓展性好，新增属性时只需要添加代码即可，不需要修改原有构造对象的代码。</p><ul><li><h2 id="builder"><a href="#builder" class="headerlink" title="builder"></a>builder</h2></li></ul><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-keyword">type</span> ServerBuilder <span class="hljs-keyword">struct</span> &#123;<br>option Server<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewServerBuilder</span><span class="hljs-params">()</span></span> *ServerBuilder &#123;<br><span class="hljs-keyword">return</span> &amp;ServerBuilder&#123;&#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(b *ServerBuilder)</span></span> WithPort(port <span class="hljs-type">int</span>) *ServerBuilder &#123;<br>b.option.Port = port<br><span class="hljs-keyword">return</span> b<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(b *ServerBuilder)</span></span> WithProtocol(protocol <span class="hljs-type">string</span>) *ServerBuilder &#123;<br>b.option.Protocol = protocol<br><span class="hljs-keyword">return</span> b<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(b *ServerBuilder)</span></span> Build() *Server &#123;<br><span class="hljs-keyword">return</span> &amp;b.option<br>&#125;<br></code></pre></td></tr></table></figure><p>适合复杂对象的构造，因为支持链式调用，所以需要分布进行构造对象时为首选。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;有以下名为 Server 的类型。&lt;/p&gt;
&lt;figure class=&quot;highlight golang&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;lin</summary>
      
    
    
    
    <category term="基础知识" scheme="https://victorwong171.github.io/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="设计模式" scheme="https://victorwong171.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>使用私有空变量作为全局唯一key</title>
    <link href="https://victorwong171.github.io/2024/12/26/%E4%BD%BF%E7%94%A8%E7%A7%81%E6%9C%89%E7%A9%BA%E5%8F%98%E9%87%8F%E4%BD%9C%E4%B8%BA%E5%85%A8%E5%B1%80%E5%94%AF%E4%B8%80key/"/>
    <id>https://victorwong171.github.io/2024/12/26/%E4%BD%BF%E7%94%A8%E7%A7%81%E6%9C%89%E7%A9%BA%E5%8F%98%E9%87%8F%E4%BD%9C%E4%B8%BA%E5%85%A8%E5%B1%80%E5%94%AF%E4%B8%80key/</id>
    <published>2024-12-26T05:37:54.000Z</published>
    <updated>2024-12-25T21:48:32.560Z</updated>
    
    <content type="html"><![CDATA[<p>今天看代码的时候，遇到一段：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-keyword">type</span> keyType <span class="hljs-keyword">struct</span>&#123;&#125;<br><br><span class="hljs-keyword">var</span> ctxKey = keyType&#123;&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">FromContext</span><span class="hljs-params">(ctx context.Context)</span></span> (OrgUser, <span class="hljs-type">error</span>) &#123;<br><span class="hljs-keyword">if</span> orgUser, ok := ctx.Value(ctxKey).(OrgUser); !ok &#123;<br><span class="hljs-keyword">return</span> orgUser, errors.New(<span class="hljs-string">&quot;OrgUser not found&quot;</span>)<br>&#125; <span class="hljs-keyword">else</span> &#123;<br><span class="hljs-keyword">return</span> orgUser, <span class="hljs-literal">nil</span><br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>细一思索发现这种实现很巧妙，虽然 ctxKey 实际上只是一个空 struct ，但是 golang 中对于值的比对分为两部分，一部分是 type ，一部分是 value ，两者完全相同，才会视为相等。<br>上述代码中，keyType 为私有类型，外部正常途径无法获得这种类型，也就避免了会有 ctx 的 key 与 ctxKey 相同，此外空 struct 也不占用额外内存。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今天看代码的时候，遇到一段：&lt;/p&gt;
&lt;figure class=&quot;highlight golang&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;</summary>
      
    
    
    
    <category term="基础知识" scheme="https://victorwong171.github.io/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>struct 内嵌 interface</title>
    <link href="https://victorwong171.github.io/2024/12/26/struct%E5%86%85%E5%B5%8Cinterface/"/>
    <id>https://victorwong171.github.io/2024/12/26/struct%E5%86%85%E5%B5%8Cinterface/</id>
    <published>2024-12-26T05:23:13.000Z</published>
    <updated>2024-12-25T21:35:46.827Z</updated>
    
    <content type="html"><![CDATA[<p>今天看context的代码时，发现了一个这样的写法</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs golang"><span class="hljs-keyword">type</span> Context <span class="hljs-keyword">interface</span> &#123;<br><br>Deadline() (deadline time.Time, ok <span class="hljs-type">bool</span>)<br><br>Done() &lt;-<span class="hljs-keyword">chan</span> <span class="hljs-keyword">struct</span>&#123;&#125;<br><br>Err() <span class="hljs-type">error</span><br><br>Value(key any) any<br>&#125;<br><br><span class="hljs-keyword">type</span> valueCtx <span class="hljs-keyword">struct</span> &#123;<br>Context<br>key, val any<br>&#125;<br></code></pre></td></tr></table></figure><p>仔细看才发现可以通过把 interface 内嵌到 struct 中来达到不实现 interface 的全部 method 就能使用实例指代 interface。<br>示例如下:</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-keyword">type</span> Info <span class="hljs-keyword">interface</span> &#123;<br>Name() <span class="hljs-type">string</span><br>Age() <span class="hljs-type">int</span><br>&#125;<br><br><span class="hljs-keyword">type</span> Cow <span class="hljs-keyword">struct</span> &#123;<br>name <span class="hljs-type">string</span><br>Info<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(c *Cow)</span></span> Name() <span class="hljs-type">string</span> &#123;<br><span class="hljs-keyword">return</span> c.name<br>&#125;<br></code></pre></td></tr></table></figure><p>测试代码如下：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">TestName</span><span class="hljs-params">(t *testing.T)</span></span> &#123;<br>cow := &amp;Cow&#123;<br>name: <span class="hljs-string">&quot;cc&quot;</span>,<br>&#125;<br>cl := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(i Info)</span></span> <span class="hljs-type">string</span> &#123;<br><span class="hljs-keyword">return</span> i.Name()<br>&#125;<br><br>t.Log(cl(cow))<br>&#125;<br></code></pre></td></tr></table></figure><p>输出：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">=== RUN   TestName<br>    interface_in_struct_test.go:15: cc<br>--- PASS: TestName (0.00s)<br>PASS<br></code></pre></td></tr></table></figure><p>可以清楚看到，Cow 对象没有实现 Info.Age 但是依旧可以使用实例 cow 作为入参，并能正常输出。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;今天看context的代码时，发现了一个这样的写法&lt;/p&gt;
&lt;figure class=&quot;highlight golang&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span cl</summary>
      
    
    
    
    <category term="基础知识" scheme="https://victorwong171.github.io/categories/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>网易云音乐私有文件调整</title>
    <link href="https://victorwong171.github.io/2024/12/25/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E7%A7%81%E6%9C%89%E6%96%87%E4%BB%B6%E8%B0%83%E6%95%B4/"/>
    <id>https://victorwong171.github.io/2024/12/25/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%E7%A7%81%E6%9C%89%E6%96%87%E4%BB%B6%E8%B0%83%E6%95%B4/</id>
    <published>2024-12-25T09:00:00.000Z</published>
    <updated>2024-12-25T21:24:19.878Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2></li></ul><p>最近发现自动化任务里的网易云音乐私有文件(ncm)不能直接备份为公共文件格式(flac、aac、mp3等)。</p><ul><li><h2 id="现象分析"><a href="#现象分析" class="headerlink" title="现象分析"></a>现象分析</h2></li></ul><p>把之前<a href="https://github.com/yoki123">yoki123</a>的ncmdump项目打开debug发现<br><img src="/img/WechatIMG303.jpg"><br>应该是做convention时出现了字段不匹配的问题。</p><p>解析ncm的meta信息可得</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;musicId&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2308549&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;musicName&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;My Heart Will Go On&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;artist&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span> <span class="hljs-punctuation">[</span> <span class="hljs-string">&quot;Céline Dion&quot;</span><span class="hljs-punctuation">,</span> <span class="hljs-string">&quot;50893&quot;</span> <span class="hljs-punctuation">]</span> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;albumId&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;232952&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;album&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Complete Best&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;albumPicDocId&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;941181953423930&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;albumPic&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;http://p4.music.126.net/fiNTK9Jvhg0V8QBJx0cXPg==/941181953423930.jpg&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;bitrate&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-number">999000</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;mp3DocId&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;0524b583bd4091bdf34be9406fb0b102&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;duration&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-number">280333</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;mvId&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;22617298&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;alias&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;transNames&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span> <span class="hljs-string">&quot;我心永恒&quot;</span> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;format&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;flac&quot;</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;fee&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-number">8</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;volumeDelta&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-number">-4.4074</span><span class="hljs-punctuation">,</span><br>  <span class="hljs-attr">&quot;privilege&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;flag&quot;</span> <span class="hljs-punctuation">:</span> <span class="hljs-number">2064644</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>而meta结构定义为</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-keyword">type</span> Album <span class="hljs-keyword">struct</span> &#123;<br>Id       <span class="hljs-type">float64</span> <span class="hljs-string">`json:&quot;albumId&quot;`</span><br>Name     <span class="hljs-type">string</span>  <span class="hljs-string">`json:&quot;album&quot;`</span><br>CoverUrl <span class="hljs-type">string</span>  <span class="hljs-string">`json:&quot;albumPic&quot;`</span><br>&#125;<br><br><span class="hljs-keyword">type</span> Artist <span class="hljs-keyword">struct</span> &#123;<br>Name <span class="hljs-type">string</span><br>Id   <span class="hljs-type">float64</span><br>&#125;<br><br><span class="hljs-comment">// @ref https://music.163.com/#/song?id=&#123;id&#125;</span><br><span class="hljs-keyword">type</span> Meta <span class="hljs-keyword">struct</span> &#123;<br>Id       <span class="hljs-type">float64</span> <span class="hljs-string">`json:&quot;musicId&quot;`</span><br>Name     <span class="hljs-type">string</span>  <span class="hljs-string">`json:&quot;musicName&quot;`</span><br>*Album   <span class="hljs-string">`json:&quot;,inline&quot;`</span><br>Artists  []Artist <span class="hljs-string">`json:&quot;artist&quot;`</span><br>BitRate  <span class="hljs-type">float64</span>  <span class="hljs-string">`json:&quot;bitrate&quot;`</span><br>Duration <span class="hljs-type">float64</span>  <span class="hljs-string">`json:&quot;duration&quot;`</span><br>Format   <span class="hljs-type">string</span>   <span class="hljs-string">`json:&quot;format&quot;`</span><br>Comment  <span class="hljs-type">string</span>   <span class="hljs-string">`json:&quot;-&quot;`</span><br>&#125;<br><br></code></pre></td></tr></table></figure><p>发现 <code>musicId</code>、<code>albumId</code>和<code>artistId</code>从float类型调整为了string。</p><ul><li><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2></li></ul><p>将对应字段调整类型，对解析方法稍作调整，即可正常运行。<br><img src="/img/1735089687145.jpg"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;问题&quot;&gt;&lt;a href=&quot;#问题&quot; class=&quot;headerlink&quot; title=&quot;问题&quot;&gt;&lt;/a&gt;问题&lt;/h2&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最近发现自动化任务里的网易云音乐私有文件(ncm)不能直接备份为公共文件格式(flac、aac、m</summary>
      
    
    
    
    <category term="异常" scheme="https://victorwong171.github.io/categories/%E5%BC%82%E5%B8%B8/"/>
    
    
    <category term="ncm" scheme="https://victorwong171.github.io/tags/ncm/"/>
    
  </entry>
  
  <entry>
    <title>channel</title>
    <link href="https://victorwong171.github.io/2024/12/20/channel/"/>
    <id>https://victorwong171.github.io/2024/12/20/channel/</id>
    <published>2024-12-19T20:22:48.000Z</published>
    <updated>2025-08-16T12:07:50.956Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2></li></ul><p>在源码中<code>channel</code>的数据结构大体如下：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs golang"><span class="hljs-keyword">type</span> hchan <span class="hljs-keyword">struct</span> &#123;<br><span class="hljs-comment">// chan 里元素数量</span><br>qcount   <span class="hljs-type">uint</span><br><span class="hljs-comment">// chan 底层循环数组的长度</span><br>dataqsiz <span class="hljs-type">uint</span><br><span class="hljs-comment">// 指向底层循环数组的指针</span><br><span class="hljs-comment">// 只针对有缓冲的 channel</span><br>buf      unsafe.Pointer<br><span class="hljs-comment">// chan 中元素大小</span><br>elemsize <span class="hljs-type">uint16</span><br><span class="hljs-comment">// chan 是否被关闭的标志</span><br>closed   <span class="hljs-type">uint32</span><br><span class="hljs-comment">// chan 中元素类型</span><br>elemtype *_type <span class="hljs-comment">// element type</span><br><span class="hljs-comment">// 已发送元素在循环数组中的索引</span><br>sendx    <span class="hljs-type">uint</span>   <span class="hljs-comment">// send index</span><br><span class="hljs-comment">// 已接收元素在循环数组中的索引</span><br>recvx    <span class="hljs-type">uint</span>   <span class="hljs-comment">// receive index</span><br><span class="hljs-comment">// 等待接收的 goroutine 队列</span><br>recvq    waitq  <span class="hljs-comment">// list of recv waiters</span><br><span class="hljs-comment">// 等待发送的 goroutine 队列</span><br>sendq    waitq  <span class="hljs-comment">// list of send waiters</span><br><br><span class="hljs-comment">// 保护 hchan 中所有字段 保证原子性</span><br>lock mutex<br>&#125;<br></code></pre></td></tr></table></figure><p>创建一个容量为 6 的，元素为 int 型的 channel 数据结构如下 ：<br><img src="/img/channel_data_structure.png"></p><ul><li><h2 id="写入数据"><a href="#写入数据" class="headerlink" title="写入数据"></a>写入数据</h2></li><li><h2 id="接收数据"><a href="#接收数据" class="headerlink" title="接收数据"></a>接收数据</h2></li><li><h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2></li></ul><p>必须值得注意的是：</p><h4 id="关闭-channel-后，对于等待接收者而言，会收到一个相应类型的零值"><a href="#关闭-channel-后，对于等待接收者而言，会收到一个相应类型的零值" class="headerlink" title="关闭 channel 后，对于等待接收者而言，会收到一个相应类型的零值"></a>关闭 channel 后，对于等待接收者而言，会收到一个相应类型的零值</h4><p>优雅关闭<code>channel</code>, 根据 sender 和 receiver 的个数，分下面几种情况：</p><ol><li>一个 sender， M 个 receiver， 直接在sender端关闭，用双值语句检查是否关闭</li><li>N 个 sender，一个 reciver，提供一个关闭信号channel，reciver端发送信号(关闭信号channel)</li><li>N 个 sender， M 个 receiver，协调好全体发送者，只关闭一次即可。</li></ol><table><thead><tr><th>操作</th><th>nil channel</th><th>closed channel</th><th>not nil, not closed channel</th></tr></thead><tbody><tr><td>close</td><td>panic</td><td>panic</td><td>正常关闭</td></tr><tr><td>读 &lt;- ch</td><td>阻塞</td><td>读到对应类型的零值</td><td>阻塞或正常读取数据。缓冲型 channel 为空或非缓冲型 channel 没有等待发送者时会阻塞</td></tr><tr><td>写 ch &lt;-</td><td>阻塞</td><td>panic</td><td>阻塞或正常写入数据。非缓冲型 channel 没有等待接收者或缓冲型 channel buf 满时会被阻塞</td></tr></tbody></table><ul><li><h2 id="常见应用"><a href="#常见应用" class="headerlink" title="常见应用"></a>常见应用</h2></li><li><h3 id="协程池"><a href="#协程池" class="headerlink" title="协程池"></a>协程池</h3></li></ul><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br>taskCh := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-type">int</span>, <span class="hljs-number">100</span>)<br><span class="hljs-keyword">go</span> worker(taskCh)<br><br>    <span class="hljs-comment">// 塞任务</span><br><span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++ &#123;<br>taskCh &lt;- i<br>&#125;<br><br>    <span class="hljs-comment">// 等待 1 小时 </span><br><span class="hljs-keyword">select</span> &#123;<br><span class="hljs-keyword">case</span> &lt;-time.After(time.Hour):<br>&#125;<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">worker</span><span class="hljs-params">(taskCh &lt;-<span class="hljs-keyword">chan</span> <span class="hljs-type">int</span>)</span></span> &#123;<br><span class="hljs-keyword">const</span> N = <span class="hljs-number">5</span><br><span class="hljs-comment">// 启动 5 个工作协程</span><br><span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; N; i++ &#123;<br><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(id <span class="hljs-type">int</span>)</span></span> &#123;<br><span class="hljs-keyword">for</span> &#123;<br>task := &lt;- taskCh<br>fmt.Printf(<span class="hljs-string">&quot;finish task: %d by worker %d\n&quot;</span>, task, id)<br>time.Sleep(time.Second)<br>&#125;<br>&#125;(i)<br>&#125;<br>&#125;<br></code></pre></td></tr></table></figure><ul><li><h3 id="限流"><a href="#限流" class="headerlink" title="限流"></a>限流</h3></li></ul><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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><code class="hljs golang"><span class="hljs-keyword">var</span> limit = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-type">int</span>, <span class="hljs-number">3</span>)<br><br><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> &#123;<br>    <span class="hljs-comment">// …………</span><br>    <span class="hljs-keyword">for</span> _, w := <span class="hljs-keyword">range</span> work &#123;<br>        <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> &#123;<br>            limit &lt;- <span class="hljs-number">1</span><br>            w()<br>            &lt;-limit<br>        &#125;()<br>    &#125;<br>    <span class="hljs-comment">// …………</span><br>&#125;<br></code></pre></td></tr></table></figure><ul><li><h2 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h2></li></ul><p><a href="https://github.com/golang-design/go-questions/tree/main/content/channel">go-questions: channel</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在源码中&lt;code&gt;channel&lt;/code&gt;的数据结构大体如下：&lt;/p&gt;
&lt;figure cl</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="channel" scheme="https://victorwong171.github.io/tags/channel/"/>
    
  </entry>
  
  <entry>
    <title>golang GMP模型</title>
    <link href="https://victorwong171.github.io/2024/12/20/golang-GMP%E6%A8%A1%E5%9E%8B/"/>
    <id>https://victorwong171.github.io/2024/12/20/golang-GMP%E6%A8%A1%E5%9E%8B/</id>
    <published>2024-12-19T18:53:58.000Z</published>
    <updated>2025-08-04T18:26:14.848Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2></li><li><p>G — 表示 Goroutine，它是一个待执行的任务；</p></li><li><p>M — 表示操作系统线程，它由操作系统的调度器调度和管理，实际承载goroutine的运行；</p></li><li><p>P — 表示逻辑处理器，它可以被看做运行在线程上的本地调度器。</p></li><li><h2 id="M-N模型"><a href="#M-N模型" class="headerlink" title="M:N模型"></a>M:N模型</h2></li></ul><p>Runtime 会在程序启动的时候，创建 M 个线程（CPU 执行调度的单位），之后创建的 N 个 goroutine 都会依附在这 M 个线程上执行。</p><ul><li><h2 id="scheduler调度"><a href="#scheduler调度" class="headerlink" title="scheduler调度"></a>scheduler调度</h2></li></ul><ol><li>P先从自己的本地runnable queue找一个G</li><li>要是没找到，就去别的P上找</li><li>没找到就去Global runnable queue找</li><li>还没找到就要去network poller中找阻塞在网络请求中的G<br>此外， 每61次会强行去 Global runnable queue 找 G</li></ol><ul><li><h2 id="GMP运行的简单流程"><a href="#GMP运行的简单流程" class="headerlink" title="GMP运行的简单流程"></a>GMP运行的简单流程</h2></li></ul><ol><li>Goroutine 创建：当使用 go 关键字创建一个新的 Goroutine 时，它会被放入 P 的本地队列若本地队列已满会放到调度器的全局队列中。</li><li>调度器调度：Go 的调度器会将 Goroutine 分配给一个 P，P 会尝试在其本地队列中执行 Goroutine。如果本地队列已满或长时间未执行，Goroutine 会被放入全局队列。</li><li>M 的执行：M 会从 P 的本地队列或全局队列中取出 Goroutine 并执行。当 M 空闲时，它会尝试从全局队列或其他 P 的本地队列中 “窃取” Goroutine 来执行，这种机制称为 “work stealing”。</li></ol><ul><li><h3 id="如何保证执行死循环的-Goroutine-不会一直抢占M"><a href="#如何保证执行死循环的-Goroutine-不会一直抢占M" class="headerlink" title="如何保证执行死循环的 Goroutine 不会一直抢占M"></a>如何保证执行死循环的 Goroutine 不会一直抢占M</h3></li></ul><ol><li><p><strong>系统信号</strong>: 调度器会周期性地向运行 Goroutine 的 M 线程发送一个<strong>系统信号（<code>SIGURG</code>）</strong>。</p><p><strong>栈扫描</strong>: 当 M 线程接收到这个信号后，会<strong>异步地</strong>暂停正在执行的 Goroutine。</p><p><strong>栈检查</strong>: 调度器会检查这个 Goroutine 的栈，寻找一个<strong>“安全点”</strong>。所谓安全点，就是指 Goroutine 的执行状态是可中断的。</p><p><strong>抢占</strong>: 如果找到了安全点，调度器会修改 Goroutine 的栈，将其标记为可抢占，并将其放入全局队列或本地队列。</p><p><strong>切换</strong>: M 线程随后就会去执行其他 Goroutine。</p></li><li><p>在发生系统调用、阻塞操作时，goroutine会被挂起，切换到其他goroutine</p></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;G — 表示 Goroutine，它是一个待执行的任务；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;M — 表</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="GMP模型" scheme="https://victorwong171.github.io/tags/GMP%E6%A8%A1%E5%9E%8B/"/>
    
  </entry>
  
  <entry>
    <title>红黑树</title>
    <link href="https://victorwong171.github.io/2024/12/17/%E7%BA%A2%E9%BB%91%E6%A0%91/"/>
    <id>https://victorwong171.github.io/2024/12/17/%E7%BA%A2%E9%BB%91%E6%A0%91/</id>
    <published>2024-12-17T10:33:19.000Z</published>
    <updated>2024-12-25T01:22:25.525Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2></li></ul><p>红黑树是一种自平衡二叉搜索树 <em>(任意节点的左子树的值均小于该节点，任意节点右子树的值均大于该节点，在插入删除节点后自动调整树的结构保证树的高度平衡，即任意节点的左右子树高度差不大于1)</em> , 红黑树的节点会有颜色区分，不是红色就是黑色。</p><ol><li>叶子结点*(没有子结点的节点)*、根结点、空节点为黑色</li><li>红色结点的子结点为黑色</li><li>任意节点到叶子结点所有路径上的黑色结点数目一致</li></ol><ul><li><h2 id="操作"><a href="#操作" class="headerlink" title="操作"></a>操作</h2></li><li><h3 id="旋转"><a href="#旋转" class="headerlink" title="旋转"></a>旋转</h3></li></ul><p>为保证二叉树平衡，在插入删除节点后可能会通过旋转操作保持平衡。<br><img src="/img/red_black_tree_rotate.png"></p><ul><li><h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3></li></ul><p>红黑树插入一个新结点的过程RB_INSERT是在二叉查找树插入过程的基础上改进的，先按照二叉排序的插入过程插入到红黑树中，然后将新插入的结点标记为红色，然后调用一个辅助的过程RB_INSERT_FIXUP来调整结点并重新着色，使得满足红黑树的性质。</p><ul><li><h3 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h3></li></ul><p>红黑树删除结点过程是在二叉查找树删除结点过程的基础改进的。与二叉查找树类似，删除的结点分为三种情况：</p><ol><li>无左右孩子</li><li>有左孩子或者右孩子</li><li>既左孩子又有右孩子</li></ol><p>红黑树在删除结点后需要检查是否破坏了红黑树的性质。如果删除的结点y是红色的，则删除后的树仍然是保持红黑树的性质，因为树中各个结点的黑高度没有改变，不存在两个相邻（父结点和子结点）的红色结点，y是红色不可能是根，所有根仍然是黑色。如果删除的结点z是黑色的，则这个是破坏了红黑树的性质，需要调用RB_DELETE_FIXUP进行调整。从删除结点y的唯一孩子结点x或者是NIL处开始调整。</p><ul><li><h2 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h2></li></ul><p><a href="https://www.cnblogs.com/wxgblogs/p/5513315.html">红黑树详解</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;红黑树是一种自平衡二叉搜索树 &lt;em&gt;(任意节点的左子树的值均小于该节点，任意节点右子树的值均大于</summary>
      
    
    
    
    <category term="wip" scheme="https://victorwong171.github.io/categories/wip/"/>
    
    
    <category term="数据结构" scheme="https://victorwong171.github.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>数据库八股</title>
    <link href="https://victorwong171.github.io/2024/11/26/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%85%AB%E8%82%A1/"/>
    <id>https://victorwong171.github.io/2024/11/26/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%85%AB%E8%82%A1/</id>
    <published>2024-11-26T09:47:41.000Z</published>
    <updated>2025-02-12T02:49:08.744Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="三大范式"><a href="#三大范式" class="headerlink" title="三大范式"></a>三大范式</h2></li><li><p>第一范式<br>数据库表的所有字段都是不可分解的元组。</p></li><li><p>第二范式<br>满足第一范式的情况下，所有非主键字段完全依赖整个主键。</p></li><li><p>第三范式<br>满足第二范式情况下，所有非主键字段均不依赖其他非主键字段。</p></li><li><h2 id="sql语句执行过程"><a href="#sql语句执行过程" class="headerlink" title="sql语句执行过程"></a>sql语句执行过程</h2></li></ul><ol><li><p>解析：检查语法和语义，生成解析树。</p></li><li><p>预处理：参数化处理（如果有参数）。</p></li><li><p>优化：查询优化器选择最优的执行计划。</p></li><li><p>执行：按照执行计划，读写数据，对数据进行整理。</p></li><li><p>结果返回：将结果集返回给客户端。</p></li><li><p>清理：释放资源，记录日志。</p></li></ol><ul><li><h2 id="mysql的锁"><a href="#mysql的锁" class="headerlink" title="mysql的锁"></a>mysql的锁</h2></li></ul><table><thead><tr><th>名称</th><th>介绍</th><th align="left">优点</th><th>缺点</th></tr></thead><tbody><tr><td>表级锁</td><td>对当前整张表加锁</td><td align="left">实现简单，加锁块</td><td>并发性能差</td></tr><tr><td>行锁</td><td>对某行加锁。若存在索引，则锁记录在索引上；若没索引，innoDB会创建一个隐藏的聚簇索引加锁。</td><td align="left">锁粒度小，并发高</td><td>加锁开销大，加锁慢，可能死锁</td></tr><tr><td>Gap 锁</td><td>间隙锁，锁定索引记录的间隙，防止其他事务在两个索引记录中间插入新的记录。</td><td align="left">可防止幻读，保证事务一致性，隔离性</td><td>阻塞在间隙中的数据插入</td></tr><tr><td>Next-key lock</td><td>Next-Key 锁是 Gap 锁和 行锁的组合。它不仅锁定索引记录本身，还锁定记录之前的间隙。相比gap锁，就相当于从开区间*(1,3)<em>变为闭区间</em>(1,3]*</td><td align="left">可防止幻读，保证事务一致性，隔离性</td><td>阻塞在间隙中的数据插入</td></tr></tbody></table><ul><li><h3 id="共享锁-排他锁"><a href="#共享锁-排他锁" class="headerlink" title="共享锁&amp;排他锁"></a>共享锁&amp;排他锁</h3></li><li><p>共享锁<br>读锁，允许多个事务同时读取同一数据，但不允许其他事务修改。<br>查询操作，读多写少</p></li><li><p>排他锁<br>写锁，仅允许一个事务在执行阶段访问数据，不允许其他事务读取、修改。<br>更新删除操作，写多读少</p></li><li><h3 id="乐观锁-悲观锁"><a href="#乐观锁-悲观锁" class="headerlink" title="乐观锁&amp;悲观锁"></a>乐观锁&amp;悲观锁</h3></li><li><p>乐观锁<br>假设事务执行过程中数据不会被修改，仅在提交事务时会检查数据是否被修改*(时间戳、版本号)*<br>加锁时间短，并发性能好，适合读多写少</p></li><li><p>悲观锁<br>假设事务执行过程中数据可能被修改，在读取数据时就加锁，事务结束释放锁。<br>加锁时间长，并发性能差，适合写频繁场景，有效避免数据冲突</p></li><li><h3 id="如何解决死锁"><a href="#如何解决死锁" class="headerlink" title="如何解决死锁"></a>如何解决死锁</h3></li><li><p>预防</p></li></ul><ol><li>按顺序加锁</li><li>使用事务隔离级别</li><li>减少事务持有锁的时间，将大事务拆成多个小事务</li><li>读多写少的场景，使用乐观锁</li><li>避免事务嵌套</li></ol><ul><li>处理</li></ul><ol><li>设置事务的超时时间*(innodb_lock_wait_timeout)*，超时自动会滚</li><li>回滚重试，程序检测到死锁异常时，手动回滚重试</li><li>数据库会存在死锁检测，检测到以后会自动选择一个事务回滚</li></ol><ul><li><h2 id="存储引擎"><a href="#存储引擎" class="headerlink" title="存储引擎"></a>存储引擎</h2></li></ul><table><thead><tr><th>名称</th><th>介绍</th></tr></thead><tbody><tr><td>InnoDB</td><td>适合需要事务支持和高并发写操作的场景</td></tr><tr><td>MyISAM</td><td>适合读多写少的场景，对事务支持要求不高，支持<strong>全文索引</strong></td></tr><tr><td>MEMORY</td><td>适合需要高速读写的临时数据</td></tr><tr><td>ARCHIVE</td><td>适合存储历史数据或日志数据</td></tr><tr><td>CSV</td><td>适合数据导入导出和交换</td></tr><tr><td>BLACKHOLE</td><td>适合测试和开发环境，日志记录</td></tr><tr><td>FEDERATED</td><td>适合分布式数据库和跨服务器查询</td></tr><tr><td>NDB</td><td>适合高可用性和高并发的实时数据处理场景</td></tr></tbody></table><ul><li><h3 id="InnoDB"><a href="#InnoDB" class="headerlink" title="InnoDB"></a>InnoDB</h3></li></ul><p>mysql的默认事务存储引擎，支持事务<code>ACID</code>(原子性、一致性、隔离性、持久性)、行级锁、外间约束、多版本并发控制(MVCC)与聚簇索引等特点。</p><ul><li><h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h2></li></ul><p>用于加速数据库数据检索的一种数据结构，索引检索数据不需要扫描整个表，即可直接定位到对应数据。</p><ol><li>能提高查询速度</li><li>需要占用额外空间</li><li>影响写操作性能(需要维护索引)</li></ol><ul><li><h3 id="索引类型"><a href="#索引类型" class="headerlink" title="索引类型"></a>索引类型</h3></li></ul><p>按数据结构：</p><table><thead><tr><th>name</th><th>memo</th></tr></thead><tbody><tr><td>B+Tree</td><td>innodb默认使用 范围查询、精确匹配</td></tr><tr><td>哈希</td><td>memory引擎显式支持 等值查询，不支持范围查询</td></tr><tr><td>全文</td><td>搜索引擎、文档管理、知识库 全文搜索，支持文本匹配</td></tr><tr><td>位图</td><td>数据仓库 低基数列</td></tr><tr><td>B-Tree</td><td>MyISAM默认使用 等值、范围查询、排序分组</td></tr></tbody></table><p>按数据存储角度：</p><table><thead><tr><th>name</th><th>memo</th></tr></thead><tbody><tr><td>聚簇索引(主键索引)</td><td>innodb的主键索引，叶子结点包含整行数据，索引顺序与数据顺序相同，范围查询、排序性能好</td></tr><tr><td>非聚簇索引(非主键索引，二级索引，辅助索引)</td><td>叶子结点一般存的是指向数据行的地址以及指定列，索引顺序与数据顺序无关，等值查询性能好</td></tr></tbody></table><p>业务使用角度：</p><table><thead><tr><th>name</th><th>memo</th></tr></thead><tbody><tr><td>聚集索引</td><td>所有数据行都有，直接包含数据行，决定数据存储顺序，每个表唯一，范围查询、排序性能好，维护成本高</td></tr><tr><td>稀疏索引</td><td>不是所有数据行都有，一般只包含部分列，适用数据分布不均匀的场景，维护成本低</td></tr></tbody></table><p>hash索引&amp;自适应hash索引：</p><table><thead><tr><th>name</th><th>memo</th></tr></thead><tbody><tr><td>hash索引</td><td>仅memory引擎显式支持，每个数据行都对应一个hash值，存储在索引中，同时维护一个hash表，可根据hash值在hash表中找到对应数据行的地址。由于hash的特性，hash索引不支持范围查询、排序以及部分索引列匹配</td></tr><tr><td>自适应hash索引</td><td>innodb引擎对频繁使用的索引会在内存中基于B-Tree索引上在创建一个hash索引，用于快速查询</td></tr></tbody></table><ul><li><h4 id="辅助索引的查询过程"><a href="#辅助索引的查询过程" class="headerlink" title="辅助索引的查询过程"></a>辅助索引的查询过程</h4></li></ul><ol><li>先根据条件查询到对应索引，若索引覆盖(查询的列全在辅助索引的数据列中)，直接从索引返回数据</li><li>若不满足索引覆盖，根据索引中的数据行地址，查询实际的数据行，返回数据</li></ol><ul><li><h4 id="联合索引的查询过程"><a href="#联合索引的查询过程" class="headerlink" title="联合索引的查询过程"></a>联合索引的查询过程</h4></li></ul><p>联合索引(复合索引，包含多个列的索引)，</p><ol><li>解析查询条件，与所需筛选的列</li><li>选择索引</li><li>依据最左匹配原则(按索引顺序与查询条件进行匹配)，逐级分次匹配到满足条件的数据行</li><li>若满足索引覆盖则直接返回数据，若不满足则需要回表查询</li></ol><ul><li><h3 id="主键索引与非主键索引查询的区别是什么"><a href="#主键索引与非主键索引查询的区别是什么" class="headerlink" title="主键索引与非主键索引查询的区别是什么"></a>主键索引与非主键索引查询的区别是什么</h3></li></ul><p>主键索引叶子结点存储了整个数据行的数据，所以不会存在回表查询，而非主键索引可能会出现索引不覆盖的情况需要回表查询</p><ul><li><h3 id="InnoDB索引采用B-Tree而不是其他数据结构的原因"><a href="#InnoDB索引采用B-Tree而不是其他数据结构的原因" class="headerlink" title="InnoDB索引采用B+Tree而不是其他数据结构的原因"></a>InnoDB索引采用B+Tree而不是其他数据结构的原因</h3></li></ul><p>红黑树等二叉搜索树的出度*(分叉个数、子结点数码)*最多为2，会导致树高相对更高，在查询时会io次数更多，效率更低。<br>b+Tree的数据全在叶子结点中，叶子结点是一个有序连表，而bTree结点数据分布在所有节点中。</p><p>高效的范围查询：叶子节点的有序链表使得范围查询非常高效。<br>减少磁盘 I&#x2F;O：内部节点只存储键值和指针，同样空间下可以存储更多结点，减少树的高度，减少磁盘访问次数。<br>更好的缓存性能：更多的节点可以被缓存，提高查询性能。<br>高度平衡：所有叶子节点在同一层，保证了稳定的查询性能。<br>高效的插入和删除操作：主要影响叶子节点，减少内部节点的调整。</p><ul><li><h2 id="执行与优化"><a href="#执行与优化" class="headerlink" title="执行与优化"></a>执行与优化</h2></li><li><h3 id="EXPLAIN"><a href="#EXPLAIN" class="headerlink" title="EXPLAIN"></a>EXPLAIN</h3></li></ul><p>explain关键字用于分析sql语句的执行情况，可以通过他进行sql语句的性能分析。</p><p>type：表示连接类型，从好到差的类型排序为</p><ul><li>system：系统表，数据已经加载到内存里。</li><li>const：常量连接，通过索引一次就找到。</li><li>eq_ref：唯一性索引扫描，返回所有匹配某个单独值的行。</li><li>ref：非主键非唯一索引等值扫描，const或eq_ref改为普通非唯一索引。</li><li>range：范围扫描，在索引上扫码特定范围内的值。</li><li>index：索引树扫描，扫描索引上的全部数据。</li><li>all：全表扫描。</li></ul><p>key：显示MySQL实际决定使用的键。</p><p>key_len：显示MySQL决定使用的键长度，长度越短越好</p><p>Extra：额外信息</p><ul><li><p>Using filesort：MySQL使用外部的索引排序，很慢需要优化。</p></li><li><p>Using temporary：使用了临时表保存中间结果，很慢需要优化。</p></li><li><p>Using index：使用了覆盖索引。</p></li><li><p>Using where：使用了where。</p></li><li><h3 id="简述优化过程"><a href="#简述优化过程" class="headerlink" title="简述优化过程"></a>简述优化过程</h3></li></ul><ol><li>通过慢日志定位执行较慢的SQL语句</li><li>利用explain对这些关键字段进行分析</li><li>根据分析结果进行优化</li></ol><ul><li><h3 id="分库分表"><a href="#分库分表" class="headerlink" title="分库分表"></a>分库分表</h3></li></ul><p>垂直分库：将不同业务模块数据分离到不同数据库<br>水平分库：按照一定规则将数据分散到多个数据库<br>垂直分表：将表中不同字段按业务逻辑分离<br>水平分表：按特定规则将表数据分到多个表</p><ul><li><h2 id="log"><a href="#log" class="headerlink" title="log"></a>log</h2></li></ul><table><thead><tr><th>type</th><th>memo</th></tr></thead><tbody><tr><td>redo log</td><td>存储引擎级别的log（InnoDB有，MyISAM没有），该log关注于事务的恢复.在重启mysql服务的时候，根据redo log进行重做，从而使事务有持久性。</td></tr><tr><td>undo log</td><td>是存储引擎级别的log（InnoDB有，MyISAM没有）保证数据的原子性，该log保存了事务发生之前的数据的一个版本，可以用于回滚，是MVCC的重要实现方法之一。</td></tr><tr><td>bin log</td><td>数据库级别的log，关注恢复数据库的数据。</td></tr></tbody></table><ul><li><h3 id="redo-log与binlog的区别"><a href="#redo-log与binlog的区别" class="headerlink" title="redo log与binlog的区别"></a>redo log与binlog的区别</h3></li></ul><ol><li>redo log是InnoDB引擎特有的，只记录该引擎中表的修改记录。binlog是MySQL的Server层实现<br>的，会记录所有引擎对数据库的修改。</li><li>redo log是物理日志，记录的是在具体某个数据页上做了什么修改；binlog是逻辑日志，记录的是这<br>个语句的原始逻辑。</li><li>redo log是循环写的，空间固定会用完；binlog是可以追加写入的，binlog文件写到一定大小后会切<br>换到下一个，并不会覆盖以前的日志。</li></ol><ul><li><h3 id="crash-safe能力是什么"><a href="#crash-safe能力是什么" class="headerlink" title="crash-safe能力是什么"></a>crash-safe能力是什么</h3></li></ul><p>InnoDB通过redo log保证即使数据库发生异常重启，之前提交的记录都不会丢失，这个能力称为crashsafe。</p><ul><li><h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2></li><li><h3 id="简述"><a href="#简述" class="headerlink" title="简述"></a>简述</h3><p>是数据库中用于确保数据一致性和完整性的机制。具体来讲，事务是一连串数据库操作的集合，这些操作要么全部执行，要么全部不执行，以保证数据库的一致状态。<br>主要特性如下(ACID):</p><table><thead><tr><th>特性</th><th>memo</th></tr></thead><tbody><tr><td>原子性</td><td>事务是一个不可分割的最小工作单位，事务中的所有操作要么全部成功，要么全部失败。如果事务中的任何一个操作失败，整个事务都会回滚，撤销所有已经完成的操作</td></tr><tr><td>一致性</td><td>事务必须保证数据库从一个一致状态转换到另一个一致状态。事务执行前后，数据库的完整性约束必须保持不变</td></tr><tr><td>隔离性</td><td>多个事务并发执行时，每个事务的执行不应受到其他事务的影响。事务的隔离性确保了事务之间的独立性，避免了修改丢失、脏读、不可重复读和幻读等问题</td></tr><tr><td>持久性</td><td>一旦事务提交，其对数据库的更改将是永久的，即使系统发生故障也不会丢失</td></tr></tbody></table></li></ul><blockquote><p>脏读：读取了另一个事务尚未提交的数据。<br>不可重复读：在一个事务中，多次读取同一数据行得到的结果不一致。<br>幻读：在一个事务中，多次执行相同的查询，但结果集不同。</p></blockquote><ul><li><h3 id="多个事务同时进行可能会出现什么问题"><a href="#多个事务同时进行可能会出现什么问题" class="headerlink" title="多个事务同时进行可能会出现什么问题"></a>多个事务同时进行可能会出现什么问题</h3></li></ul><p>修改丢失、脏读、不可重复读和幻读等。</p><ul><li><h3 id="隔离界别"><a href="#隔离界别" class="headerlink" title="隔离界别"></a>隔离界别</h3></li></ul><table><thead><tr><th>category</th><th>memo</th></tr></thead><tbody><tr><td>读未提交</td><td>最低的隔离级别，允许一个事务读取另一个事务尚未提交的数据，可能会导致脏读。</td></tr><tr><td>读已提交</td><td>一个事务只能读取另一个事务已经提交的数据，可以避免脏读，但可能会导致不可重复读和幻读。</td></tr><tr><td>可重复读</td><td>在同一个事务中，多次读取同一数据的结果是一致的，可以避免脏读和不可重复读，但可能会导致幻读。</td></tr><tr><td>序列化</td><td>最高的隔离级别，完全隔离并发事务，避免了脏读、不可重复读和幻读，但性能较差。</td></tr></tbody></table><ul><li><h3 id="MVCC"><a href="#MVCC" class="headerlink" title="MVCC"></a>MVCC</h3></li></ul><p>多版本并发控制（MVCC）通过生成和管理数据行的多个版本，减少了锁的竞争，提高了数据库的并发性能。MVCC 允许多个事务同时读取和写入数据，而不会相互干扰，从而支持更高效的并发操作。</p><ul><li><h4 id="读提交和可重复读都基于MVCC实现，有什么区别"><a href="#读提交和可重复读都基于MVCC实现，有什么区别" class="headerlink" title="读提交和可重复读都基于MVCC实现，有什么区别"></a>读提交和可重复读都基于MVCC实现，有什么区别</h4></li><li><p>读已提交：每次读取操作都会看到最新的已提交数据，可能产生不可重复读。</p></li><li><p>可重复读：事务在开始时会看到一个快照，避免不可重复读，但可能产生幻读。</p></li><li><h3 id="InnoDB如何保证事务的原子性、持久性和一致性"><a href="#InnoDB如何保证事务的原子性、持久性和一致性" class="headerlink" title="InnoDB如何保证事务的原子性、持久性和一致性"></a>InnoDB如何保证事务的原子性、持久性和一致性</h3></li></ul><ol><li>利用undo log保障原子性。该log保存了事务发生之前的数据的一个版本，可以用于回滚，从而保证事务原子性。</li><li>利用redo log保证事务的持久性，该log关注于事务的恢复。在重启mysql服务的时候，根据redo log进行重做，从而使事务有持久性。</li><li>利用undo log+redo log保障一致性。事务中的执行需要redo log，如果执行失败，需要undo log 回滚。</li></ol><ul><li><h2 id="集群"><a href="#集群" class="headerlink" title="集群"></a>集群</h2></li><li><h3 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h3></li></ul><p>通过binlog完成数据复制，提高可用性，拓展读取能力，数据备份。<br>其中：</p><blockquote><p>主服务器（Master）：负责处理所有的写操作（INSERT、UPDATE、DELETE等），并将这些操作记录到 Binlog 中。<br>从服务器（Slave）：从主服务器获取 Binlog，并应用这些日志来保持与主服务器的数据同步。</p></blockquote><ul><li><h4 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h4></li></ul><ol><li><p>在master上开启、设置binlog</p></li><li><p>在master上创建一个复制的用户并授权</p></li><li><p>在slave上的配置文件中设置 </p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell">[mysqld]<br>server-id=2<br>relay-log=mysql-relay-bin<br>log-slave-updates=1<br>read-only=1<br></code></pre></td></tr></table></figure></li><li><p>在slave设置主从关系</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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><code class="hljs sql">CHANGE MASTER <span class="hljs-keyword">TO</span><br>MASTER_HOST<span class="hljs-operator">=</span><span class="hljs-string">&#x27;master_host&#x27;</span>,<br>MASTER_USER<span class="hljs-operator">=</span><span class="hljs-string">&#x27;repl&#x27;</span>,<br>MASTER_PASSWORD<span class="hljs-operator">=</span><span class="hljs-string">&#x27;password&#x27;</span>,<br>MASTER_LOG_FILE<span class="hljs-operator">=</span><span class="hljs-string">&#x27;mysql-bin.000001&#x27;</span>,<br>MASTER_LOG_POS<span class="hljs-operator">=</span><span class="hljs-number">12345</span>;<br></code></pre></td></tr></table></figure></li><li><p>启动slave的I&#x2F;O线程从master获取binlog事件，启动SQL线程应用binlog事件</p></li></ol><ul><li><h4 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h4></li><li><p>优点<br>提高可用性，master故障时slave可以转正，数据也能备份<br>读写分离，降低master压力</p></li><li><p>缺点<br>延迟、数据不一致，master负载大、网络差或者故障的情况下会有binlog同步延迟或者不能同步的问题</p></li><li><h3 id="如何保证主备一致"><a href="#如何保证主备一致" class="headerlink" title="如何保证主备一致"></a>如何保证主备一致</h3><p>主服务器将所有的数据更改操作记录到二进制日志（Binlog）中。<br>从服务器通过 I&#x2F;O 线程从主服务器获取 Binlog，并通过 SQL 线程应用这些日志，从而保持与主服务器的数据一致</p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;三大范式&quot;&gt;&lt;a href=&quot;#三大范式&quot; class=&quot;headerlink&quot; title=&quot;三大范式&quot;&gt;&lt;/a&gt;三大范式&lt;/h2&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;第一范式&lt;br&gt;数据库表的所有字段都是不可分解的元组。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="数据库" scheme="https://victorwong171.github.io/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    <category term="mysql" scheme="https://victorwong171.github.io/tags/mysql/"/>
    
  </entry>
  
  <entry>
    <title>golang gc</title>
    <link href="https://victorwong171.github.io/2024/09/27/golang-gc/"/>
    <id>https://victorwong171.github.io/2024/09/27/golang-gc/</id>
    <published>2024-09-27T08:00:01.000Z</published>
    <updated>2025-02-12T02:48:21.892Z</updated>
    
    <content type="html"><![CDATA[<h2 id="goroutine-对比-threrad"><a href="#goroutine-对比-threrad" class="headerlink" title="goroutine 对比 threrad"></a>goroutine 对比 threrad</h2><table><thead><tr><th></th><th>goroutine</th><th>thread</th></tr></thead><tbody><tr><td>内存占用</td><td>2KB</td><td>1MB</td></tr><tr><td>创建销毁</td><td>用户级，go runtime负责管理</td><td>系统级</td></tr><tr><td>切换</td><td>3个寄存器，200ns，约2400～3600条指令时间</td><td>16个寄存器，1000～1500ns，12000～18000指令时间</td></tr></tbody></table><ol><li><p><strong>Go语言使用了哪种垃圾回收算法？</strong></p><ul><li>Go语言采用了三色标记清除算法，并结合写屏障技术来提高并发性。从Go 1.5开始引入了并发垃圾收集器，之后版本不断优化以减少停顿时间。</li></ul></li><li><p><strong>解释一下三色标记法的工作原理？</strong></p><ul><li>三色标记法将对象分为白色、黑色和灰色三种状态：<ul><li>白色：表示未访问过的对象。</li><li>灰色：表示已经被发现但其引用的对象还未全部检查的对象。</li><li>黑色：表示已经完全扫描过且确定为存活的对象。</li></ul></li><li>在GC过程中，所有可达对象最终会被标记为黑色，而仅存的白色对象则被认为是垃圾并被回收。</li></ul></li><li><p><strong>如何触发垃圾回收？</strong></p><ul><li>Go语言的GC是自动触发的，基于分配速率和堆大小的增长情况。也可以通过调用<code>runtime.GC()</code>手动触发一次完整的垃圾回收过程。</li></ul></li><li><p><strong>Go语言中的GC是并发的还是串行的？</strong></p><ul><li>Go语言的GC是并发的，这意味着它可以与应用程序代码同时运行，从而尽量减少对程序执行的影响。不过，在某些阶段仍需要短暂地暂停所有的goroutine（称为”STW”, Stop-The-World），以便安全地完成特定的操作。</li></ul></li><li><p><strong>什么是写屏障？为什么需要它？</strong></p><ul><li>写屏障是在对象之间建立或更新引用时插入的一段辅助代码。它的作用是在并发GC期间确保所有活跃对象都能被正确地标记，即使这些对象正在被修改。</li></ul></li><li><p><strong>GC Roots是什么？</strong><br>GC Roots是指那些可以直接访问到的对象集合，</p><ul><li>全局变量</li><li>活跃的goroutine的栈上的所有引用</li><li>runtime的部分数据</li></ul></li><li><p><strong>堆栈上分别会有哪些数据</strong><br> 栈上：</p><ul><li><p>不会发生逃逸的局部变量</p></li><li><p>小型且大小固定的结构体、数组</p></li></ul><p> 堆上：</p><ul><li>make、new创建的对象</li><li>大型的数据 大结构体、大数组</li><li>切片 map channel，基本信息比如长度、容量、指针可能在栈上，实际的数据会在堆上</li><li>逃逸的对象</li></ul></li><li><p><strong>什么情况会发生逃逸</strong><br>局部对象的作用域发生改变、扩散就会发生逃逸</p></li></ol><ul><li>函数返回了期内局部变量的指针</li><li>局部变量被全局变量持有指针</li><li>局部变量的指针被传递给另外一个goroutine</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;goroutine-对比-threrad&quot;&gt;&lt;a href=&quot;#goroutine-对比-threrad&quot; class=&quot;headerlink&quot; title=&quot;goroutine 对比 threrad&quot;&gt;&lt;/a&gt;goroutine 对比 threrad&lt;/h2&gt;&lt;</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="gc" scheme="https://victorwong171.github.io/tags/gc/"/>
    
  </entry>
  
  <entry>
    <title>golang map</title>
    <link href="https://victorwong171.github.io/2024/09/26/golang-map%E8%AF%A6%E8%A7%A3/"/>
    <id>https://victorwong171.github.io/2024/09/26/golang-map%E8%AF%A6%E8%A7%A3/</id>
    <published>2024-09-26T10:58:27.000Z</published>
    <updated>2025-08-05T18:53:25.395Z</updated>
    
    <content type="html"><![CDATA[<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>和 map 相关的操作主要是：</p><ol><li>增加一个 k-v 对 —— Add or insert；</li><li>删除一个 k-v 对 —— Remove or delete；</li><li>修改某个 k 对应的 v —— Reassign；</li><li>查询某个 k 对应的 v —— Lookup；</li></ol><p>简单说就是最基本的 <code>增删查改</code>。</p><p>Go 语言采用的是哈希查找表，并且使用链表解决哈希冲突。</p><p>表示 map 的结构体是 hmap</p><p><code>bmap</code> 就是我们常说的“桶”，桶里面会最多装 8 个 key，这些 key 之所以会落入同一个桶，是因为它们经过哈希计算后，哈希结果是“一类”的。在桶内，又会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置（一个桶内最多有8个位置）</p><p>每个 bucket 设计成最多只能放 8 个 key-value 对，如果有第 9 个 key-value 落入当前的 bucket，那就需要再构建一个 bucket ，通过 <code>overflow</code> 指针连接起来。<br><img src="/img/map_lookup.png"></p><p>上图中，假定 B &#x3D; 5，所以 bucket 总数就是 2^5 &#x3D; 32。首先计算出待查找 key 的哈希，使用低 5 位 <code>00110</code>，找到对应的 6 号 bucket，使用高 8 位 <code>10010111</code>，对应十进制 151，在 6 号 bucket 中寻找 tophash 值（HOB hash）为 151 的 key，找到了 2 号槽位，这样整个查找过程就结束了。</p><p>如果在 bucket 中没找到，并且 overflow 不为空，还要继续去 overflow bucket 中寻找，直到找到或是所有的 key 槽位都找遍了，包括所有的 overflow bucket。</p><h2 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h2><p>使用哈希表的目的就是要快速查找到目标 key，然而，随着向 map 中添加的 key 越来越多，key 发生碰撞的概率也越来越大。随着向 map 中添加的 key 越来越多，bucket 中的 8 个 cell 会被逐渐塞满，接近退化成链表，各种操作的效率直接降为 O(n)</p><p>需要有一个指标来衡量前面描述的情况，这就是<code>装载因子</code>。Go 源码里这样定义 <code>装载因子</code>：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs golang">loadFactor := count / (<span class="hljs-number">2</span>^B)<br></code></pre></td></tr></table></figure><p>count 就是 map 的元素个数，2^B 表示 bucket 数量。</p><p>再来说触发 map 扩容的时机：在向 map 插入新 key 的时候，会进行条件检测，符合下面这 2 个条件，就会触发扩容：</p><ol><li>装载因子超过阈值，源码里定义的阈值是 6.5。</li><li>overflow 的 bucket 数量过多：当 B 小于 15，也就是 bucket 总数 2^B 小于 2^15 时，如果 overflow 的 bucket 数量超过 2^B；当 B &gt;&#x3D; 15，也就是 bucket 总数 2^B 大于等于 2^15，如果 overflow 的 bucket 数量超过 2^15。即：<br>B&lt;15 cnt(overflow bucket) &gt; cnt(bucket)<br>B&gt;&#x3D;15 cnt(overflow bucket) &gt; 2^15</li></ol><p>for case 1: 平均每个 bucket 中存放了超过6.5个 kv<br>for case 2: 本质上是 key 的 hash 特征趋同<br>    1 多次循环 插入大量但未达到 case 1 的元素，然后删除其中部分元素，致使出现了 大量 overflow bucket, 即 kv 不多，但 overflow bucket 很多<br>    2 就是 key 的 hash 特征恰好一致，未达到分流的效果(此时golang的扩容策略无效 因为问题不是容量的问题 是hash算法的问题 应该无解 或者做丑陋的patch)</p><p>由于 map 扩容需要将原有的 key&#x2F;value 重新搬迁到新的内存地址，如果有大量的 key&#x2F;value 需要搬迁，会非常影响性能。因此 Go map 的扩容采取了一种称为“渐进式”地方式，<strong>原有的 key 并不会一次性搬迁完毕，每次最多只会搬迁 2 个 bucket</strong>。</p><p>对于条件 1，元素太多，而 bucket 数量太少，将 B 加 1 ：<br>bucket 最大数量（2^B）直接变成原来 bucket 数量的 2 倍。就没这么简单了。要重新计算 key 的哈希，才能决定它到底落在哪个 bucket。例如，原来 B &#x3D; 5，计算出 key 的哈希后，只用看它的低 5 位，就能决定它落在哪个 bucket。扩容后，B 变成了 6，因此需要多看一位，它的低 6 位决定 key 落在哪个 bucket。这称为 <code>rehash</code>。<br><img src="/img/map_rehash.png"></p><p>对于条件 2，元素没那么多，但是 overflow bucket 数特别多，说明很多 bucket 都没装满。解决办法就是开辟一个新 bucket 空间，将老 bucket 中的元素移动到新 bucket，使得同一个 bucket 中的 key 排列地更紧密。这样，原来，在 overflow bucket 中的 key 可以移动到 bucket 中来。结果是节省空间，提高 bucket 利用率，map 的查找和插入效率自然就会提升。<br>从老的 buckets 搬迁到新的 buckets，由于 bucktes 数量不变，因此可以按序号来搬，比如原来在 0 号 bucktes，到新的地方后，仍然放在 0 号 buckets。</p><p>在搬迁过程中，oldbuckets 指针还会指向原来老的 []bmap，并且已经搬迁完毕的 key 的 tophash 值会是一个状态值，表示 key 的搬迁去向。</p><p>在插入、修改、删除 key 以及遍历的时候，都会尝试进行搬迁 buckets 的工作。先检查 oldbuckets 是否搬迁完毕，具体来说就是检查 oldbuckets 是否为 nil。</p><h2 id="遍历"><a href="#遍历" class="headerlink" title="遍历"></a>遍历</h2><p>遍历所有的 bucket 以及它后面挂的 overflow bucket，然后挨个遍历 bucket 中的所有 cell。每个 bucket 中包含 8 个 cell，从有 key 的 cell 中取出 key 和 value。</p><p>开始在哪个 bucket 以及在 bucket 中哪个 cell 开始遍历是通过生成随机数 然后进行位运算决定的</p><h2 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h2><p>对 key 计算 hash 值，根据 hash 值按照之前的流程，找到要赋值的位置（可能是插入新 key，也可能是更新老 key）</p><h2 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h2><p>删除操作同样是两层循环，核心还是找到 key 的具体位置。寻找过程都是类似的，在 bucket 中挨个 cell 寻找，找到对应位置后，对 key 或者 value 进行“清零”，最后，将 count 值减 1，将对应位置的 tophash 值置成 <code>Empty</code></p><h2 id="float-类型作为key"><a href="#float-类型作为key" class="headerlink" title="float 类型作为key"></a>float 类型作为key</h2><p>float 类型可以作为 map 的 key ，当用 float64 作为 key 的时候，先要将其转成 uint64 类型，再插入 key 中。但是由于精度的问题，会导致一些诡异的问题，慎用之。</p><h2 id="线程安全"><a href="#线程安全" class="headerlink" title="线程安全"></a>线程安全</h2><ul><li>边遍历边删除，遍历的结果就可能不会是相同的了，有可能结果遍历结果集中包含了删除的 key，也有可能不包含，这取决于删除 key 的时间：是在遍历到 key 所在的 bucket 时刻前或者后</li><li>多协程并发写 会触发 <strong>concurrent map writes</strong></li><li>多协程并发读写 会触发 <strong>concurrent map read and map write</strong></li><li>多协程并发遍历&#x2F;写 会触发 <strong>concurrent map iteration and map write</strong></li></ul><h2 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h2><p><a href="https://github.com/golang-design/go-questions/tree/main/content/map">go-questions: map</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerlink&quot; title=&quot;概述&quot;&gt;&lt;/a&gt;概述&lt;/h2&gt;&lt;p&gt;和 map 相关的操作主要是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;增加一个 k-v 对 —— Add or insert；&lt;/li&gt;
&lt;li&gt;删除一</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="hash" scheme="https://victorwong171.github.io/tags/hash/"/>
    
    <category term="map" scheme="https://victorwong171.github.io/tags/map/"/>
    
  </entry>
  
  <entry>
    <title>hexo 图片路径生成问题</title>
    <link href="https://victorwong171.github.io/2024/09/26/hexo%E5%9B%BE%E7%89%87%E8%B7%AF%E5%BE%84%E7%94%9F%E6%88%90%E9%97%AE%E9%A2%98/"/>
    <id>https://victorwong171.github.io/2024/09/26/hexo%E5%9B%BE%E7%89%87%E8%B7%AF%E5%BE%84%E7%94%9F%E6%88%90%E9%97%AE%E9%A2%98/</id>
    <published>2024-09-26T06:17:04.000Z</published>
    <updated>2024-09-25T22:29:22.249Z</updated>
    
    <content type="html"><![CDATA[<p>使用hexo进行博客部署时，图片路径生成出现如下问题</p><p>预期元素应该为:<br><code>&lt;img src=&quot;/img/assets.avif&quot; alt=&quot;Terraform deployment workflow&quot;&gt;</code><br>但实际是<br><code>&lt;img src=&quot;/.io//assets.avif&quot; alt=&quot;Terraform deployment workflow&quot;&gt;</code></p><p>hexo版本配置信息如下：</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></pre></td><td class="code"><pre><code class="hljs bash">INFO  Validating config<br>hexo: 7.2.0<br>hexo-cli: 4.3.2<br>os: darwin 23.5.0 14.5<br><br>node: 20.5.1<br>acorn: 8.10.0<br>ada: 2.5.1<br>ares: 1.19.1<br><span class="hljs-built_in">base64</span>: 0.5.0<br>brotli: 1.0.9<br>cjs_module_lexer: 1.2.2<br>cldr: 43.1<br>icu: 73.2<br>llhttp: 8.1.1<br>modules: 115<br>napi: 9<br>nghttp2: 1.55.1<br>openssl: 3.1.2<br>simdutf: 3.2.14<br>tz: 2023c<br>undici: 5.22.1<br>unicode: 15.0<br>uv: 1.46.0<br>uvwasi: 0.0.18<br>v8: 11.3.244.8-node.10<br>zlib: 1.2.11<br></code></pre></td></tr></table></figure><p>根据网上<a href="https://hexo.io/zh-cn/docs/asset-folders">线索</a>，修改hexo的<code>\node_modules\hexo-asset-image\index.js </code> 26行。<br>原：<br><code>link = link.substring(beginPos, endPos) + &#39;/&#39; + appendLink;</code><br>改为(根据自己情况)：<br><code>link = &#39;img/&#39; + appendLink;</code></p><p>经过 <code>hexo g</code> 后生成的图像元素即与预期元素<br><code>&lt;img src=&quot;/img/assets.avif&quot; alt=&quot;Terraform deployment workflow&quot;&gt;</code><br>相同。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;使用hexo进行博客部署时，图片路径生成出现如下问题&lt;/p&gt;
&lt;p&gt;预期元素应该为:&lt;br&gt;&lt;code&gt;&amp;lt;img src=&amp;quot;/img/assets.avif&amp;quot; alt=&amp;quot;Terraform deployment workflow&amp;quot</summary>
      
    
    
    
    <category term="异常" scheme="https://victorwong171.github.io/categories/%E5%BC%82%E5%B8%B8/"/>
    
    
    <category term="hexo" scheme="https://victorwong171.github.io/tags/hexo/"/>
    
  </entry>
  
  <entry>
    <title>golang map</title>
    <link href="https://victorwong171.github.io/2024/09/18/golang-map/"/>
    <id>https://victorwong171.github.io/2024/09/18/golang-map/</id>
    <published>2024-09-18T14:39:02.000Z</published>
    <updated>2024-12-17T04:22:21.081Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="引"><a href="#引" class="headerlink" title="引"></a>引</h2></li></ul><p>维基百科里这样定义 map：</p><blockquote><p>In computer science, an associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection.</p></blockquote><ul><li><h2 id="算法基础"><a href="#算法基础" class="headerlink" title="算法基础"></a>算法基础</h2></li></ul><p>在<code>golang</code>中，map的实现主要是基于hash算法。而hash算法常常会出现碰撞的问题，一般有两种方式进行应对，一是链表地址(golang就采用了此方式)，再就是开放寻址法。</p><ul><li><h3 id="hash表-单向散列表"><a href="#hash表-单向散列表" class="headerlink" title="hash表(单向散列表)"></a>hash表(单向散列表)</h3></li></ul><p>通过建立key与elem的映射，达到高效查找key对应的elem，一般时间复杂度为O(1)。<br>举个简单的例子：</p><blockquote><p>仅用一个数组来实现哈希表。在哈希表中，我们将数组中的每个空位称为桶（bucket），每个桶可存储一个键值对。因此，查询操作就是找到 key 对应的桶，并在桶中获取 value 。<br>那么，如何基于 key 定位对应的桶呢？这是通过哈希函数（hash function）实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间<br>输入一个 key ，哈希函数的计算过程分为以下两步。</p><ol><li><p>通过某种哈希算法 hash() 计算得到哈希值。</p></li><li><p>将哈希值对桶数量（数组长度）capacity 取模，从而获取该 key 对应的数组索引 index 。<br><code>index = hash(key) % capacity</code></p></li></ol><p>随后，我们就可以利用 index 在哈希表中访问对应的桶，从而获取 value 。</p><p>设数组长度 capacity &#x3D; 100、哈希算法 hash(key) &#x3D; key ，易得哈希函数为 key % 100 。<br><img src="/img/hash_function.png"></p></blockquote><ul><li><h3 id="hash冲突"><a href="#hash冲突" class="headerlink" title="hash冲突"></a>hash冲突</h3></li></ul><p>从本质上看，哈希函数的作用是将所有 <code>key</code> 构成的输入空间映射到数组所有索引构成的输出空间，而输入空间往往远大于输出空间。因此，<strong>理论上一定存在“多个输入对应相同输出”的情况</strong>。<br>对于上述示例中的哈希函数，当输入的 <code>key</code> 后两位相同时，哈希函数的输出结果也相同。例如，查询学号为 12836 和 20336 的两个学生时，我们得到：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">12836 % 100 = 36<br>20336 % 100 = 36<br></code></pre></td></tr></table></figure><p>两个学号指向了同一个姓名，这显然是不对的。我们将这种多个输入对应同一输出的情况称为哈希冲突（hash collision）<br><img src="/img/hash_collision.png"></p><blockquote><p>解决hash冲突有两种方法</p><ol><li>链式地址<br>在原始哈希表中，每个桶仅能存储一个键值对。链式地址（separate chaining）将单个元素转换为链表，将键值对作为链表节点，将所有发生冲突的键值对都存储在同一链表中。<br><img src="/img/hash_table_chaining.png"><br>基于链式地址实现的哈希表的操作方法发生了以下变化。</li></ol><ul><li><strong>查询元素</strong>：输入 <code>key</code> ，经过哈希函数得到桶索引，即可访问链表头节点，然后遍历链表并对比 <code>key</code> 以查找目标键值对。</li><li><strong>添加元素</strong>：首先通过哈希函数访问链表头节点，然后将节点（键值对）添加到链表中。</li><li><strong>删除元素</strong>：根据哈希函数的结果访问链表头部，接着遍历链表以查找目标节点并将其删除。<br>链式地址存在以下局限性。</li><li><strong>占用空间增大</strong>：链表包含节点指针，它相比数组更加耗费内存空间。</li><li><strong>查询效率降低</strong>：因为需要线性遍历链表来查找对应元素。<br>值得注意的是，当链表很长时，查询效率 $O(n)$ 很差。<strong>此时可以将链表转换为“AVL 树”或“红黑树”</strong>，从而将查询操作的时间复杂度优化至 $O(\log n)$ 。</li></ul><ol start="2"><li>开放寻址<br><u>开放寻址（open addressing）</u>不引入额外的数据结构，而是通过“多次探测”来处理哈希冲突，探测方式主要包括线性探测、平方探测和多次哈希等。<br>下面以线性探测为例，介绍开放寻址哈希表的工作机制。<br>线性探测采用固定步长的线性搜索来进行探测，其操作方法与普通哈希表有所不同。</li></ol><ul><li><strong>插入元素</strong>：通过哈希函数计算桶索引，若发现桶内已有元素，则从冲突位置向后线性遍历（步长通常为 $1$ ），直至找到空桶，将元素插入其中。</li><li><strong>查找元素</strong>：若发现哈希冲突，则使用相同步长向后进行线性遍历，直到找到对应元素，返回 <code>value</code> 即可；如果遇到空桶，说明目标元素不在哈希表中，返回 <code>None</code> 。<br>下图展示了开放寻址（线性探测）哈希表的键值对分布。根据此哈希函数，最后两位相同的 <code>key</code> 都会被映射到相同的桶。而通过线性探测，它们被依次存储在该桶以及之下的桶中。<br><img src="/img/hash_table_linear_probing.png"><br>然而，<strong>线性探测容易产生“聚集现象”</strong>。具体来说，数组中连续被占用的位置越长，这些连续位置发生哈希冲突的可能性越大，从而进一步促使该位置的聚堆生长，形成恶性循环，最终导致增删查改操作效率劣化。<br>值得注意的是，<strong>我们不能在开放寻址哈希表中直接删除元素</strong>。这是因为删除元素会在数组内产生一个空桶 <code>None</code> ，而当查询元素时，线性探测到该空桶就会返回，因此在该空桶之下的元素都无法再被访问到，程序可能误判这些元素不存在.<br>为了解决该问题，我们可以采用<u>懒删除（lazy deletion）</u>机制：它不直接从哈希表中移除元素，<strong>而是利用一个常量 <code>TOMBSTONE</code> 来标记这个桶</strong>。在该机制下，<code>None</code> 和 <code>TOMBSTONE</code> 都代表空桶，都可以放置键值对。但不同的是，线性探测到 <code>TOMBSTONE</code> 时应该继续遍历，因为其之下可能还存在键值对。<br>然而，<strong>懒删除可能会加速哈希表的性能退化</strong>。这是因为每次删除操作都会产生一个删除标记，随着 <code>TOMBSTONE</code> 的增加，搜索时间也会增加，因为线性探测可能需要跳过多个 <code>TOMBSTONE</code> 才能找到目标元素。<br>为此，考虑在线性探测中记录遇到的首个 <code>TOMBSTONE</code> 的索引，并将搜索到的目标元素与该 <code>TOMBSTONE</code> 交换位置。这样做的好处是当每次查询或添加元素时，元素会被移动至距离理想位置（探测起始点）更近的桶，从而优化查询效率。</li></ul></blockquote><p>容易想到，哈希表容量越大，多个 key 被分配到同一个桶中的概率就越低，冲突就越少。因此，我们可以通过扩容哈希表来减少哈希冲突。</p><ul><li><h3 id="hash扩容"><a href="#hash扩容" class="headerlink" title="hash扩容"></a>hash扩容</h3></li></ul><p>类似于数组扩容，哈希表扩容需将所有键值对从原哈希表迁移至新哈希表，非常耗时；并且由于哈希表容量 <code>capacity</code> 改变，我们需要通过哈希函数来重新计算所有键值对的存储位置，这进一步增加了扩容过程的计算开销。为此，编程语言通常会预留足够大的哈希表容量，防止频繁扩容。<br>负载因子（load factor）是哈希表的一个重要概念，其定义为哈希表的元素数量除以桶数量，用于衡量哈希冲突的严重程度，<strong>也常作为哈希表扩容的触发条件</strong>。例如在 golang 中，当负载因子超过 $0.65$ 时，系统会将哈希表扩容至原先的 $2$ 倍。</p><ul><li><h2 id><a href="#" class="headerlink" title></a></h2></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;引&quot;&gt;&lt;a href=&quot;#引&quot; class=&quot;headerlink&quot; title=&quot;引&quot;&gt;&lt;/a&gt;引&lt;/h2&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;维基百科里这样定义 map：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In computer scienc</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    <category term="基础知识" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="hash" scheme="https://victorwong171.github.io/tags/hash/"/>
    
    <category term="map" scheme="https://victorwong171.github.io/tags/map/"/>
    
  </entry>
  
  <entry>
    <title>goroutine 排队、调度</title>
    <link href="https://victorwong171.github.io/2024/06/26/goroutine%E6%8E%92%E9%98%9F%E8%B0%83%E5%BA%A6/"/>
    <id>https://victorwong171.github.io/2024/06/26/goroutine%E6%8E%92%E9%98%9F%E8%B0%83%E5%BA%A6/</id>
    <published>2024-06-25T20:04:43.000Z</published>
    <updated>2024-06-25T12:59:32.038Z</updated>
    
    <content type="html"><![CDATA[<p>有以下代码：</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs golang"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">question</span><span class="hljs-params">(cnt <span class="hljs-type">int</span>)</span></span> &#123;<br>runtime.GOMAXPROCS(<span class="hljs-number">1</span>)<br><br><span class="hljs-keyword">var</span> wg sync.WaitGroup<br><br><span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt;= cnt; i++ &#123;<br>wg.Add(<span class="hljs-number">1</span>)<br><span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(n <span class="hljs-type">int</span>)</span></span> &#123;<br><span class="hljs-keyword">defer</span> wg.Done()<br><span class="hljs-built_in">println</span>(n)<br>&#125;(i)<br>&#125;<br><br>wg.Wait()<br>&#125;<br></code></pre></td></tr></table></figure><p>给出不同范围的cnt的输出是什么</p><p>当 <code>cnt</code> 小于等于 257 时，输出为序列</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell">cnt<br>1<br>2<br>3<br>...<br>cnt-1<br></code></pre></td></tr></table></figure><p>当 <code>cnt</code> 大于等于 258 时，输出为 </p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs shell">cnt<br>*<br>...<br>1<br>*<br>...<br>*<br>...<br>2<br>...<br></code></pre></td></tr></table></figure><p>首先，解释为什么第一个一般都是<code>cnt</code>, 在循环中<code>cnt</code>最后一个被加入到协程队列中，其实会被加到<code>runnext</code>中，在协程队列中第一个会执行<code>runnext</code>，然后会从<code>本地runq</code>中取协程，但是本地队列中一旦加入超过256个协程，就会切出128个到<code>全局runq</code>中，如此往复。同时每当<code>本地runq</code>执行了61次后就会从<code>全局runq</code>中获取一个进行执行，主要是防止<code>全局runq</code>中的协程被饿死。于是就会有如上输出。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;有以下代码：&lt;/p&gt;
&lt;figure class=&quot;highlight golang&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;</summary>
      
    
    
    
    <category term="面试题" scheme="https://victorwong171.github.io/categories/%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="groutine" scheme="https://victorwong171.github.io/tags/groutine/"/>
    
  </entry>
  
  <entry>
    <title>浏览器与服务器实时通信</title>
    <link href="https://victorwong171.github.io/2024/06/04/%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/"/>
    <id>https://victorwong171.github.io/2024/06/04/%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%9E%E6%97%B6%E9%80%9A%E4%BF%A1/</id>
    <published>2024-06-04T09:23:43.000Z</published>
    <updated>2024-06-04T05:19:50.210Z</updated>
    
    <content type="html"><![CDATA[<ul><li><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2></li><li><h3 id="Long-Polling"><a href="#Long-Polling" class="headerlink" title="Long-Polling"></a>Long-Polling</h3></li></ul><p>长轮询是一种模拟HTTP上服务器推送的技术，通过保持连接打开状态直到有新数据可用来减少不必要的网络流量。</p><ul><li><h3 id="WebSocket"><a href="#WebSocket" class="headerlink" title="WebSocket"></a>WebSocket</h3></li></ul><p>WebSocket提供了一个全双工的、长期存在的连接通道，适合需要低延迟的应用场景，如实时聊天、游戏和金融交易平台。</p><ul><li><h3 id="服务器发送事件（Server-Sent-Events-SSE）"><a href="#服务器发送事件（Server-Sent-Events-SSE）" class="headerlink" title="服务器发送事件（Server-Sent Events, SSE）"></a>服务器发送事件（Server-Sent Events, SSE）</h3></li></ul><p>服务器发送事件（Server-Sent Events, SSE）是一种从服务器单向推送更新到客户端的简单方法，适用于只需要服务器更新的场景，如实时新闻源、体育比分或需要实时更新的情况。SSE在连接断开时会自动重连，管理起来比WebSocket更简单。</p><ul><li><h3 id="WebTransport"><a href="#WebTransport" class="headerlink" title="WebTransport"></a>WebTransport</h3></li></ul><p>WebTransport是一个基于HTTP&#x2F;3 QUIC协议的新兴API，旨在实现高效、低延迟的客户端和服务器通信。它支持多流传输、可靠和不可靠的数据以及乱序发送数据，适用于高性能应用，如实时游戏和流媒体。然而，WebTransport目前仍处于工作草案阶段，支持度不广。</p><ul><li><h3 id="WebRTC"><a href="#WebRTC" class="headerlink" title="WebRTC"></a>WebRTC</h3></li></ul><p>WebRTC是一个用于浏览器和移动应用之间实时通信的开源项目，支持音频、视频和数据的点对点交换，但不直接与上述技术相比较，因为它主要用于客户端之间的交互，而非服务器客户端通信。</p><ul><li><h2 id="限制"><a href="#限制" class="headerlink" title="限制"></a>限制</h2></li><li>基础限制</li></ul><ol><li>只有WebSocket和WebTransport支持双向通信。</li><li>长轮询理论上可以双向通信，但不推荐，因为发送新数据到现有的长轮询连接会需要额外的HTTP请求。</li><li>SSE不支持向服务器发送数据，只能接收服务器的初始请求，且通常不能在HTTP正文中发送POST数据，但可以使用一些库（如eventsource polyfill）来扩展其功能。</li></ol><ul><li><p>浏览器的连接限制：<br>大多数现代浏览器每个域名允许六个连接，这限制了持续服务器到客户端消息传递方法的使用。尽管可以通过使用HTTP&#x2F;2或HTTP&#x2F;3来解决，这些协议使用单一连接并利用多路复用技术，但仍有默认的最大并发流设置（通常是100个并发流）。<br>在移动应用中，操作系统可能会关闭后台应用的开放连接，导致WebSocket等技术难以维持。在这种情况下，通常使用移动推送通知来更有效地发送数据。<br>企业环境中的代理和防火墙可能阻止非HTTP连接，使得WebSocket等技术难以集成，而SSE使用标准HTTP请求，可能更容易适应这些环境。</p></li><li><h2 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h2></li></ul><p>WebSocket、SSE和WebTransport在低延迟、高吞吐量和可扩展性方面表现相似，但WebTransport依赖于新的HTTP&#x2F;3协议，未来可能会有更多性能优化。<br>长轮询在延迟和吞吐量方面较差，因为它依赖于频繁建立新的HTTP连接。</p><ul><li><h2 id="场景"><a href="#场景" class="headerlink" title="场景"></a>场景</h2></li></ul><ol><li>SSE适合需要频繁服务器到客户端更新的简单场景，如新闻提要和实时数据流。</li><li>WebSocket适用于需要持续双向通信的应用，如游戏和聊天应用。</li><li>WebTransport是未来的趋势，但目前支持度不足，适用于需要高效多流通信的场景。</li><li>长轮询现在已被其他技术取代，不建议作为首选方案。</li></ol><p>在构建实时应用程序时，应考虑这些技术的局限性和适用场景，并确保处理可能的连接问题，如客户端重新连接时可能错过的事件，以及公司网络环境中可能遇到的代理和防火墙问题。</p><h2 id="ref："><a href="#ref：" class="headerlink" title="ref："></a>ref：</h2><p><a href="https://rxdb.info/articles/websockets-sse-polling-webrtc-webtransport.html">WebSockets vs Server-Sent-Events vs Long-Polling vs WebRTC vs WebTransport</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;ul&gt;
&lt;li&gt;&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;/li&gt;
&lt;li&gt;&lt;h3 id=&quot;Long-Polling&quot;&gt;&lt;a href=&quot;#Long-Polling&quot; class=&quot;h</summary>
      
    
    
    
    <category term="技术方案" scheme="https://victorwong171.github.io/categories/%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88/"/>
    
    
    <category term="WebSocket" scheme="https://victorwong171.github.io/tags/WebSocket/"/>
    
    <category term="SSE" scheme="https://victorwong171.github.io/tags/SSE/"/>
    
    <category term="WebTransport" scheme="https://victorwong171.github.io/tags/WebTransport/"/>
    
    <category term="Long-Polling" scheme="https://victorwong171.github.io/tags/Long-Polling/"/>
    
    <category term="WebRTC" scheme="https://victorwong171.github.io/tags/WebRTC/"/>
    
  </entry>
  
  <entry>
    <title>在github发布自己的依赖</title>
    <link href="https://victorwong171.github.io/2024/05/30/%E5%9C%A8github%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BE%9D%E8%B5%96/"/>
    <id>https://victorwong171.github.io/2024/05/30/%E5%9C%A8github%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BE%9D%E8%B5%96/</id>
    <published>2024-05-30T09:27:10.000Z</published>
    <updated>2024-05-30T01:43:41.448Z</updated>
    
    <content type="html"><![CDATA[<p>平时工作积累了一些用起来比较顺手的函数，数据结构的封装，整理一下，发布成一个包。</p><h3 id="1-创建仓库"><a href="#1-创建仓库" class="headerlink" title="1. 创建仓库"></a>1. 创建仓库</h3><p>创建一个github仓库，根据自己的情况选择公开或者私有，再根据功能范围起一个库名。</p><h3 id="2-初始化"><a href="#2-初始化" class="headerlink" title="2. 初始化"></a>2. 初始化</h3><p>将项目拉到本地，</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">go mod init &lt;你的库名&gt;<br></code></pre></td></tr></table></figure><h3 id="3-添加库功能"><a href="#3-添加库功能" class="headerlink" title="3. 添加库功能"></a>3. 添加库功能</h3><p>把需要发布的功能函数添加到项目中，其中不希望被引用的功能，可以放到项目下<code>internal/</code>路径中</p><h3 id="4-push"><a href="#4-push" class="headerlink" title="4. push"></a>4. push</h3><p>将变更完的项目推送到github</p><h3 id="5-引用"><a href="#5-引用" class="headerlink" title="5. 引用"></a>5. 引用</h3><p>在其他项目中，执行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">go get -v github.com/&lt;你的github用户名&gt;/&lt;你的库名&gt;<br></code></pre></td></tr></table></figure><p>然后就结束啦！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;平时工作积累了一些用起来比较顺手的函数，数据结构的封装，整理一下，发布成一个包。&lt;/p&gt;
&lt;h3 id=&quot;1-创建仓库&quot;&gt;&lt;a href=&quot;#1-创建仓库&quot; class=&quot;headerlink&quot; title=&quot;1. 创建仓库&quot;&gt;&lt;/a&gt;1. 创建仓库&lt;/h3&gt;&lt;p&gt;创建一个</summary>
      
    
    
    
    <category term="教程" scheme="https://victorwong171.github.io/categories/%E6%95%99%E7%A8%8B/"/>
    
    
    <category term="golang" scheme="https://victorwong171.github.io/tags/golang/"/>
    
    <category term="github" scheme="https://victorwong171.github.io/tags/github/"/>
    
    <category term="go modules" scheme="https://victorwong171.github.io/tags/go-modules/"/>
    
  </entry>
  
  <entry>
    <title>linux串口通信 接收信息不完整 读取不全</title>
    <link href="https://victorwong171.github.io/2024/05/17/linux%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1%E6%8E%A5%E6%94%B6%E4%BF%A1%E6%81%AF%E4%B8%8D%E5%AE%8C%E6%95%B4/"/>
    <id>https://victorwong171.github.io/2024/05/17/linux%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1%E6%8E%A5%E6%94%B6%E4%BF%A1%E6%81%AF%E4%B8%8D%E5%AE%8C%E6%95%B4/</id>
    <published>2024-05-16T17:50:10.000Z</published>
    <updated>2024-05-16T09:50:56.877Z</updated>
    
    <content type="html"><![CDATA[<p>类似这种 ready.o是我用来读取串口信息的一个程序 执行结果如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs shell">[root@localhost testPlc]# ./ready.o <br>0       02<br>1       30<br>2       30<br>3       46<br>4       46<br>5       31<br>6       03<br>7       32<br>8       bd<br></code></pre></td></tr></table></figure><p>按照通信协议，串口应返回的的是</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs shell">0       02<br>1       30<br>2       30<br>3       46<br>4       46<br>5       31<br>6       03<br>7       32<br>8       31<br>9       38<br></code></pre></td></tr></table></figure><p>并且其他几个指令，也是与通信协议所要求的返回的位数不同，并且最后一位也有是错误的，使用串口助手有没有问题。所以推测是接收区只有8位，然后代码里的接收buffer设置为128，所以应该是底层的原因，所以采用了select进行等待，最终实现了完整读取串口信息<br>代码示例：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">SetBench</span><span class="hljs-params">(<span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> *msg, <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> *recv)</span> &#123;<br>    fd_set fs_read;<br>    <span class="hljs-type">int</span> fd = nFd;<br><span class="hljs-comment">//nFd是串口句柄</span><br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">char</span> buf[<span class="hljs-number">16</span>] = &#123;<span class="hljs-number">0</span>&#125;;<br><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">timeval</span> <span class="hljs-title">time</span>;</span><br>    FD_ZERO(&amp;fs_read);<br>    FD_SET(fd, &amp;fs_read);<br><br>    time.tv_sec = <span class="hljs-number">2</span>;<br>    time.tv_usec = <span class="hljs-number">0</span>;<br>    write(fd, msg, <span class="hljs-built_in">strlen</span>(msg));<br><br>    <span class="hljs-keyword">while</span> (select(fd + <span class="hljs-number">1</span>, &amp;fs_read, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, &amp;time) &gt; <span class="hljs-number">0</span>) &#123;<br>        len = read(fd, buf, <span class="hljs-keyword">sizeof</span>(recv));<br>        <span class="hljs-keyword">if</span> (<span class="hljs-number">8</span> == len) &#123;<br>            <span class="hljs-built_in">strncpy</span>(recv + count, buf, <span class="hljs-number">8</span>);<br>            count += <span class="hljs-number">8</span>;<br>        &#125;<br>        <span class="hljs-keyword">if</span> (len &gt; <span class="hljs-number">0</span> &amp;&amp; len &lt; <span class="hljs-number">8</span>) &#123;<br>            <span class="hljs-built_in">strncpy</span>(recv + count, buf, len);<br>            count += len;<br>        &#125;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;类似这种 ready.o是我用来读取串口信息的一个程序 执行结果如下：&lt;/p&gt;
&lt;figure class=&quot;highlight shell&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br</summary>
      
    
    
    
    <category term="异常" scheme="https://victorwong171.github.io/categories/%E5%BC%82%E5%B8%B8/"/>
    
    
    <category term="串口通信" scheme="https://victorwong171.github.io/tags/%E4%B8%B2%E5%8F%A3%E9%80%9A%E4%BF%A1/"/>
    
    <category term="linux" scheme="https://victorwong171.github.io/tags/linux/"/>
    
    <category term="c++" scheme="https://victorwong171.github.io/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>sed: -e expression #1, char 0: no previous regular expression</title>
    <link href="https://victorwong171.github.io/2024/05/16/sed%E8%A1%A8%E8%BE%BE%E5%BC%8F%E9%94%99%E8%AF%AF/"/>
    <id>https://victorwong171.github.io/2024/05/16/sed%E8%A1%A8%E8%BE%BE%E5%BC%8F%E9%94%99%E8%AF%AF/</id>
    <published>2024-05-16T15:17:10.000Z</published>
    <updated>2024-05-16T09:51:02.885Z</updated>
    
    <content type="html"><![CDATA[<p>执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sed -i -e <span class="hljs-string">&quot;s/<span class="hljs-variable">$ori</span>/<span class="hljs-variable">$curr</span>/g&quot;</span> <span class="hljs-variable">$CONF</span><br></code></pre></td></tr></table></figure><p>报错 <code>sed: -e expression #1, char 0: no previous regular expression</code></p><p>是因为 <code>$ori</code> 为空</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;执行&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;</summary>
      
    
    
    
    <category term="异常" scheme="https://victorwong171.github.io/categories/%E5%BC%82%E5%B8%B8/"/>
    
    
    <category term="linux" scheme="https://victorwong171.github.io/tags/linux/"/>
    
    <category term="sed" scheme="https://victorwong171.github.io/tags/sed/"/>
    
    <category term="shell" scheme="https://victorwong171.github.io/tags/shell/"/>
    
  </entry>
  
</feed>
