https://geeknote.net/mark24
Mark24
MIX CODER
https://geeknote-storage.oss-cn-hongkong.aliyuncs.com/7r7qqgrhsz3jvrfl57v25g3eb7ch?x-oss-process=image%2Fresize%2Cm_fill%2Cw_160%2Ch_160
2022-10-28T15:02:48Z
mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/1449
2022-08-16T11:18:41Z
2022-10-28T15:02:48Z
代码会说话之一个脚本帮你检查项目 996 状态
<h2>
<a id="%E9%A9%AC%E4%B8%8A%E8%A6%81%E8%BF%9B%E6%96%B0%E7%BB%84%E4%BA%86%EF%BC%8C%E4%B8%80%E7%9C%8B%E4%BB%96%E4%BB%AC%E7%9A%84%E4%BB%93%E5%BA%93%E7%9C%BC%E6%B3%AA%E7%95%99%E4%B8%8B%E6%9D%A5" href="#%E9%A9%AC%E4%B8%8A%E8%A6%81%E8%BF%9B%E6%96%B0%E7%BB%84%E4%BA%86%EF%BC%8C%E4%B8%80%E7%9C%8B%E4%BB%96%E4%BB%AC%E7%9A%84%E4%BB%93%E5%BA%93%E7%9C%BC%E6%B3%AA%E7%95%99%E4%B8%8B%E6%9D%A5" class="anchor"></a>马上要进新组了,一看他们的仓库眼泪留下来</h2>
<p>项目地址 <a href="https://github.com/Mark24Code/check_996">Mark24Code/check_996</a></p>
<hr>
<h1>
<a id="Check+996" href="#Check+996" class="anchor"></a>Check 996</h1>
<p>帮助你检查项目 996 状态. 😎</p>
<pre class="highlight"><code>Usage: check_996.rb [options]
-s, --start WORK_START_TIME start job time e.g. 10:00:00
-e, --end WORK_END_TIME end job time e.g. 18:00:00
-g, --git-log GIT_LOG_CMD use git log command, default is `git log --all`
-f, --filter FILTER time range filter e.g. last_[day|week|month|year] last_5_[day|week|month|year] '2022-01-01 08:10:00,2022-10-01 08:10:00'
-v, --version version
</code></pre>
<h1>
<a id="%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E" href="#%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E" class="anchor"></a>使用说明</h1>
<h2>
<a id="%E4%BE%9D%E8%B5%96%E9%A1%B9%E7%9B%AE" href="#%E4%BE%9D%E8%B5%96%E9%A1%B9%E7%9B%AE" class="anchor"></a>依赖项目</h2>
<ul>
<li>确保你有 <code>ruby 2.7+</code>
</li>
<li>如果有 <code>curl</code> 可以帮助远程执行</li>
</ul>
<h3>
<a id="%E6%AD%A5%E9%AA%A4%E4%B8%80%3A" href="#%E6%AD%A5%E9%AA%A4%E4%B8%80%3A" class="anchor"></a>步骤一:</h3>
<p>终端,进入你想统计的 git 仓库</p>
<pre class="highlight"><code class="language-bash"><span class="nb">cd</span> </path/to/your/git_repo>
</code></pre>
<h3>
<a id="%E6%AD%A5%E9%AA%A4%E4%BA%8C" href="#%E6%AD%A5%E9%AA%A4%E4%BA%8C" class="anchor"></a>步骤二</h3>
<p>终端使用如下命令</p>
<ul>
<li>curl support</li>
</ul>
<pre class="highlight"><code class="language-bash">ruby <span class="nt">-e</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Mark24Code/check_996/main/check_996.rb<span class="si">)</span><span class="s2">"</span>
</code></pre>
<h2>
<a id="%E6%9B%B4%E5%A4%9A%E5%BB%BA%E8%AE%AE%EF%BC%9A" href="#%E6%9B%B4%E5%A4%9A%E5%BB%BA%E8%AE%AE%EF%BC%9A" class="anchor"></a>更多建议:</h2>
<p>脚本下载在本地可以直接使用参数,远程执行也可以使用参数,使用 <code>--</code> 分隔参数:</p>
<pre class="highlight"><code> <script> -- -s 10:30 -e 19:30
</code></pre>
<p>例如自定义理论上的工作时间:</p>
<pre class="highlight"><code class="language-bash">ruby <span class="nt">-e</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Mark24Code/check_996/main/check_996.rb<span class="si">)</span><span class="s2">"</span> <span class="nt">--</span> <span class="nt">-s</span> 10:30 <span class="nt">-e</span> 19:30
</code></pre>
<h2>
<a id="%E9%A2%9D%E5%A4%96%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E" href="#%E9%A2%9D%E5%A4%96%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E" class="anchor"></a>额外参数说明</h2>
<h4>
<a id="%E8%BF%87%E6%BB%A4%E5%99%A8" href="#%E8%BF%87%E6%BB%A4%E5%99%A8" class="anchor"></a>过滤器</h4>
<p>如果我不想对全量 git 进行计算,只关心一段时间,可以使用 -f 参数</p>
<p>提供人性化语义化参数</p>
<pre class="highlight"><code class="language-bash"><span class="nt">-f</span>, <span class="nt">--filter</span> FILTER <span class="nb">time </span>range filter e.g. last_[day|week|month|year] last_5_[day|week|month|year] <span class="s1">'2022-01-01 08:10:00,2022-10-01 08:10:00'</span>
</code></pre>
<p>例如</p>
<pre class="highlight"><code class="language-bash"><span class="nt">-f</span> last_week
<span class="nt">-f</span> last_month
<span class="nt">-f</span> last_25_days
<span class="nt">-f</span> <span class="s1">'2022-01-01 08:10:00,2022-10-01 08:10:00'</span>
</code></pre>
<h3>
<a id="%E7%BB%9F%E8%AE%A1%E6%96%B9%E5%BC%8F" href="#%E7%BB%9F%E8%AE%A1%E6%96%B9%E5%BC%8F" class="anchor"></a>统计方式</h3>
<p>默认使用 <code>git log --all</code> 会在当前分支进入可触达分支,也可以自己定义, 但是检查必须是 <code>git log xxxx</code></p>
<pre class="highlight"><code class="language-bash"><span class="nt">-g</span>, <span class="nt">--git-log</span> GIT_LOG_CMD use git log <span class="nb">command</span>, default is <span class="sb">`</span>git log <span class="nt">--all</span><span class="sb">`</span>
</code></pre>
马上要进新组了,一看他们的仓库眼泪留下来
项目地址 Mark24Code/check_996
Check 996
帮助你检查项目 996 状态. 😎
Usage: check_996.rb...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/1448
2022-08-16T04:57:26Z
2022-10-28T15:02:48Z
用 100 行 Ruby 代码模拟 JavaScript 的 Eventloop
<h2>
<a id="%E5%89%8D%E8%A8%80" href="#%E5%89%8D%E8%A8%80" class="anchor"></a>前言</h2>
<p>大家好,我是Mark24</p>
<ul>
<li>代码仓库: <a href="https://github.com/Mark24Code/rb_simulate_eventloop">Mark24Code/rb_simulate_eventloop</a>
</li>
<li>[本文博客地址] (<a href="https://mark24code.github.io/ruby/2022/08/11/%E7%94%A8100%E8%A1%8CRuby%E4%BB%A3%E7%A0%81%E6%A8%A1%E6%8B%9FJavaScript%E7%9A%84Eventloop.html?source=juejin">https://mark24code.github.io/ruby/2022/08/11/%E7%94%A8100%E8%A1%8CRuby%E4%BB%A3%E7%A0%81%E6%A8%A1%E6%8B%9FJavaScript%E7%9A%84Eventloop.html?source=juejin</a>)</li>
<li><a href="https://ruby-china.org/topics/42590">RubyChina同话题讨论</a></li>
</ul>
<h2>
<a id="%E8%83%8C%E6%99%AF" href="#%E8%83%8C%E6%99%AF" class="anchor"></a>背景</h2>
<p>我们都知道 JavaScript 是单线程的。</p>
<p>今天看到一个有趣的<a href="https://www.v2ex.com/t/871848#reply95">帖子 www.v2ex.com/t/871848</a>,主要是争论JavaScript的优缺点。我看到这个评论觉得很有意思:</p>
<pre class="highlight"><code>@qrobot:
....省略....
多线程下会消耗以下资源
1. 切换页表全局目录
2. 切换内核态堆栈
3. 切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)
ip(instruction pointer):指向当前执行指令的下一条指令
bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
cr3:页目录基址寄存器,保存页目录表的物理地址
......
4. 刷新 TLB
5. 系统调度器的代码执行
....省略.....
</code></pre>
<p>这位同学列举了多线程切换的时候发生了什么。
这样给了一种很直观的感受,就是多线程切换的时候发生了很多事情,实际上会比单线程(只需要切换函数上下文)要消耗点更多的资源。</p>
<p>实际上凡是交互的软件,最终都是 单线程模型 + 事件驱动辅助。</p>
<p>从熟悉的浏览器、游戏、应用程序……都是如此。</p>
<p>也有多线程实现的。这里<a href="https://news.ycombinator.com/item?id=10490627">Multithreaded toolkits: A failed dream? (2004) </a> 有很多讨论。</p>
<p>实际上单线程模型是最后的胜出者。</p>
<p>JavaScript 内部单线程处理任务,主要是有一个 EventLoop 单线程的循环实现。</p>
<p>我们可以通过 JavaScript 的表现,反推实现一下 EventLoop。</p>
<h1>
<a id="EventLoop+%E5%AE%9E%E7%8E%B0" href="#EventLoop+%E5%AE%9E%E7%8E%B0" class="anchor"></a>EventLoop 实现</h1>
<h3>
<a id="JavaScript+%E7%9A%84%E8%A1%8C%E4%B8%BA" href="#JavaScript+%E7%9A%84%E8%A1%8C%E4%B8%BA" class="anchor"></a>JavaScript 的行为</h3>
<p>我们知道 <code>setTimeout</code> 在 JavaScript 中用来推迟任务。实际上自从 Promise 出现之后,渐渐有两个概念出现在大家的视野里。</p>
<ul>
<li>Macrotask(宏任务)</li>
<li>Microtask (微任务)</li>
</ul>
<p>setTimeout属于宏任务,而 promise的then回调属于微任务。</p>
<p>还有一个就是 JavaScript 在第一次同步执行代码的时候,是宏任务。</p>
<p>EventLoop的表现是,除了第一次执行结束之后,如果有更高优先级的 微任务总是先执行微任务,然后再执行宏任务。</p>
<p>setTimeout 是一个定时器,很特别的是他在会在计时器线程工作,运行时间之后,回调函数会被插入到 宏任务中执行。计时器线程其实不是 JavaScript虚拟的一部分,他是浏览器的部分。</p>
<h3>
<a id="Ruby+%E6%A8%A1%E6%8B%9F" href="#Ruby+%E6%A8%A1%E6%8B%9F" class="anchor"></a>Ruby 模拟</h3>
<p>JavaScript 是单线程的。 Ruby是支持多线程的。我们可以用 Ruby 模拟一个 单线程的核心,和单独的计时器线程,这都是很轻松的事情。</p>
<p>其实我们听到了这个行为 —— 花1分钟大概能想到,EventLoop 的工作模型</p>
<ul>
<li>首先他是一个主循环,这样才能所谓的单线程</li>
<li>其次,既然有两种任务,应该是两种队列</li>
<li>再者,如果第一次同步代码是宏任务,多半可以代码任务也丢到队列里</li>
</ul>
<p>我们可以用数组当做 队列。但是 由于存在时间线程,还得用 Thread#Queue 有保障一点。</p>
<p>大概的模型可以画出来,想这个样:</p>
<h3>
<a id="Eventloop+Model" href="#Eventloop+Model" class="anchor"></a>Eventloop Model</h3>
<pre class="highlight"><code>(start)
|
init (e.g create TimerThread )
|
sync task (e.g read & run code)
|
|
------------------>
| | -------------
| macro_task --- add timer task --> | TimerThread |
| (Eventloop) | <-- insertjob result --- -------------
| |
| micro_task
| |
| |
<-----------------
|
|
(end)
</code></pre>
<ul>
<li>完整的代码仓库 <a href="https://github.com/Mark24Code/rb_simulate_eventloop">Mark24Code/rb_simulate_eventloop</a>
</li>
</ul>
<p>然后我们大概用100行不到就可以实现如下:</p>
<h4>
<a id="%E9%9C%80%E8%A6%81%E8%AF%B4%E6%98%8E%E7%9A%84%E6%98%AF%3A" href="#%E9%9C%80%E8%A6%81%E8%AF%B4%E6%98%8E%E7%9A%84%E6%98%AF%3A" class="anchor"></a>需要说明的是:</h4>
<ol>
<li>
<p>settimeout 不要用每一个新的线程来模拟,因为一旦多线程,涉及到抢占式回调,其实返回的时间不确定。你的结果是不稳定的。
我们需要单独实现一个计时器线程。</p>
</li>
<li>
<p>我们通过行为封装,把两边函数写法对照,这样可以复制</p>
</li>
</ol>
<p>运行看结果</p>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60bb8e9548554970ab35219e17dbeae0~tplv-k3u1fbpfcp-zoom-1.image" alt="result_example"></p>
<h2>
<a id="%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0" href="#%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0" class="anchor"></a>具体实现</h2>
<pre class="highlight"><code class="language-ruby"><span class="c1"># https://github.com/Mark24Code/rb_simulate_eventloop</span>
<span class="nb">require</span> <span class="s1">'thread'</span>
<span class="k">class</span> <span class="nc">EventLoop</span>
<span class="nb">attr_accessor</span> <span class="ss">:macro_queue</span><span class="p">,</span> <span class="ss">:micro_queue</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@running</span> <span class="o">=</span> <span class="kp">true</span>
<span class="vi">@macro_queue</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span>
<span class="vi">@micro_queue</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span>
<span class="vi">@time_thr_task_queue</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span>
<span class="vi">@timer</span> <span class="o">=</span> <span class="no">Timer</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="vi">@time_thr_task_queue</span><span class="p">,</span> <span class="vi">@macro_queue</span><span class="p">)</span>
<span class="c1"># 计时线程,是一个同步队列</span>
<span class="c1"># 会把定时任务结果塞回宏队列</span>
<span class="vi">@timer_thx</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="vi">@timer</span><span class="p">.</span><span class="nf">run</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">before_loop_sync_tasks</span>
<span class="c1"># do sth setting</span>
<span class="vi">@first_task</span><span class="p">.</span><span class="nf">call</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">task</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="c1"># 这里放置第一次同步任务</span>
<span class="c1"># </span>
<span class="c1"># 外部书写的代码,模拟读取js</span>
<span class="c1"># 提供内部的api</span>
<span class="vi">@first_task</span> <span class="o">=</span> <span class="o">-></span> <span class="p">()</span> <span class="p">{</span> <span class="nb">instance_eval</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">after_loop</span>
<span class="nb">puts</span> <span class="s2">"[after_loop] eventloop is quit :D"</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">macro_queue_works</span>
<span class="k">while</span> <span class="o">!</span><span class="vi">@macro_queue</span><span class="p">.</span><span class="nf">empty?</span>
<span class="n">job</span> <span class="o">=</span> <span class="vi">@macro_queue</span><span class="p">.</span><span class="nf">shift</span>
<span class="n">job</span><span class="p">.</span><span class="nf">call</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">micro_queue_works</span>
<span class="k">while</span> <span class="o">!</span><span class="vi">@micro_queue</span><span class="p">.</span><span class="nf">empty?</span>
<span class="n">job</span> <span class="o">=</span> <span class="vi">@micro_queue</span><span class="p">.</span><span class="nf">shift</span>
<span class="n">job</span><span class="p">.</span><span class="nf">call</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">start</span>
<span class="k">begin</span>
<span class="n">before_loop_sync_tasks</span>
<span class="k">while</span> <span class="vi">@running</span>
<span class="n">macro_queue_works</span>
<span class="n">micro_queue_works</span>
<span class="c1"># avoid CPU 100%</span>
<span class="nb">sleep</span> <span class="mf">0.1</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="n">after_loop</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># dsl public api</span>
<span class="c1"># inner api</span>
<span class="k">def</span> <span class="nf">macro_task</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="vi">@macro_queue</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">block</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">micro_task</span><span class="p">(</span><span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="vi">@micro_queue</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">block</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">settimeout</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="c1"># 模拟定时器线程</span>
<span class="k">if</span> <span class="n">time</span> <span class="o">==</span> <span class="mi">0</span>
<span class="n">time</span> <span class="o">=</span> <span class="mf">0.1</span>
<span class="k">end</span>
<span class="c1"># 方案1: 用独立分散的线程模拟存在问题</span>
<span class="c1"># 抢占的返回顺序不是固定的</span>
<span class="c1"># t = Thread.new do</span>
<span class="c1"># sleep time</span>
<span class="c1"># @micro_queue.push(block)</span>
<span class="c1"># end</span>
<span class="c1">## !!! 这里一定不能阻塞,一旦阻塞就不是单线程模型</span>
<span class="c1">## 有外循环控制不会结束</span>
<span class="c1"># t.join</span>
<span class="c1"># 方案2: 时间线程也需要单独模拟</span>
<span class="c1"># 建立一个时间任务</span>
<span class="vi">@time_thr_task_queue</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span>
<span class="ss">sleep_time: </span><span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">+</span> <span class="n">time</span><span class="p">,</span>
<span class="ss">job: </span><span class="o">-></span> <span class="p">()</span> <span class="p">{</span> <span class="vi">@micro_queue</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">block</span><span class="p">)</span> <span class="p">}</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Timer</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">task_queue</span><span class="p">,</span> <span class="n">macro_queue</span><span class="p">)</span>
<span class="vi">@task_queue</span> <span class="o">=</span> <span class="n">task_queue</span>
<span class="vi">@macro_queue</span> <span class="o">=</span> <span class="n">macro_queue</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">run</span>
<span class="k">while</span> <span class="p">(</span><span class="n">task</span> <span class="o">=</span> <span class="vi">@task_queue</span><span class="p">.</span><span class="nf">shift</span><span class="p">)</span>
<span class="n">sleep_time</span> <span class="o">=</span> <span class="n">task</span><span class="p">[</span><span class="ss">:sleep_time</span><span class="p">]</span>
<span class="k">if</span> <span class="n">sleep_time</span> <span class="o">>=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">to_i</span>
<span class="vi">@macro_queue</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">task</span><span class="p">[</span><span class="ss">:job</span><span class="p">])</span>
<span class="k">else</span>
<span class="vi">@task_queue</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<h1>
<a id="%E6%80%BB%E7%BB%93" href="#%E6%80%BB%E7%BB%93" class="anchor"></a>总结</h1>
<p>选择单线程的原因是因为</p>
<ul>
<li>结果运行的更快</li>
<li>无上下文负担</li>
<li>任务队列清晰而又简单</li>
<li>非IO密级任务,可以跑满CPU</li>
</ul>
<p>Nginx、Redis 内部也实现了单线程模型,来应对大量的请求,提高并发。</p>
<p>现在我们大概知道了,浏览器、应用、app、图形界面、游戏……</p>
<p>他们的背后大概是什么样子。 破除神秘感 +1 :D</p>
<ul>
<li>[本文博客地址] (<a href="https://mark24code.github.io/ruby/2022/08/11/%E7%94%A8100%E8%A1%8CRuby%E4%BB%A3%E7%A0%81%E6%A8%A1%E6%8B%9FJavaScript%E7%9A%84Eventloop.html?source=juejin">https://mark24code.github.io/ruby/2022/08/11/%E7%94%A8100%E8%A1%8CRuby%E4%BB%A3%E7%A0%81%E6%A8%A1%E6%8B%9FJavaScript%E7%9A%84Eventloop.html?source=juejin</a>)</li>
</ul>
前言
大家好,我是Mark24
代码仓库: Mark24Code/rb_simulate_eventloop
[本文博客地址] (https://mark24code.github.io/...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/1447
2022-08-16T04:56:16Z
2022-10-28T15:02:31Z
管窥蠡测从思考游戏到实现 2048
<p>大家好,我是Mark24,可以叫我Mark</p>
<p><a href="https://github.com/Mark24Code">Github Mark24Code</a></p>
<p><a href="https://mark24code.github.io/ruby/2022/07/26/%E8%BF%99%E5%BE%97%E4%BB%8E2048%E8%AF%B4%E8%B5%B7.html">我的博客</a></p>
<p><a href="https://ruby-china.org/topics/42553">RubyChina同话题</a></p>
<p><a href="https://www.v2ex.com/t/868773">V2EX同话题</a></p>
<h1>
<a id="%E5%89%8D%E8%A8%80" href="#%E5%89%8D%E8%A8%80" class="anchor"></a>前言</h1>
<p>本文比较啰嗦,更倾向于是自言自语。不过我写完回顾,这更像是这段时间,自由思考的总结 :P</p>
<p>不过我不是游戏领域的人,这部分都是业余摸鱼思考的记录,如果有勘误,请与我联系,非常乐意交流。</p>
<p>文章可能需要30分钟。</p>
<p>主要涉及的主题:</p>
<ul>
<li>游戏之难</li>
<li>游戏基本构成</li>
<li>游戏引擎</li>
<li>游戏与交互程序</li>
<li>框架和库思考</li>
<li>语言是否是游戏的瓶颈</li>
<li>双缓冲模式</li>
<li>线程和协程的讨论</li>
<li>线程队列&中断</li>
</ul>
<p>使用Ruby实现demo。</p>
<h1>
<a id="rb2048" href="#rb2048" class="anchor"></a>rb2048</h1>
<ul>
<li>项目地址: <a href="https://github.com/Mark24Code/rb2048">rb2048</a>
</li>
<li>gem地址: <a href="https://gems.ruby-china.com/gems/rb2048">rb2048</a>
</li>
</ul>
<p>项目安装: <code>gem install rb2048</code></p>
<h4>
<a id="%E8%BF%9B%E5%85%A5%E6%B8%B8%E6%88%8F" href="#%E8%BF%9B%E5%85%A5%E6%B8%B8%E6%88%8F" class="anchor"></a>进入游戏</h4>
<p>帮助信息: <code>rb2048 --help</code></p>
<pre class="highlight"><code class="language-ruby"><span class="no">Usage</span><span class="p">:</span> <span class="n">rb2048</span> <span class="p">[</span><span class="n">options</span><span class="p">]</span>
<span class="o">--</span><span class="n">version</span> <span class="n">verison</span>
<span class="o">--</span><span class="n">size</span> <span class="no">SIZE</span> <span class="no">Size</span> <span class="n">of</span> <span class="ss">board: </span><span class="mi">4</span><span class="o">-</span><span class="mi">10</span>
<span class="o">--</span><span class="n">level</span> <span class="no">LEVEL</span> <span class="no">Hard</span> <span class="no">Level</span> <span class="mi">2</span><span class="o">-</span><span class="mi">5</span>
</code></pre>
<p>开始游戏 <code>rb2048</code></p>
<pre class="highlight"><code> -- Ruby 2048 --
-------------------------------------
| 16 | 16 | 2 | 16 |
-------------------------------------
| 0 | 0 | 0 | 0 |
-------------------------------------
| 0 | 0 | 0 | 2 |
-------------------------------------
| 0 | 0 | 0 | 0 |
-------------------------------------
Score: 16 You:UP
Control: W(↑) A(←) S(↓) D(→) Q(quit) R(Restart)
</code></pre>
<p>升级难度 <code>rb2048 --size=10 --level=5</code></p>
<pre class="highlight"><code> -- Ruby 2048 --
-----------------------------------------------------------------------
| 8 | 16 | 0 | 0 | 0 | 0 | 0 | 2 | 0 | 0 |
-----------------------------------------------------------------------
| 0 | 16 | 0 | 16 | 0 | 8 | 0 | 0 | 0 | 0 |
-----------------------------------------------------------------------
| 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 16 | 8 |
-----------------------------------------------------------------------
| 0 | 16 | 0 | 8 | 0 | 0 | 0 | 0 | 0 | 2 |
-----------------------------------------------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-----------------------------------------------------------------------
| 0 | 8 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-----------------------------------------------------------------------
| 8 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 0 | 0 |
-----------------------------------------------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
-----------------------------------------------------------------------
| 0 | 0 | 0 | 4 | 0 | 0 | 0 | 0 | 0 | 0 |
-----------------------------------------------------------------------
| 0 | 4 | 0 | 0 | 4 | 8 | 0 | 0 | 0 | 16 |
-----------------------------------------------------------------------
Score: 0
Control: W(↑) A(←) S(↓) D(→) Q(quit) R(Restart)
</code></pre>
<h1>
<a id="%E8%83%8C%E6%99%AF" href="#%E8%83%8C%E6%99%AF" class="anchor"></a>背景</h1>
<p>我觉得命令行的程序比较赛博朋克,一直想做个命令行的交互程序。
目前在游戏公司,虽然我不是游戏工程师,但是接触了一些游戏行业的优秀小伙伴,我也忍不住思考关于游戏的主题。</p>
<p>我想做的命令行交互式程序,其实和游戏的思想内核是一致的。一拍即合。</p>
<p>我以前做过一点点研究。记录了一些笔记。关于Ruby中如何实现交互式命令行程序。
本文也是建立在这个基础之上。</p>
<ul>
<li><a href="https://mark24code.github.io/cli/tui/2022/03/01/%E5%91%BD%E4%BB%A4%E8%A1%8C%E7%95%8C%E9%9D%A2TUI&CLI%E7%9B%B8%E5%85%B3%E6%94%B6%E9%9B%86.html">命令行界面TUI&CLI相关收集</a></li>
<li><a href="https://mark24code.github.io/%E6%B8%B8%E6%88%8F/2022/03/01/%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8E%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E%E5%88%9D%E6%8E%A2.html">命令行与游戏引擎初探</a></li>
</ul>
<p>用最简单的方式实现了一个 [贪吃蛇]</p>
<ul>
<li>Github: <a href="https://github.com/Mark24Code/snakes">https://github.com/Mark24Code/snakes</a>
</li>
<li>Gem: <a href="https://gems.ruby-china.com/gems/snakes">https://gems.ruby-china.com/gems/snakes</a>
</li>
</ul>
<h1>
<a id="rb2048%E5%BF%83%E8%B7%AF%E5%8E%86%E7%A8%8B" href="#rb2048%E5%BF%83%E8%B7%AF%E5%8E%86%E7%A8%8B" class="anchor"></a>rb2048心路历程</h1>
<h2>
<a id="rb2048+%E4%BA%AE%E7%82%B9" href="#rb2048+%E4%BA%AE%E7%82%B9" class="anchor"></a>rb2048 亮点</h2>
<p>rb2048有趣的地方在于,在设计的时候,没有简单实现了之。毕竟有太多2048了,不差这一个。</p>
<p>对于我不是完成一个任务。由于最近两天关注于线程的使用,于是我把线程方面的使用加入到rb2048。这算是一个实验性的例子。验证我的想法:</p>
<p>rb2048将:</p>
<ul>
<li>用户I/O</li>
<li>游戏数据计算</li>
<li>游戏渲染</li>
</ul>
<p>这三部分分别用单独的线程实现,用队列通信。麻雀虽小,五脏俱全。虽然粗糙,但是代表了游戏引擎典型的设计思路。
(虽然我了解的不多)</p>
<h2>
<a id="%E8%AE%A4%E7%9F%A5%E5%8F%98%E5%8C%96" href="#%E8%AE%A4%E7%9F%A5%E5%8F%98%E5%8C%96" class="anchor"></a>认知变化</h2>
<p>简单说说我最近的思考吧:</p>
<p>1)对于计算机不同领域认识发生了变化</p>
<p>以前会觉得:游戏是游戏,web是web,语言是语言,元编程就是元编程……也许还有很多概念,但是渐渐现在觉得无非是一件事 —— 编程罢了。</p>
<p>随着看到思考的东西逐渐变多,很多计算机领域的问题,在我的角度觉得都一样。</p>
<p>2)第一性原理 + 交流,向内习得</p>
<p>这次摸着石头过河,比较新奇的体验就是,从当初一个想法到原理的讨论到最后实现。主要是思考推理,还有和优秀的同事的聊天中习得 (这里感谢 @谷神)。</p>
<ul>
<li>刻意学习 VS 内在习得</li>
</ul>
<p>现实中有很多游戏引擎。他们也许内有乾坤,不过其实是否研究他们也不重要。</p>
<p>我也不在乎别人的实现,或者更好地实现,是否有实现过了可以参考。其实没什么可参考的。只要我们自己想明白了,别忘了我们上面说的,他们都是一件事 —— 编程罢了。
当我们面临新问题,我们也会加强我们的 “引擎”。从思想上,他们是平等的。:P</p>
<p>可能与以前向外求知,现在会额外的向内思考。比较神奇的体验是,一些东西听个大概,也能盲猜个七八分。</p>
<h1>
<a id="%E4%BB%8E%E6%B8%B8%E6%88%8F%E5%BC%80%E5%A7%8B%E8%81%8A%E5%90%A7" href="#%E4%BB%8E%E6%B8%B8%E6%88%8F%E5%BC%80%E5%A7%8B%E8%81%8A%E5%90%A7" class="anchor"></a>从游戏开始聊吧</h1>
<h2>
<a id="%E6%B8%B8%E6%88%8F%E4%B9%8B%E9%9A%BE" href="#%E6%B8%B8%E6%88%8F%E4%B9%8B%E9%9A%BE" class="anchor"></a>游戏之难</h2>
<p>其实2048没啥好聊,写2048的背后是对游戏的一些思考。</p>
<p>其实游戏是一个比较特别的存在。他是一种比较特殊的程序,特殊在哪儿呢?</p>
<p>1)他是持续交互程序</p>
<p>不同于简单的脚本,跑完结束。或者传递一个初始参数,就像函数一样运行完结束。</p>
<p>他是一个持续交互的过程,随着时间累计游戏的方方面面都在变化。</p>
<p>2)多面平衡</p>
<p>不同于你写一段function就结束了。游戏要在运行的生命周期里:</p>
<ul>
<li>用户交互事件</li>
<li>游戏数据计算</li>
<li>渲染视图</li>
</ul>
<p>在至少这三个方面互相作用。</p>
<p>还可能有:</p>
<ul>
<li>网络</li>
<li>调度</li>
<li>硬件CPU、GPU加速渲染</li>
<li>AI</li>
<li>资源生成</li>
<li>数据采集</li>
<li>各种优化技术</li>
</ul>
<p>其他周边并不展开</p>
<p>3)稳定的帧率</p>
<p>如果是60HZ的游戏,必须在 16.6ms 内完成动作进行刷新。</p>
<p>这也不是普通业务脚本、程序一直跑自己的线性逻辑就算了,根本不关心时间。</p>
<p>4)密集对象计算</p>
<p>简单的游戏还好,传统的模式是面向对象建模,一切看起来还算自然。</p>
<p>但是也出现了万人同台的游戏,这里传统的编程模式已经满足不了游戏对象的遍历了,很快会达到性能瓶颈。</p>
<p>这几年,出现了ECS架构(Entity-Component-System)。</p>
<blockquote>
<p><a href="https://blog.codingnow.com/2017/06/overwatch_ecs.html">浅谈《守望先锋》中的 ECS 构架</a></p>
</blockquote>
<p>小结:</p>
<p>其实还有各种发散。如何使用CPU、GPU加速渲染,这就不再提了。</p>
<p>游戏是一个非常特殊的存在,它意味着密集型计算、密集型IO混合出现的场景。我理解是比Web复杂在另一个维度上。</p>
<p>游戏涉及到 编程架构、网络、图形学、美术设计、资源加载…… 诸多丰富的话题。</p>
<p>这些就不是我这个门外汉靠管窥蠡测能够说得清的。我今天可以只谈谈我对游戏的理解和认识,以及构建2048的思考。</p>
<h2>
<a id="%E6%B8%B8%E6%88%8F%E5%9F%BA%E6%9C%AC%E6%9E%84%E6%88%90" href="#%E6%B8%B8%E6%88%8F%E5%9F%BA%E6%9C%AC%E6%9E%84%E6%88%90" class="anchor"></a>游戏基本构成</h2>
<p>其实一个基本游戏可以用如下代码描述:</p>
<pre class="highlight"><code class="language-ruby"><span class="kp">loop</span> <span class="k">do</span>
<span class="no">IOEvent</span>
<span class="no">UpdateGameData</span>
<span class="no">Render</span>
<span class="k">end</span>
</code></pre>
<p>游戏处在一个主循环中,我们依次要处理用户输入事件,根据用户输入事件进行游戏模型的变化,最后再把数据渲染在屏幕上。</p>
<p>这是一个单线程,主循环的例子。</p>
<p>现实中每个部分都可以额外变得复杂。也可以用线程单独实现。一切看需求。</p>
<h3>
<a id="%E6%B8%B8%E6%88%8F%E4%B8%8E%E4%BA%A4%E4%BA%92%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F" href="#%E6%B8%B8%E6%88%8F%E4%B8%8E%E4%BA%A4%E4%BA%92%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F" class="anchor"></a>游戏与交互应用程序</h3>
<p>你会发现游戏就是交互程序。</p>
<p>上面的三部分,你也可以和 MVC 强行扯在一起。</p>
<ul>
<li>M 就是 Model 游戏数据</li>
<li>V 就是 View 负责渲染视图</li>
<li>C 就是 Controler 可以对应事件控制</li>
</ul>
<p>MVC 的典型程序,除了桌面软件,Web也算是, App也算。</p>
<p>看似是在说游戏,实际上他们是一回事。</p>
<h2>
<a id="%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E%E7%9A%84%E7%A7%98%E5%AF%86" href="#%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E%E7%9A%84%E7%A7%98%E5%AF%86" class="anchor"></a>游戏引擎的秘密</h2>
<p>游戏引擎其实就是框架,很佩服他们会起名字。</p>
<p>框架、引擎其实是一个东西,他们的特征就是一个半成品的软件。</p>
<pre class="highlight"><code class="language-ruby"><span class="kp">loop</span> <span class="k">do</span>
<span class="no">IOEvent</span>
<span class="no">UpdateGameData</span>
<span class="no">Render</span>
<span class="k">end</span>
</code></pre>
<p>比如这个游戏循环,如果我们封装了主循环,封装了事件对象。对外暴露了一些生命周期。
这种半成品软件就是 所谓的框架,在游戏领域就是引擎。</p>
<p>作为下游,游戏引擎/框架的使用者来说,我们写的程序就像填空一样和主循环工作在一起。</p>
<h3>
<a id="%E4%B8%BB%E5%BE%AA%E7%8E%AF%E5%86%B3%E5%AE%9A%E4%BA%86%E4%BB%80%E4%B9%88%E6%98%AF%E6%A1%86%E6%9E%B6%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF%E5%BA%93" href="#%E4%B8%BB%E5%BE%AA%E7%8E%AF%E5%86%B3%E5%AE%9A%E4%BA%86%E4%BB%80%E4%B9%88%E6%98%AF%E6%A1%86%E6%9E%B6%E3%80%81%E4%BB%80%E4%B9%88%E6%98%AF%E5%BA%93" class="anchor"></a>主循环决定了什么是框架、什么是库</h3>
<p>所以我个人觉得,决定了什么是 框架Framework 和 库Library 的本质区别是 —— 主循环。</p>
<p>当你的程序是一种可被调用的状态,那么基本上你的程序可以看成一个lib
当你的程序如果拥有了主循环的状态,基本宣告了不可被直接调用。那么它其实是一个 Framework了。除了各种Pattern很少见到主循环的lib 展示,不存在的原因是因为拥有主循环的程序,一般以具体的软件形态出来:</p>
<ol>
<li>某种语言,比如 自带调度的 golang、自带EventLoop的JavaScript 引擎V8</li>
<li>某种框架,比如 Web框架自带监听循环</li>
<li>某种引擎,比如 游戏引擎</li>
</ol>
<p>Framework式的程序,你的工作任务就会转向熟悉这个程序暴露的对象,期待你的程序和主循环能一起工作。</p>
<h2>
<a id="%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E4%BC%9A%E6%98%AF%E6%B8%B8%E6%88%8F%E7%9A%84%E7%93%B6%E9%A2%88%E4%B9%88%EF%BC%9F" href="#%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%E4%BC%9A%E6%98%AF%E6%B8%B8%E6%88%8F%E7%9A%84%E7%93%B6%E9%A2%88%E4%B9%88%EF%BC%9F" class="anchor"></a>编程语言会是游戏的瓶颈么?</h2>
<p>我们再来聊聊游戏引擎和编程语言。</p>
<p>Unity的背后是 C# 支撑;虚幻引擎的背后是 C++。他们采用了更底层的语言。那么问题来了,编程语言会成为制约游戏的瓶颈么?</p>
<p>这也是我自己思考的一个问题。</p>
<p>我们可能会很粗暴地觉得 动态语言普遍慢,当然是越接近底层越好。其实我更想知道,如此这样选择的标准在哪儿?</p>
<p>其实我们可以思考下,这个结论不难获得。</p>
<h3>
<a id="%E5%8A%A8%E6%80%81%E8%AF%AD%E8%A8%80%E7%9C%9F%E7%9A%84%E6%85%A2%E4%B9%88%EF%BC%9F" href="#%E5%8A%A8%E6%80%81%E8%AF%AD%E8%A8%80%E7%9C%9F%E7%9A%84%E6%85%A2%E4%B9%88%EF%BC%9F" class="anchor"></a>动态语言真的慢么?</h3>
<p>其实动态语言在执行一个命令的时候,Ruby这种最后C实现;Golang最后也落在C(Golang实现自举之后,那就用汇编思考吧)。其实他们在执行一个具体操作的时候,数量级一致的。</p>
<p>他们其实差不多。</p>
<p>速度差距在哪儿呢?</p>
<p>1)载入环境</p>
<p>C、Golang这种可以打包成二进制的语言。他编译阶段会把需要执行的代码编译成二进制。</p>
<p>所以执行的时候载入的是所需要用到的部分功能。</p>
<p>Python、Ruby 这种其实 二进制是语言的解释器。运行的时候更多的时间花费在加载解释器。</p>
<p>不过,当你的程序复杂到涉及大量IO、基础库的时候,Golang的打包结果会趋向于接近一个解释器的大小,比如 Ruby 差不多在 30M左右。</p>
<p>我曾经比较过:</p>
<p>Golang的一个项目命令行编辑器 <a href="https://github.com/zyedidia/micro">micro</a> 、Ruby的一个项目命令行编辑器 <a href="https://www.ruby-toolbox.com/projects/diakonos">diakonos</a></p>
<p>micro运行内存16M,也就是他本地大小;diakonos运行内存30M,也就是Ruby解释器差不多的大小。ruby代码会执行才加载,所以可以忽略不计。</p>
<p>最大的差距,在于 30-16 的载入速度差,这个量级是不同的。</p>
<p>2)语言构件</p>
<p>C语言就像是一个高级一点的汇编。C的角度一切都需要手动管理。那么其实对于底层语言,更现实一点的是会自己手动实现数据结构。</p>
<p>Ruby这种动态语言,内部默认会有一个数据结构。</p>
<p>举个例子:</p>
<p>比如 <code>a = "GAME"</code></p>
<p>C语言实际上只会手动创建 "GAME" 四个字符</p>
<p>Python 底层可能创建一个 20字符长度的数组。存GAME。也有好处,可以不定长支持动态扩容。</p>
<p>在生成语言构建的时候存在速度差。
动态语言等于多创建了很多语言在内存里的解构。</p>
<p>3)解析时间</p>
<p>二进制的文件,直接载入内存执行。</p>
<p>动态语言有一个解析的过程。当然,也有优化空间,我们可以提前编译动态语言为虚拟机字节码。这样就获得了 对于解释器是二进制类似的东西。</p>
<p>4)GC时间</p>
<p>和C语言相比,Python、Ruby自带GC。</p>
<p>他们存在一个 必须 GC 暂停的那么一个问题。C语言的策略是手动回收。</p>
<h3>
<a id="%E5%8F%8C%E7%BC%93%E5%86%B2%E6%A8%A1%E5%BC%8F" href="#%E5%8F%8C%E7%BC%93%E5%86%B2%E6%A8%A1%E5%BC%8F" class="anchor"></a>双缓冲模式</h3>
<p>我们好像列举了一大堆 动态语言的缺点似的。实际上自动管理的数据结构、自带GC、可以动态的编译执行…… 这些都是动态语言的缺点。</p>
<p>虽然付出了些许时间的代价。只要我们不滥用语言构件 和 特别烂的算法,真是巧妙的接近底层高效的实现。</p>
<p>其实我想说,动态语言至少在目标上不是特别大的瓶颈。</p>
<p>Java也有游戏的例子;C# 也是自带GC。GC不会是瓶颈。</p>
<p>语言的速度不会绝对意义上成为一个游戏组成的阻碍。</p>
<p>EVE 这样的大型游戏,内部使用了 巨慢的 Python 就可以说明问题。</p>
<p>之所以语言不一定构成拖慢游戏的原因,还有一个就是游戏和屏幕的刷新机制 —— 双缓冲模式。</p>
<p>其实可以理解为一个 内存空间,我们称之为 Buffer。我们有两个 Buffer,分别叫 A Buffer、B Buffer。</p>
<p>显示器先从A Buffer中读取数据渲染屏幕。我们程序写入B Buffer,等我们真的写完了,可慢或者快,但是无所谓,反正屏幕这时候在稳定的读取 A Buffer 内容。我们计算完毕,B Buffer中写入了我们想要的东西,这时候只要把显示器读取的指针指向B Buffer,下次屏幕就会获得我们想要的画面。这就是双缓冲模式。由于存在双缓冲解构,算快和快慢,至少不会成为画面撕裂的原因。</p>
<blockquote>
<p>rb2048 使用了 Curses 库来绘制界面,而 Curses 内部使用了双缓冲模式。</p>
</blockquote>
<h1>
<a id="%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E7%9A%84%E8%AE%A8%E8%AE%BA" href="#%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E7%9A%84%E8%AE%A8%E8%AE%BA" class="anchor"></a>线程和协程的讨论</h1>
<p>我们自己研究了两天线程和队列。主要是Ruby的实现。</p>
<p>这里不教线程和协程,只记录我觉得好玩的交流结果。</p>
<h2>
<a id="Ruby%E7%BA%BF%E7%A8%8B%E7%9A%84%E9%97%AE%E9%A2%98" href="#Ruby%E7%BA%BF%E7%A8%8B%E7%9A%84%E9%97%AE%E9%A2%98" class="anchor"></a>Ruby线程的问题</h2>
<p>缺点:</p>
<p>Ruby存在线程锁,这导致每一时刻只能运行一个线程。线程就像背后虽然有很多工人,但是只能交替的一人一锤子。</p>
<p>这背后的原因在于 Ruby 考虑安全更多一点 —— 线程安全。</p>
<p>这样的多线程无法利用CPU多核心并行的特点。希望利用多核的,可以去用 JRuby,因为Java底层没有加锁。</p>
<p>Ruby3中也有了无锁线程的替代品 Ractor 也可以了解下。</p>
<p>CRuby如果想利用多核心可以使用进程替代线程。如果设计得当,其实差不多。Ruby里面Webserver有名气的Puma采用的就是多进程实现。</p>
<p>优点:</p>
<p>加上锁最大好处是线程安全,你可以自由的编码,Ruby帮你加锁。这样多线程访问变量的时候,不会出错。</p>
<p>但是你退出来想,反正你自己也要加锁啊,谁加不是加。Ruby默认的线程其实书写起来非常友好。</p>
<h2>
<a id="%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E3%80%81%E5%8D%8F%E7%A8%8B+%E5%82%BB%E5%82%BB%E5%88%86%E4%B8%8D%E6%B8%85%E6%A5%9A" href="#%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E3%80%81%E5%8D%8F%E7%A8%8B+%E5%82%BB%E5%82%BB%E5%88%86%E4%B8%8D%E6%B8%85%E6%A5%9A" class="anchor"></a>进程、线程、协程 傻傻分不清楚</h2>
<p>我觉得再这样介绍这三个概念,这文章太冗长了。</p>
<p>直接说结论吧,直观上,这三者存在量级差,不仅体现在空间资源,时间资源都差不多。</p>
<p>进程 >> 线程 >> 协程</p>
<p>比如一台机器4G内存:</p>
<p>可能只能实际生成几百个进程就不太行了。
同样,可以生成几千个线程,就动不了了。
协程可以生成几十万个。</p>
<p>他们大概就是这个差距(有更好数据支持的,请联系我)。</p>
<p>他们切换上下文的时间也遵循这个比较关系。</p>
<p>所以我们一般的策略,尽量多用协程&线程,少用进程。</p>
<p>如果任务独立运行还好,就怕彼此还要通信,出现互相等待的局面。</p>
<p>线程具有CPU亲和性(一般语言来讲)。</p>
<p>比如 Golang的 M:N 模型,主张 先生成 M 个线程,M 是机器CPU核心数,然后再在M个线程之间调度实际产生的N个任务。</p>
<p>比如 Nginx 的配置也主张 配置线程核心数和CPU核心数一致。</p>
<h3>
<a id="%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E7%94%A8%E7%BA%BF%E7%A8%8B%E3%80%81%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E7%94%A8%E5%8D%8F%E7%A8%8B%EF%BC%9F" href="#%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E7%94%A8%E7%BA%BF%E7%A8%8B%E3%80%81%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E7%94%A8%E5%8D%8F%E7%A8%8B%EF%BC%9F" class="anchor"></a>什么时候用线程、什么时候用协程?</h3>
<p>线程、协程产生的原因是什么?</p>
<p>其实还是为了调度。</p>
<p>线程是细分进程下共享内存的场景;协程是为了细化调度。</p>
<p>因为进程、线程本质上是操作系统在调度。操作系统并不清楚什么时候应该调度。只能采用各种优先计算法、平均算法。再怎么算,也是盲人摸象罢了。</p>
<p>协程给了程序员一个口子,你可以用 协程在 涉及阻塞部分进行让出控制权。</p>
<p>简而言之,经验之谈:</p>
<p>涉及到 计算密集型 请用线程。</p>
<p>如果涉及到IO阻塞密集,请用协程。</p>
<p>我们的目的不是为了用而用,而是使用调度,提高我们代码执行的效率,减少等待。</p>
<h3>
<a id="%E7%A1%AC%E4%BB%B6%E4%B8%AD%E6%96%AD" href="#%E7%A1%AC%E4%BB%B6%E4%B8%AD%E6%96%AD" class="anchor"></a>硬件中断</h3>
<p>如果说其实没有 if-else\switch\while,计算机器其实只有 goto。</p>
<p>如果你看过汇编,大概理解我是什么意思。</p>
<p>同样,计算机里进程、线程、协程背后调度的秘密,都来自于 CPU的硬件中断功能。</p>
<p>只不过是上下文快速切换,切换上下文多和少罢了。</p>
<h1>
<a id="2048+%E7%9A%84%E5%AE%9E%E7%8E%B0" href="#2048+%E7%9A%84%E5%AE%9E%E7%8E%B0" class="anchor"></a>2048 的实现</h1>
<p>其实2048的关键就是相邻元素合并,实现这么一个算法,反复执行到无元素可以继续合并。再把这个应用到 x\y 方向所有行列就好了。</p>
<h2>
<a id="%E5%85%B7%E4%BD%93%E7%BA%BF%E7%A8%8B" href="#%E5%85%B7%E4%BD%93%E7%BA%BF%E7%A8%8B" class="anchor"></a>具体线程</h2>
<p>目前实现成通过队列来实现通信:</p>
<p>IO线程,用户产生一个输入,进入事件队列。
游戏读取事件队列,开始计算游戏数据,把结果塞入渲染队列。
渲染线程,读取渲染队列数据进行渲染。</p>
<h2>
<a id="%E5%90%8E%E7%BB%AD%E8%AE%A8%E8%AE%BA" href="#%E5%90%8E%E7%BB%AD%E8%AE%A8%E8%AE%BA" class="anchor"></a>后续讨论</h2>
<p>我和同事交流了一下,就2048而言其实可以很多方式做:</p>
<ol>
<li>如果是队列依赖式</li>
</ol>
<p>我们等于做出一个pipline的方式了</p>
<ol>
<li>我们也可以解开队列阻塞</li>
</ol>
<p>真正的自由渲染。虽然2048看不出效果</p>
<h2>
<a id="%E9%98%9F%E5%88%97%E8%BF%BD%E8%B5%B6%E9%97%AE%E9%A2%98" href="#%E9%98%9F%E5%88%97%E8%BF%BD%E8%B5%B6%E9%97%AE%E9%A2%98" class="anchor"></a>队列追赶问题</h2>
<p>用户不断地敲击,产生时间,如果队列里一致产生数据,那不是渲染永远追不上?</p>
<p>多线程队列需要思考 生产者、消费者模型,需要设计匹配的方式。</p>
<p>解决方法</p>
<p>1)控制生产频率,生产和消耗相抵消</p>
<p>事件采样、渲染 可以保持一个频率</p>
<p>2)不控制生产,但是跳过生产</p>
<p>事件采样,可以携带时间戳。</p>
<p>如果渲染的时候,每次时间超时,跳过关键帧。</p>
<p>当然这些都是很细化的问题了。</p>
<h1>
<a id="%E6%80%BB%E7%BB%93" href="#%E6%80%BB%E7%BB%93" class="anchor"></a>总结</h1>
<p>我倾向于研究一个东西,思考他的全部,寻找最佳的路径。
这些都是摸鱼结果,简单分享下。更深的感受还需要实践和交流。</p>
<h1>
<a id="%E5%90%8E%E7%BB%AD" href="#%E5%90%8E%E7%BB%AD" class="anchor"></a>后续</h1>
<p>上文提到游戏里面最新流行 ECS 架构。ECS 抛弃了面向对象的思想,把同类数据摆放在一起,亲和CPU运行机制,方便大规模属性遍历。</p>
<p>ECS 应该如何用Ruby实现呢?</p>
<p><a href="https://mark24code.github.io/ruby/2022/07/26/%E8%BF%99%E5%BE%97%E4%BB%8E2048%E8%AF%B4%E8%B5%B7.html">我的博客</a></p>
大家好,我是Mark24,可以叫我Mark
Github Mark24Code
我的博客
RubyChina同话题
V2EX同话题
前言
本文比较啰嗦,更倾向于是自言自语。不过我写完回顾,这更...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/1446
2022-08-16T04:54:42Z
2022-10-28T15:02:31Z
用Ruby讲从创业到996公司的故事(戏说master-worker模式)
<h1>
<a id="%E5%89%8D%E8%A8%80" href="#%E5%89%8D%E8%A8%80" class="anchor"></a>前言</h1>
<p>阅读大概需要20分钟。</p>
<p>假设你希望了解 线程、线程池、集群模式/Master-Worker模式、调度器。</p>
<p>需要了解 Ruby 基本的用法和面向对象思想。</p>
<p>本文戏说,无须严肃对待。勿对号入座。个人也没有严肃观点。个人观点和所有人没有关系。</p>
<p><a href="https://mark24code.github.io/ruby/2022/07/23/%E7%94%A8ruby%E6%9D%A5%E5%86%99%E4%B8%80%E4%B8%AA996%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F(master-worker).html">本文博客地址</a></p>
<h2>
<a id="%E5%AE%8C%E6%95%B4%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" href="#%E5%AE%8C%E6%95%B4%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B" class="anchor"></a>完整代码示例</h2>
<p><a href="https://github.com/Mark24Code/rb-master-worker-demo">github:rb-master-worker-demo</a></p>
<h1>
<a id="Master+Worker+%E6%A8%A1%E5%BC%8F" href="#Master+Worker+%E6%A8%A1%E5%BC%8F" class="anchor"></a>Master Worker 模式</h1>
<p>MasterWorker 模式,也有翻译成作集群模式、也叫 Master-Slave 模式。</p>
<blockquote>
<p>Git 不许使用 master了,换成了 main ,Master/Slave 具有政治不正确的歧视色彩。不过这不重要了。其实这个名字很能表达这个模式的特点。</p>
</blockquote>
<p>主要思想就是由一个Master抽象对象来调度Worker对象来工作。</p>
<h2>
<a id="Ruby%E6%96%87%E5%AD%A6%E7%BC%96%E7%A8%8B%EF%BC%8C%E7%94%A8%E4%BB%A3%E7%A0%81%E8%AE%B2%E6%95%85%E4%BA%8B" href="#Ruby%E6%96%87%E5%AD%A6%E7%BC%96%E7%A8%8B%EF%BC%8C%E7%94%A8%E4%BB%A3%E7%A0%81%E8%AE%B2%E6%95%85%E4%BA%8B" class="anchor"></a>Ruby文学编程,用代码讲故事</h2>
<p>其实这也非常像现实中的工作模型。Ruby天生面向对象,表达的文学性,我们可以很方便的来使用代码模拟这种现实情况。
我们来用Ruby模拟下现实中这种情况,顺便学下如何实现这个模式。</p>
<h2>
<a id="%E7%BA%A6%E5%AE%9A" href="#%E7%BA%A6%E5%AE%9A" class="anchor"></a>约定</h2>
<p>会出现几个类:</p>
<ul>
<li>Master 代表 “领导”,不干活,主要工作任务是分配任务,这是 Master 类的特征。</li>
<li>Worker 代表 “打工人”,工作和创造价值的主体。主要任务就是干活。</li>
<li>Workshop 代表 “公司”,主要是负责接单。</li>
</ul>
<p>故事的思路:</p>
<p>我们自己是客户,把“任务”订单交给“公司”,这些任务会转交给“领导”手中,然后“领导”会排期,把工作布置给“打工人”。最终“打工人”乐此不疲的完成任务。</p>
<h2>
<a id="%E5%AE%9E%E7%8E%B0+%E6%89%93%E5%B7%A5%E4%BA%BA+Worker+%E7%B1%BB" href="#%E5%AE%9E%E7%8E%B0+%E6%89%93%E5%B7%A5%E4%BA%BA+Worker+%E7%B1%BB" class="anchor"></a>实现 打工人 Worker 类</h2>
<h3>
<a id="step1+%E7%BB%99%E5%91%98%E5%B7%A5%E5%B7%A5%E5%8F%B7" href="#step1+%E7%BB%99%E5%91%98%E5%B7%A5%E5%B7%A5%E5%8F%B7" class="anchor"></a>step1 给员工工号</h3>
<p>首先我们建立一个 Worker 类,我们给他一个名字属性。<code>attr</code> 暴露出 <code>name</code> 属性。</p>
<pre class="highlight"><code class="language-ruby"><span class="c1"># Workshop.rb</span>
<span class="k">class</span> <span class="nc">Worker</span>
<span class="kp">attr</span> <span class="ss">:name</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="s2">"worker@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>我们采用TDD方式来逐步实现我们的想法:</p>
<pre class="highlight"><code class="language-ruby"><span class="c1">#Workshop_test.rb</span>
<span class="nb">require</span> <span class="s1">'minitest/autorun'</span>
<span class="nb">require_relative</span> <span class="s1">'../lib/Workshop'</span>
<span class="n">describe</span> <span class="no">Worker</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"check worker name"</span> <span class="k">do</span>
<span class="n">w</span> <span class="o">=</span> <span class="no">Worker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"ruby01"</span><span class="p">)</span>
<span class="n">assert_equal</span> <span class="n">w</span><span class="p">.</span><span class="nf">name</span><span class="p">,</span> <span class="s2">"worker@ruby01"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>很快,我们知道这名打工人他叫 “ruby01” 员工。</p>
<h2>
<a id="step2+%E7%BB%99%E5%91%98%E5%B7%A5+KPI%2FOKR" href="#step2+%E7%BB%99%E5%91%98%E5%B7%A5+KPI%2FOKR" class="anchor"></a>step2 给员工 KPI/OKR</h2>
<p>我们不希望打工人每次只能做一件事,你必须得推着他才能工作。他最好学会“成长”会自己努力的工作。
其实就是一堆任务,我们希望他们一直忙。给他N件事情,他一个一个自己做。
我们要给他一个目标,也就是 KPI 或者 OKR 随便吧,实际上这是一个队列对吧。我们用队列实现。</p>
<pre class="highlight"><code class="language-ruby"><span class="nb">require</span> <span class="s1">'thread'</span>
<span class="k">class</span> <span class="nc">Worker</span>
<span class="kp">attr</span> <span class="ss">:name</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="s2">"worker@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
<span class="vi">@queue</span> <span class="o">=</span> <span class="no">Queue</span><span class="p">.</span><span class="nf">new</span>
<span class="vi">@thr</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="n">perfom</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf"><<</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="vi">@queue</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">join</span>
<span class="vi">@thr</span><span class="p">.</span><span class="nf">join</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">perfom</span>
<span class="k">while</span> <span class="p">(</span><span class="n">job</span> <span class="o">=</span> <span class="vi">@queue</span><span class="p">.</span><span class="nf">deq</span><span class="p">)</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">job</span> <span class="o">==</span> <span class="ss">:done</span>
<span class="nb">puts</span> <span class="s2">"worker@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">: job:</span><span class="si">#{</span><span class="n">job</span><span class="si">}</span><span class="s2">"</span>
<span class="n">job</span><span class="p">.</span><span class="nf">call</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">size</span>
<span class="vi">@queue</span><span class="p">.</span><span class="nf">size</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>现在打工人变得充实了许多,他自从来了公司培训之后,就拥有了很多属性和方法。</p>
<ul>
<li>属性说明:</li>
</ul>
<p><code>@queue</code> 就是他的 OKR 清单,他必须完成所有的工作任务。</p>
<p><code>@thr</code> 意思是 thread 缩写,这里是会使用一个线程来调用 <code>perform</code> 我们在用线程模拟打工人干活这件事。可以理解为 <code>@thr</code> 就是打工人的灵魂。</p>
<ul>
<li>方法说明:</li>
</ul>
<p><code><<</code> 是一个 push 方法的语法糖,就给给自己的 OKR 里添加任务。</p>
<p><code>perform</code>
可能要说下 perform 方法, 这里是 “运行”的意思哈,不是“表演” :P。
打工人怎么干活呢?这得说道说道。我们得指导他如何“成长”。</p>
<p>我们前面说了 <code>@queue</code> 就是他的OKR, 他必须从自己的 OKR 中取出任务然后执行。这里我用了 <code>job.call</code>。
暗示,这必须是一个 callable 对象,在ruby里也就是拥有 <code>call</code> 方法的对象。可以是 lambda、或者实现call的。
这也很合理,需求必须能做才会做。没法做的需求,做不了就是做不了。</p>
<p>但是如果给了一个 <code>:done</code> 另说。循环会结束,这个线程会消失。(裁员了 :P)</p>
<pre class="highlight"><code class="language-ruby"> <span class="k">def</span> <span class="nf">perfom</span>
<span class="k">while</span> <span class="p">(</span><span class="n">job</span> <span class="o">=</span> <span class="vi">@queue</span><span class="p">.</span><span class="nf">deq</span><span class="p">)</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">job</span> <span class="o">==</span> <span class="ss">:done</span>
<span class="nb">puts</span> <span class="s2">"worker@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">: job:</span><span class="si">#{</span><span class="n">job</span><span class="si">}</span><span class="s2">"</span>
<span class="n">job</span><span class="p">.</span><span class="nf">call</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<blockquote>
<p>其实Queue这个对象很有意思,Ruby做了一些工作。Queue 在空的时候,虚拟机会让线程进入睡眠等待。如果队列里有任务,就会继续工作。Ruby很贴心,果然是程序员的好朋友啊。 其实我不知道其他语言什么样,懒得查了。</p>
</blockquote>
<p><code>join</code> 方法是一个 Thread 的线程方法,主要的作用是告诉主线程你要等待每一个子线程(自己)的完成。如果不写这句,主线程如果比所有子线程提前结束。那么子线程会被全部关闭。简而言之 <code>join</code> 就是同步等待线程结果。</p>
<p>让我们来看看TDD:</p>
<p>我们可以加一段验证工号 ruby02 的打工人是不是如期的完成了工作。</p>
<pre class="highlight"><code class="language-ruby"><span class="c1"># ....</span>
<span class="n">it</span> <span class="s2">"check worekr do sth job"</span> <span class="k">do</span>
<span class="n">w</span> <span class="o">=</span> <span class="no">Worker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"ruby02"</span><span class="p">)</span>
<span class="n">finished</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">w</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"do job 1"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job1"</span><span class="p">}</span>
<span class="n">w</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"do job 2"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job2"</span><span class="p">}</span>
<span class="n">w</span> <span class="o"><<</span> <span class="ss">:done</span>
<span class="n">w</span><span class="p">.</span><span class="nf">join</span>
<span class="n">assert_equal</span> <span class="n">finished</span><span class="p">,</span> <span class="p">[</span><span class="s2">"job1"</span><span class="p">,</span><span class="s2">"job2"</span><span class="p">]</span>
<span class="k">end</span>
<span class="c1"># ....</span>
</code></pre>
<p>其实到这里,一个合格的打工人就打造完毕了。打工人很简单,只要吃苦耐劳,一切都OK。
下面我们要实现下 Workshop 公司类。</p>
<h2>
<a id="%E5%AE%9E%E7%8E%B0+%E5%85%AC%E5%8F%B8+Workshop+%E7%B1%BB" href="#%E5%AE%9E%E7%8E%B0+%E5%85%AC%E5%8F%B8+Workshop+%E7%B1%BB" class="anchor"></a>实现 公司 Workshop 类</h2>
<h3>
<a id="%E5%9C%A8%E6%AD%A4%E4%B9%8B%E5%89%8D%EF%BC%8C%E6%88%91%E4%BB%AC%E5%85%88%E5%AE%9E%E7%8E%B0%EF%BC%9A%E5%88%9B%E4%B8%9A%E5%85%AC%E5%8F%B8+MiniWorkshop+%E7%B1%BB" href="#%E5%9C%A8%E6%AD%A4%E4%B9%8B%E5%89%8D%EF%BC%8C%E6%88%91%E4%BB%AC%E5%85%88%E5%AE%9E%E7%8E%B0%EF%BC%9A%E5%88%9B%E4%B8%9A%E5%85%AC%E5%8F%B8+MiniWorkshop+%E7%B1%BB" class="anchor"></a>在此之前,我们先实现:创业公司 MiniWorkshop 类</h3>
<p>其实我打算过渡下,首先实现一个 “创业公司” <code>MiniWorkshop</code>。
创业公司刚起步,一般是只有“打工人”,没有真正意义上的中层出现。
这一时期非常简单,伊甸园时期。有活大家一起干,大家都是兄弟。</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">MiniWorkshop</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">count</span><span class="p">)</span>
<span class="vi">@worker_count</span> <span class="o">=</span> <span class="n">count</span> <span class="c1"># 打工人数量</span>
<span class="vi">@workers</span> <span class="o">=</span> <span class="vi">@worker_count</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="c1"># 根据数量生成(招聘)打工人</span>
<span class="no">Worker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="c1"># 给个工号</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># 初创公司分配任务</span>
<span class="k">def</span> <span class="nf"><<</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">if</span> <span class="n">job</span> <span class="o">==</span> <span class="ss">:done</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span> <span class="o"><<</span> <span class="n">job</span><span class="p">}</span>
<span class="k">else</span>
<span class="c1"># 随机选择一个打工人,接活</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">sample</span> <span class="o"><<</span> <span class="n">job</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">join</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="p">.</span><span class="nf">join</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>这里可能说下</p>
<pre class="highlight"><code class="language-ruby"> <span class="k">def</span> <span class="nf"><<</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">if</span> <span class="n">job</span> <span class="o">==</span> <span class="ss">:done</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span> <span class="o"><<</span> <span class="n">job</span><span class="p">}</span>
<span class="k">else</span>
<span class="c1"># 随机选择一个打工人,接活</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">sample</span> <span class="o"><<</span> <span class="n">job</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>这里干活的模式可能不好,因为我们竟然 <code>Array#sample</code> 方式。这是一个随机方法。随机选择一个。
看似不合理,实际上也合情合理。</p>
<p>创业公司初期虽然是草根,可是大家哪个不是大佬。所以活来了谁都行,问题不大。</p>
<p>没事我们后面再改进好了。</p>
<p>TDD:</p>
<p>我们的单元测试其实描述了一个故事。一家创业公司,只有2个人。接到了一个订单是4个工作内容。</p>
<pre class="highlight"><code class="language-ruby"><span class="c1"># ...</span>
<span class="n">it</span> <span class="s2">"check MiniWorkshop work"</span> <span class="k">do</span>
<span class="n">ws</span> <span class="o">=</span> <span class="no">MiniWorkshop</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">finished</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job1"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job1"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job2"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job2"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job3"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job3"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job4"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job4"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="ss">:done</span>
<span class="n">ws</span><span class="p">.</span><span class="nf">join</span>
<span class="n">assert_equal</span> <span class="n">finished</span><span class="p">.</span><span class="nf">size</span><span class="p">,</span> <span class="mi">4</span>
<span class="k">end</span>
<span class="c1"># ...</span>
</code></pre>
<p>我们回过头再看 <code>MiniWorkshop</code> 类,初始化的时候创建了两个员工。任务来了就随机分配给一个员工。
很符合小作坊的模式。</p>
<h2>
<a id="%E5%AE%9E%E7%8E%B0%E4%B8%8A%E5%B8%82%E5%85%AC%E5%8F%B8" href="#%E5%AE%9E%E7%8E%B0%E4%B8%8A%E5%B8%82%E5%85%AC%E5%8F%B8" class="anchor"></a>实现上市公司</h2>
<p>公司变大了,就不止2个员工了。可能四五百号,随机交给一个员工,不现实。中层管理出现。中层出现意味着我们公司的类也要进行改变,公司需要改革。</p>
<p>我们先实现一个改革之后的 Workshop 公司类。</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">Workshop</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="n">master_name</span><span class="p">)</span>
<span class="vi">@worker_count</span> <span class="o">=</span> <span class="n">count</span>
<span class="vi">@workers</span> <span class="o">=</span> <span class="vi">@worker_count</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="no">Worker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
<span class="k">end</span>
<span class="vi">@master</span> <span class="o">=</span> <span class="no">Master</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="vi">@workers</span><span class="p">)</span> <span class="c1"># 新增角色</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf"><<</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">if</span> <span class="n">job</span> <span class="o">==</span> <span class="ss">:done</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span> <span class="o"><<</span> <span class="n">job</span><span class="p">}</span>
<span class="k">else</span>
<span class="vi">@master</span><span class="p">.</span><span class="nf">assign</span><span class="p">(</span><span class="n">job</span><span class="p">)</span> <span class="c1"># master分配任务</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">join</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="p">.</span><span class="nf">join</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>可以看到,我们在初始化函数里新增了 <code>@master</code> 他接受 <code>@workers</code> 作为参数。毕竟领导要点兵啊。</p>
<p><code><<</code>方法也进行了改进,由以前的 直接让 @workers 接收任务,变成 <code>@master.assign</code> 分配任务。</p>
<p>让我们来看下 Master 类</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">Master</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">workers</span><span class="p">)</span>
<span class="vi">@workers</span> <span class="o">=</span> <span class="n">workers</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">assign</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">sort</span><span class="p">{</span><span class="o">|</span><span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="o">|</span> <span class="n">a</span><span class="p">.</span><span class="nf">size</span> <span class="o"><=></span> <span class="n">b</span><span class="p">.</span><span class="nf">size</span><span class="p">}.</span><span class="nf">first</span> <span class="o"><<</span> <span class="n">job</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>其实也不复杂。我们保持了 @workers 的指针, <code>assign</code> 方法更像是把以前分配的逻辑接过来实现了一遍。</p>
<p>这次我们改了分配任务的方式,我们要根据 <code>Worker#size</code> 忙碌程度来分配任务。</p>
<p>毕竟嘛,领导有个方法论,会比小作坊高级很多。</p>
<h2>
<a id="%E5%A4%9A%E9%87%8D%E9%A2%86%E5%AF%BC" href="#%E5%A4%9A%E9%87%8D%E9%A2%86%E5%AF%BC" class="anchor"></a>多重领导</h2>
<p>一个领导就足够了么?不。</p>
<p>现实中我们见过形形色色的领导,有的是自己培养,有的是留过洋,有的是大厂空降。他们拥有不同的“方法论”,也就是 <code>Master#assign</code> 的方式可能不同。</p>
<p>我们给公司再加两个领导。</p>
<h3>
<a id="%E6%97%A0%E9%99%90%E6%96%B9%E6%B3%95%E8%AE%BA" href="#%E6%97%A0%E9%99%90%E6%96%B9%E6%B3%95%E8%AE%BA" class="anchor"></a>无限方法论</h3>
<p>996ICU 领导:</p>
<p>我们使用了 <code>Array#cycle</code> 的方式,这是一个迭代器。比如 <code>[1,2,3].cycle</code> 每次 <code>.next</code> 会产生 <code>1、2、3、1、2、3、1、2、3 .....</code> 无限循环。</p>
<p>这个方法论就是 996 方法论,只要干不死就往死里干。人海战术,把人轮番填上。</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">ICU996Master</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">workers</span><span class="p">)</span>
<span class="vi">@current_worker</span> <span class="o">=</span> <span class="n">workers</span><span class="p">.</span><span class="nf">cycle</span> <span class="c1"># 迭代器</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">assign</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="vi">@current_worker</span><span class="p">.</span><span class="nf">next</span> <span class="o"><<</span> <span class="n">job</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<h3>
<a id="%E5%88%86%E7%BB%84%E4%BB%BB%E5%8A%A1%E6%96%B9%E6%B3%95%E8%AE%BA" href="#%E5%88%86%E7%BB%84%E4%BB%BB%E5%8A%A1%E6%96%B9%E6%B3%95%E8%AE%BA" class="anchor"></a>分组任务方法论</h3>
<p>等我们的公司变大了,我们的业务也会变得丰富,任务不是那么单一。很多工作要添加上组别 group_id,分门别类的交给不同工种的打工人,比如 开发、产品、测试、设计、运营。</p>
<pre class="highlight"><code class="language-ruby">
<span class="k">class</span> <span class="nc">GroupMaster</span>
<span class="no">GROUPS</span> <span class="o">=</span> <span class="p">[</span><span class="ss">:group1</span><span class="p">,</span> <span class="ss">:group2</span><span class="p">,</span> <span class="ss">:group3</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">workers</span><span class="p">)</span>
<span class="vi">@workers</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">workers_per_group</span> <span class="o">=</span> <span class="n">workers</span><span class="p">.</span><span class="nf">length</span> <span class="o">/</span> <span class="no">GROUPS</span><span class="p">.</span><span class="nf">size</span>
<span class="n">workers</span><span class="p">.</span><span class="nf">each_slice</span><span class="p">(</span><span class="n">workers_per_group</span><span class="p">).</span><span class="nf">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">slice</span><span class="p">,</span> <span class="n">index</span><span class="o">|</span>
<span class="n">group_id</span> <span class="o">=</span> <span class="no">GROUPS</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
<span class="vi">@workers</span><span class="p">[</span><span class="n">group_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">slice</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">assign</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="n">worker</span> <span class="o">=</span> <span class="vi">@workers</span><span class="p">[</span><span class="n">job</span><span class="p">.</span><span class="nf">group</span><span class="p">].</span><span class="nf">sort_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">first</span>
<span class="n">worker</span> <span class="o"><<</span> <span class="n">job</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>然后我们可以把不同风格的领导班子集中起来</p>
<pre class="highlight"><code class="language-ruby"><span class="no">Masters</span> <span class="o">=</span> <span class="p">{</span>
<span class="ss">normal: </span><span class="no">NormalMaster</span><span class="p">,</span>
<span class="no">ICU996</span><span class="p">:</span> <span class="no">ICU996Master</span><span class="p">,</span>
<span class="ss">group: </span><span class="no">GroupMaster</span>
<span class="p">}</span>
</code></pre>
<p>我们改造下 <code>Workshop</code> 毕竟这个词是一个 工作室的意思,其实是个小部门。</p>
<p>我们改造之后,我们的小部门可以按照风格不同的领导进行分派工作。</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">Workshop</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="n">master_name</span><span class="p">)</span> <span class="c1"># 新增 master_name 指定</span>
<span class="vi">@worker_count</span> <span class="o">=</span> <span class="n">count</span>
<span class="vi">@workers</span> <span class="o">=</span> <span class="vi">@worker_count</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="no">Worker</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># 匹配 master</span>
<span class="vi">@master</span> <span class="o">=</span> <span class="no">Masters</span><span class="p">[</span><span class="n">master_name</span><span class="p">].</span><span class="nf">new</span><span class="p">(</span><span class="vi">@workers</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf"><<</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">if</span> <span class="n">job</span> <span class="o">==</span> <span class="ss">:done</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span> <span class="o"><<</span> <span class="n">job</span><span class="p">}</span>
<span class="k">else</span>
<span class="vi">@master</span><span class="p">.</span><span class="nf">assign</span><span class="p">(</span><span class="n">job</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">join</span>
<span class="vi">@workers</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span><span class="o">|</span><span class="n">m</span><span class="o">|</span> <span class="n">m</span><span class="p">.</span><span class="nf">join</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>我们来看看不同部门的TDD</p>
<pre class="highlight"><code class="language-ruby"> <span class="n">it</span> <span class="s2">"check Workshop@ normal master"</span> <span class="k">do</span>
<span class="n">ws</span> <span class="o">=</span> <span class="no">Workshop</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="ss">:normal</span><span class="p">)</span>
<span class="n">finished</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job1"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job1"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job2"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job2"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job3"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job3"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job4"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job4"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="ss">:done</span>
<span class="n">ws</span><span class="p">.</span><span class="nf">join</span>
<span class="n">assert_equal</span> <span class="n">finished</span><span class="p">.</span><span class="nf">size</span><span class="p">,</span> <span class="mi">4</span>
<span class="k">end</span>
<span class="n">it</span> <span class="s2">"check Workshop@ ICU996 master"</span> <span class="k">do</span>
<span class="n">ws</span> <span class="o">=</span> <span class="no">Workshop</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="ss">:ICU996</span><span class="p">)</span>
<span class="n">finished</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job1"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job1"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job2"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job2"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job3"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job3"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="nb">puts</span> <span class="s2">"job4"</span><span class="p">;</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span> <span class="s2">"job4"</span><span class="p">}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="ss">:done</span>
<span class="n">ws</span><span class="p">.</span><span class="nf">join</span>
<span class="n">assert_equal</span> <span class="n">finished</span><span class="p">.</span><span class="nf">size</span><span class="p">,</span> <span class="mi">4</span>
<span class="k">end</span>
<span class="n">it</span> <span class="s2">"check Workshop@ group master"</span> <span class="k">do</span>
<span class="n">ws</span> <span class="o">=</span> <span class="no">Workshop</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="ss">:group</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">GroupJob</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">group_id</span><span class="p">,</span> <span class="o">&</span><span class="n">b</span><span class="p">)</span>
<span class="vi">@group_id</span> <span class="o">=</span> <span class="n">group_id</span>
<span class="vi">@blk</span> <span class="o">=</span> <span class="n">b</span>
<span class="k">end</span>
<span class="c1"># 任务分组</span>
<span class="k">def</span> <span class="nf">group</span>
<span class="s2">"group</span><span class="si">#{</span><span class="vi">@group_id</span><span class="si">}</span><span class="s2">"</span><span class="p">.</span><span class="nf">to_sym</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">call</span>
<span class="vi">@blk</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="vi">@group_id</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">finished</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="no">GroupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">group_id</span><span class="o">|</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">group_id</span><span class="p">)}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="no">GroupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">group_id</span><span class="o">|</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">group_id</span><span class="p">)}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="no">GroupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">group_id</span><span class="o">|</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">group_id</span><span class="p">)}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="no">GroupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">group_id</span><span class="o">|</span> <span class="n">finished</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="n">group_id</span><span class="p">)}</span>
<span class="n">ws</span> <span class="o"><<</span> <span class="ss">:done</span>
<span class="n">ws</span><span class="p">.</span><span class="nf">join</span>
<span class="n">assert_equal</span> <span class="n">finished</span><span class="p">.</span><span class="nf">size</span><span class="p">,</span> <span class="mi">4</span>
<span class="k">end</span>
</code></pre>
<h1>
<a id="%E6%80%BB%E7%BB%93+Master-Worker+%E6%A8%A1%E5%BC%8F" href="#%E6%80%BB%E7%BB%93+Master-Worker+%E6%A8%A1%E5%BC%8F" class="anchor"></a>总结 Master-Worker 模式</h1>
<p>好吧,戏说不是胡说,改编不是乱编。</p>
<p>我们从现实的故事中走出来。</p>
<ul>
<li>调度器(Scheduler)</li>
</ul>
<p>其实在这里 Master 类,可能会被叫做 <code>Scheduler</code> 即调度器。内部的方法主要是使用不同的策略来分配任务。</p>
<p>而不同的 Master 实现的 assign 方法就是 调度策略。</p>
<ul>
<li>线程池(Thread Pool)</li>
</ul>
<p>Workshop 其实 持有 <code>@workers</code>,也就是说汇聚了实际工作线程的对象。他们可能会有另一个名字 —— 线程池(Thread Pool)</p>
<p>故事讲完了,你有没有学会呢? :D</p>
<h1>
<a id="%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A" href="#%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%EF%BC%9A" class="anchor"></a>示例代码:</h1>
<ul>
<li><a href="https://github.com/Mark24Code/rb-master-worker-demo">rb-master-worker-demo</a></li>
</ul>
<h1>
<a id="%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99%EF%BC%9A" href="#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99%EF%BC%9A" class="anchor"></a>参考资料:</h1>
<ul>
<li><a href="https://devdocs.io/ruby~3.1/thread">Ruby3 Doc: Thread</a></li>
<li><a href="https://devdocs.io/ruby~3.1/thread/queue">Ruby3 Doc: Thread#Queue</a></li>
<li><a href="https://hspazio.github.io/2017/worker-pool/">worker-pool</a></li>
</ul>
前言
阅读大概需要20分钟。
假设你希望了解 线程、线程池、集群模式/Master-Worker模式、调度器。
需要了解 Ruby 基本的用法和面向对象思想。
本文戏说,无须严肃对待。勿对号入...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/371
2021-12-24T05:33:33Z
2022-10-28T14:41:38Z
Ruby环境搭建asdf+ruby+gem+bundler+源替换
<p><img src="/attachments/T8ZwCrdP9KwSt4RR7FAdfAAV/cg_dev0002.jpg" alt="cg_dev0002.jpg">
我的博客 <a href="https://mark24code.github.io/ruby/2021/12/24/Ruby%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BAasdf+ruby+gem+bundler+%E6%BA%90%E6%9B%BF%E6%8D%A2.html">https://mark24code.github.io/ruby/2021/12/24/Ruby%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BAasdf+ruby+gem+bundler+%E6%BA%90%E6%9B%BF%E6%8D%A2.html</a></p>
<p>适用于新手搭建Ruby环境</p>
<h1>
<a id="step1+asdf" href="#step1+asdf" class="anchor"></a>step1 asdf</h1>
<p>asdf 的功能类似 rvm, 但是更高级点,严格意义上说 asdf 继承了所有语言的 *vm 管理。我喜欢一劳永逸。</p>
<p>开始吧</p>
<p>详细可以访问官网 <a href="https://asdf-vm.com">https://asdf-vm.com</a></p>
<p>我下面列举我要做做的事情,大家根据自己的实际情况来。</p>
<h2>
<a id="1.1+%E5%AE%89%E8%A3%85+asdf" href="#1.1+%E5%AE%89%E8%A3%85+asdf" class="anchor"></a>1.1 安装 asdf</h2>
<p>安装就是 clone</p>
<pre class="highlight"><code>git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.8.1
</code></pre>
<h2>
<a id="1.2+%E5%90%AF%E7%94%A8+asdf" href="#1.2+%E5%90%AF%E7%94%A8+asdf" class="anchor"></a>1.2 启用 asdf</h2>
<p>把下句添加到你的 .zshrc</p>
<pre class="highlight"><code>. $HOME/.asdf/asdf.sh
</code></pre>
<p>bash同理,其他的 shell参考 asdf 官网</p>
<h1>
<a id="step2+%28%E9%80%9A%E8%BF%87asdf%EF%BC%89%E5%AE%89%E8%A3%85+Ruby" href="#step2+%28%E9%80%9A%E8%BF%87asdf%EF%BC%89%E5%AE%89%E8%A3%85+Ruby" class="anchor"></a>step2 (通过asdf)安装 Ruby</h1>
<p>这种形式安装的Ruby不需要Root权限,且可以安装多个版本每个版本彼此隔离,非常爽。</p>
<p>详细可以参考 <a href="https://github.com/asdf-vm/asdf-ruby">asdf-ruby plugin</a></p>
<p>基本逻辑是 asdf 是管理 vm的框架,我们要在基础上安装 asdf-ruby 插件,然后通过 这个插件安装我们想要的ruby。</p>
<p>下面我只列举用到的</p>
<p>安装插件</p>
<pre class="highlight"><code>asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git
</code></pre>
<h2>
<a id="2.1+%E5%AE%89%E8%A3%85%E5%BF%85%E8%A6%81%E7%9A%84%E4%BE%9D%E8%B5%96" href="#2.1+%E5%AE%89%E8%A3%85%E5%BF%85%E8%A6%81%E7%9A%84%E4%BE%9D%E8%B5%96" class="anchor"></a>2.1 安装必要的依赖</h2>
<p>编译式安装,所以你可以自由的安装在你的 Mac、树莓派、X86, 系统上 Arch、Debian、CentOS、FreeBSD……</p>
<p><a href="https://github.com/rbenv/ruby-build/wiki#suggested-build-environment">ruby-build/wiki#suggested-build-environment</a></p>
<p>根据下列命名,在你的系统中安装对应的依赖,没找到,查看下上面文档完整的。</p>
<pre class="highlight"><code class="language-ruby"><span class="c1"># optional, but recommended:</span>
<span class="n">brew</span> <span class="n">install</span> <span class="n">openssl</span> <span class="nb">readline</span>
<span class="c1"># use Homebrew OpenSSL (note: will not work for ruby < 2.4)</span>
<span class="n">export</span> <span class="no">RUBY_CONFIGURE_OPTS</span><span class="o">=</span><span class="s2">"--with-openssl-dir=$(brew --prefix openssl@1.1)"</span>
<span class="n">rbenv</span> <span class="n">install</span> <span class="mf">2.6</span><span class="o">.</span><span class="mi">5</span>
<span class="c1"># Depending on your version of Ubuntu/Debian/Mint, libgdbm6 won't be available.</span>
<span class="c1"># In that case, try an earlier version such as libgdbm5.</span>
<span class="n">apt</span><span class="o">-</span><span class="n">get</span> <span class="n">install</span> <span class="n">autoconf</span> <span class="n">bison</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span> <span class="n">libssl</span><span class="o">-</span><span class="n">dev</span> <span class="n">libyaml</span><span class="o">-</span><span class="n">dev</span> <span class="n">libreadline6</span><span class="o">-</span><span class="n">dev</span> <span class="n">zlib1g</span><span class="o">-</span><span class="n">dev</span> <span class="n">libncurses5</span><span class="o">-</span><span class="n">dev</span> <span class="n">libffi</span><span class="o">-</span><span class="n">dev</span> <span class="n">libgdbm6</span> <span class="n">libgdbm</span><span class="o">-</span><span class="n">dev</span> <span class="n">libdb</span><span class="o">-</span><span class="n">dev</span>
<span class="n">yum</span> <span class="n">install</span> <span class="o">-</span><span class="n">y</span> <span class="n">gcc</span><span class="o">-</span><span class="mi">6</span> <span class="n">bzip2</span> <span class="n">openssl</span><span class="o">-</span><span class="n">devel</span> <span class="n">libyaml</span><span class="o">-</span><span class="n">devel</span> <span class="n">libffi</span><span class="o">-</span><span class="n">devel</span> <span class="nb">readline</span><span class="o">-</span><span class="n">devel</span> <span class="n">zlib</span><span class="o">-</span><span class="n">devel</span> <span class="n">gdbm</span><span class="o">-</span><span class="n">devel</span> <span class="n">ncurses</span><span class="o">-</span><span class="n">devel</span>
<span class="n">dnf</span> <span class="n">install</span> <span class="o">-</span><span class="n">y</span> <span class="n">gcc</span> <span class="n">make</span> <span class="n">bzip2</span> <span class="n">openssl</span><span class="o">-</span><span class="n">devel</span> <span class="n">libyaml</span><span class="o">-</span><span class="n">devel</span> <span class="n">libffi</span><span class="o">-</span><span class="n">devel</span> <span class="nb">readline</span><span class="o">-</span><span class="n">devel</span> <span class="n">zlib</span><span class="o">-</span><span class="n">devel</span> <span class="n">gdbm</span><span class="o">-</span><span class="n">devel</span> <span class="n">ncurses</span><span class="o">-</span><span class="n">devel</span>
<span class="n">zypper</span> <span class="k">in</span> <span class="n">gcc</span> <span class="n">bzip2</span> <span class="n">libopenssl</span><span class="o">-</span><span class="n">devel</span> <span class="n">libyaml</span><span class="o">-</span><span class="n">devel</span> <span class="n">libffi</span><span class="o">-</span><span class="n">devel</span> <span class="nb">readline</span><span class="o">-</span><span class="n">devel</span> <span class="n">zlib</span><span class="o">-</span><span class="n">devel</span> <span class="n">gdbm</span><span class="o">-</span><span class="n">devel</span> <span class="n">ncurses</span><span class="o">-</span><span class="n">devel</span>
<span class="c1"># Possibly you'll also need devel_basis (build-essential on Debian)</span>
<span class="n">zypper</span> <span class="n">install</span> <span class="o">-</span><span class="n">t</span> <span class="n">pattern</span> <span class="n">devel_basis</span>
</code></pre>
<h2>
<a id="2.2+%E6%AD%A3%E5%BC%8F%E5%AE%89%E8%A3%85+Ruby" href="#2.2+%E6%AD%A3%E5%BC%8F%E5%AE%89%E8%A3%85+Ruby" class="anchor"></a>2.2 正式安装 Ruby</h2>
<h3>
<a id="2.2.1+fetch+%26+%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89Ruby%E7%89%88%E6%9C%AC" href="#2.2.1+fetch+%26+%E5%88%97%E5%87%BA%E6%89%80%E6%9C%89Ruby%E7%89%88%E6%9C%AC" class="anchor"></a>2.2.1 fetch & 列出所有Ruby版本</h3>
<pre class="highlight"><code>asdf list all ruby
</code></pre>
<pre class="highlight"><code>.....
2.7.0-preview2
2.7.0-preview3
2.7.0-rc1
2.7.0-rc2
2.7.0
2.7.1
2.7.2
2.7.3
2.7.4
3.0.0-dev
3.0.0-preview1
3.0.0-preview2
3.0.0-rc1
3.0.0
3.0.1
3.0.2
3.1.0-dev
artichoke-dev
jruby-dev
jruby-1.5.6
jruby-1.6.3
jruby-1.6.4
....
</code></pre>
<p>挑选感兴趣的版本,比如我们对 3.0.2 感兴趣</p>
<h3>
<a id="%E5%AE%89%E8%A3%85%E6%8C%87%E5%AE%9A%E7%89%88%E6%9C%AC" href="#%E5%AE%89%E8%A3%85%E6%8C%87%E5%AE%9A%E7%89%88%E6%9C%AC" class="anchor"></a>安装指定版本</h3>
<pre class="highlight"><code>asdf install ruby 3.0.2
</code></pre>
<p>自动进入 下载源码,编译,安装</p>
<p>结束了就安装完毕</p>
<h3>
<a id="%E6%8C%87%E5%AE%9A%E4%BD%A0%E7%9A%84Ruby%E4%B8%BA%E5%85%A8%E5%B1%80" href="#%E6%8C%87%E5%AE%9A%E4%BD%A0%E7%9A%84Ruby%E4%B8%BA%E5%85%A8%E5%B1%80" class="anchor"></a>指定你的Ruby为全局</h3>
<pre class="highlight"><code>asdf global ruby 3.0.2
</code></pre>
<p>可以设置你的 ruby为系统级别 默认Ruby</p>
<p>打开新的终端,可以 输入 <code>ruby -v</code> 确认下是否安装成功了</p>
<p>到这里安装Ruby的过程愉快的结束。</p>
<p>asdf 的工作只需要做一遍。之后直接 安装指定版本,就好了。</p>
<p>asdf 还支持 local 关键字,局部设置优先级高的解释器,更多参考 asdf</p>
<h1>
<a id="step3+gem" href="#step3+gem" class="anchor"></a>step3 gem</h1>
<p>gem 在 ruby 里面是一个标准的打包格式 用来打包 ruby的 库。</p>
<p>下载 gem 默认是 <a href="https://rubygems.org/">https://rubygems.org/</a></p>
<p>可以搜索。</p>
<p>这个类似 Python的Pypi、Nodejs的NPM,但是实际上 Gem在他们之前,完成度非常高且非常先进。</p>
<h2>
<a id="3.1+%E5%9B%BD%E6%83%85%E7%BD%91%E7%BB%9C%E9%97%AE%E9%A2%98%EF%BC%8C%E6%9B%BF%E6%8D%A2%E4%B8%AD%E6%96%87%E6%BA%90" href="#3.1+%E5%9B%BD%E6%83%85%E7%BD%91%E7%BB%9C%E9%97%AE%E9%A2%98%EF%BC%8C%E6%9B%BF%E6%8D%A2%E4%B8%AD%E6%96%87%E6%BA%90" class="anchor"></a>3.1 国情网络问题,替换中文源</h2>
<p><a href="https://gems.ruby-china.com/">https://gems.ruby-china.com/</a></p>
<p>是官方镜像站点。</p>
<p>这里建议 使用 中文源,提高速度</p>
<pre class="highlight"><code>请尽可能用比较新的 RubyGems 版本,建议 2.6.x 以上。
$ gem update --system # 这里请翻墙一下
$ gem -v
2.6.3
$ gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
$ gem sources -l
https://gems.ruby-china.com
# 确保只有 gems.ruby-china.com
</code></pre>
<h2>
<a id="3.2+%E9%9A%90%E8%97%8F%E9%97%AE%E9%A2%98+--+%E6%89%BE%E4%B8%8D%E5%88%B0%E5%91%BD%E4%BB%A4" href="#3.2+%E9%9A%90%E8%97%8F%E9%97%AE%E9%A2%98+--+%E6%89%BE%E4%B8%8D%E5%88%B0%E5%91%BD%E4%BB%A4" class="anchor"></a>3.2 隐藏问题 -- 找不到命令</h2>
<p>当我们通过 gem 安装的工具,比如 step4 要提到的 bundler、或者著名框架 rails 他们的命令本身都可以被当成可执行命令执行</p>
<pre class="highlight"><code># 你其实没看错,执行的时候 叫 bundle
bundle install
rails new my-project
</code></pre>
<p>如果我已经安装了,但是终端提示并没有找到命令怎么办?</p>
<p>执行下面命令,刷新 Ruby 安装路径的 path。新打开一个终端重新执行你需要的命令</p>
<pre class="highlight"><code>asdf reshim ruby
</code></pre>
<h1>
<a id="step4++Bundler" href="#step4++Bundler" class="anchor"></a>step4 Bundler</h1>
<p><a href="https://bundler.io/">https://bundler.io/</a> 是一款建立在 Gem 机制之上,并且可以批量安装 gem 的方便工具。</p>
<p>一般项目中,我们会用 Bundler 帮我们管理。</p>
<p>这个作用类似于 JavaScript 里面的 NPM 和 package.json 发挥的作用。</p>
<p>我们在设置好 step3 的 gem 的时候,首先,让我们全局安装下 Bundler</p>
<pre class="highlight"><code>gem install bundler
</code></pre>
<p>然后我们后续的日常使用,都可以借助 bundler提高效率。更多 bundler 的指导可以参考 bundler 官网。</p>
<h2>
<a id="4.1+%E7%BB%99bundler%E6%9B%BF%E6%8D%A2%E6%BA%90" href="#4.1+%E7%BB%99bundler%E6%9B%BF%E6%8D%A2%E6%BA%90" class="anchor"></a>4.1 给bundler替换源</h2>
<p>执行下这句,以后 bundler 的命令,我们也不用关心源了。</p>
<pre class="highlight"><code># 你没看到,他在工作的时候,叫 bundle
bundle config mirror.https://rubygems.org https://gems.ruby-china.com
</code></pre>
<h1>
<a id="%E6%80%BB%E7%BB%93" href="#%E6%80%BB%E7%BB%93" class="anchor"></a>总结</h1>
<p>走到这步,你拥有了一个可以完全使用的开发环境</p>
<h1>
<a id="%E5%85%B6%E4%BB%96" href="#%E5%85%B6%E4%BB%96" class="anchor"></a>其他</h1>
<h2>
<a id="Dockerfile+%E5%8F%82%E8%80%83" href="#Dockerfile+%E5%8F%82%E8%80%83" class="anchor"></a>Dockerfile 参考</h2>
<pre class="highlight"><code>FROM ruby:2.7.4-alpine3.14
RUN apk add --update --no-cache \
build-base \
postgresql-dev \
tzdata
RUN gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
RUN gem install bundler
RUN bundle config mirror.https://rubygems.org https://gems.ruby-china.com
WORKDIR /app
RUN gem install bundler
RUN bundle config set --local path 'vendor/bundle'
# Install gems
ADD Gemfile* /app/
RUN bundle install
COPY . .
# use APP_ENV
CMD bundle exec rake server:run
EXPOSE 3000
</code></pre>
我的博客 https://mark24code.github.io/ruby/2021/12/24/Ruby%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BAasdf+r...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/357
2021-12-14T04:53:33Z
2022-10-28T14:41:25Z
《Ruby 设计模式》笔记
<p>算是私人笔记。但是记录下也许需要人可以获得有价值的信息。</p>
<p>这本书绝版了</p>
<p><a href="https://mark24code.github.io/%E7%AC%94%E8%AE%B0/2021/12/02/%E7%AC%94%E8%AE%B0-Ruby%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.html">Ruby设计模式笔记</a></p>
算是私人笔记。但是记录下也许需要人可以获得有价值的信息。
这本书绝版了
Ruby设计模式笔记
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/352
2021-12-07T09:57:41Z
2022-10-28T14:41:20Z
Ruby标准库有趣部分摘要
<h2>
<a id="%E6%88%91%E7%9A%84BLOG+https%3A%2F%2Fmark24code.github.io%2Fruby%2F2021%2F12%2F07%2FRuby%25E6%25A0%2587%25E5%2587%2586%25E5%25BA%2593%25E6%259C%2589%25E8%25B6%25A3%25E9%2583%25A8%25E5%2588%2586%25E6%2591%2598%25E8%25A6%2581.html" href="#%E6%88%91%E7%9A%84BLOG+https%3A%2F%2Fmark24code.github.io%2Fruby%2F2021%2F12%2F07%2FRuby%25E6%25A0%2587%25E5%2587%2586%25E5%25BA%2593%25E6%259C%2589%25E8%25B6%25A3%25E9%2583%25A8%25E5%2588%2586%25E6%2591%2598%25E8%25A6%2581.html" class="anchor"></a>我的BLOG <a href="https://mark24code.github.io/ruby/2021/12/07/Ruby%E6%A0%87%E5%87%86%E5%BA%93%E6%9C%89%E8%B6%A3%E9%83%A8%E5%88%86%E6%91%98%E8%A6%81.html">https://mark24code.github.io/ruby/2021/12/07/Ruby%E6%A0%87%E5%87%86%E5%BA%93%E6%9C%89%E8%B6%A3%E9%83%A8%E5%88%86%E6%91%98%E8%A6%81.html</a>
</h2>
<h1>
<a id="%E6%91%98%E8%A6%81" href="#%E6%91%98%E8%A6%81" class="anchor"></a>摘要</h1>
<ul>
<li>
<a href="https://devdocs.io/ruby~2.7/etc">etc</a> 用来获得 <code>/etc</code> 下面信息。比如系统登录用户,可以做一个系统粘合性较高的程序来使用。</li>
<li>
<a href="https://devdocs.io/ruby~2.7/enumerable">enumerable</a> 把你的class变成迭代器</li>
<li>
<a href="https://devdocs.io/ruby~2.7/objectspace">objectspace</a> 可以返回class实例统计信息,size等,可以作为扫描、performance、统计使用。</li>
<li>
<a href="https://devdocs.io/ruby~2.7/observable">observable</a> 把你的class变成发布订阅模式</li>
<li>
<a href="https://devdocs.io/ruby~2.7/marshal">marshal</a> 把程序对象字节持久化,或者还原。适合做在内存中缓存对象。比如 命令模式栈里撤销的对象。</li>
<li>
<a href="https://devdocs.io/ruby~2.7/pathname">pathname</a> 有野心的module封装了路径的操作,方便目录文件操作</li>
<li>
<a href="https://devdocs.io/ruby~2.7/io#method-i-eof">IO#eof?</a> <code>eof?</code>是一个外部迭代器可以用的方法,外部迭代器更方便控制。其他<code>eof?</code> 同理。</li>
<li>
<a href="https://devdocs.io/ruby~2.7/mutex">mutex</a> 多线程中提供锁同步</li>
</ul>
<p>未完待续</p>
我的BLOG https://mark24code.github.io/ruby/2021/12/07/Ruby%E6%A0%87%E5%87%86%E5%BA%93%E6%9C%89%E8%...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/340
2021-11-25T03:44:47Z
2022-10-28T14:41:04Z
组建局域网 01_搭建 DNS 服务器给设备专属域名
<p>我的博客地址</p>
<p><a href="https://mark24code.github.io/%E5%88%86%E4%BA%AB%E4%B8%8E%E5%88%9B%E9%80%A0/2021/11/23/%E7%BB%84%E5%BB%BA%E5%B1%80%E5%9F%9F%E7%BD%9101_%E6%90%AD%E5%BB%BADNS%E6%9C%8D%E5%8A%A1%E5%99%A8.html">https://mark24code.github.io/%E5%88%86%E4%BA%AB%E4%B8%8E%E5%88%9B%E9%80%A0/2021/11/23/%E7%BB%84%E5%BB%BA%E5%B1%80%E5%9F%9F%E7%BD%9101_%E6%90%AD%E5%BB%BADNS%E6%9C%8D%E5%8A%A1%E5%99%A8.html</a></p>
<hr>
<h1>
<a id="%E8%83%8C%E6%99%AF%EF%BC%9A" href="#%E8%83%8C%E6%99%AF%EF%BC%9A" class="anchor"></a>背景:</h1>
<p>在家庭局域网中,我们有若干个设备连接在同一台路由其中。路由器和下属设备形成了一个小型局域网。</p>
<p>我们可以在局域网中通过ip互相访问。这是一般情况。</p>
<h1>
<a id="%E7%9B%AE%E6%A0%87" href="#%E7%9B%AE%E6%A0%87" class="anchor"></a>目标</h1>
<p>我希望可以给局域网中的设备专门的域名。</p>
<h1>
<a id="%E6%96%B9%E6%B3%95" href="#%E6%96%B9%E6%B3%95" class="anchor"></a>方法</h1>
<p>完成这个目标其实有很多方法。</p>
<h2>
<a id="1.+%E8%B7%AF%E7%94%B1%E5%99%A8%E5%8A%9F%E8%83%BD" href="#1.+%E8%B7%AF%E7%94%B1%E5%99%A8%E5%8A%9F%E8%83%BD" class="anchor"></a>1. 路由器功能</h2>
<p>比较新的路由器也许有更改hosts的功能。小米路由器之前是有的。现在没了。</p>
<p>我要重点介绍的就是第二个方法</p>
<h2>
<a id="2.+Dnsmasq" href="#2.+Dnsmasq" class="anchor"></a>2. Dnsmasq</h2>
<p><a href="https://wiki.archlinux.org/title/Dnsmasq_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)">Dnsmasq</a> 提供 DNS 缓存和 DHCP 服务功能。作为域名解析服务器(DNS),dnsmasq可以通过缓存 DNS 请求来提高对访问过的网址的连接速度。作为DHCP 服务器,dnsmasq 可以用于为局域网电脑分配内网ip地址和提供路由。DNS和DHCP两个功能可以同时或分别单独实现。dnsmasq轻量且易配置,适用于个人用户或少于50台主机的网络。此外它还自带了一个 PXE 服务器。</p>
<p>更详尽的功能可以查看Dnsmasq的wiki。</p>
<h2>
<a id="Dnsmasq%E5%85%B7%E4%BD%93%E6%93%8D%E4%BD%9C" href="#Dnsmasq%E5%85%B7%E4%BD%93%E6%93%8D%E4%BD%9C" class="anchor"></a>Dnsmasq具体操作</h2>
<h3>
<a id="1.+%E5%AE%89%E8%A3%85Dnsmasq" href="#1.+%E5%AE%89%E8%A3%85Dnsmasq" class="anchor"></a>1. 安装Dnsmasq</h3>
<p>各大发行版都有自己的安装方式,以Ubuntu为例</p>
<pre class="highlight"><code>sudo apt install dnsmasq
</code></pre>
<h3>
<a id="2.+%E9%85%8D%E7%BD%AEdnsmasq" href="#2.+%E9%85%8D%E7%BD%AEdnsmasq" class="anchor"></a>2. 配置dnsmasq</h3>
<p>先备份原始的 <code>dnsmasq.conf</code> 养成好习惯</p>
<pre class="highlight"><code>sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bak
</code></pre>
<p>编辑<code>/etc/dnsmasq.conf</code></p>
<pre class="highlight"><code>sudo vim /etc/dnsmasq.conf
</code></pre>
<p>文件最下方新增</p>
<pre class="highlight"><code># /etc/dnsmasq.conf
resolv-file=/etc/resolv.dnsmasq.conf
strict-order
listen-address=127.0.0.1
listen-address=192.168.31.223
</code></pre>
<p>dnsmasq.conf 默认有很多设置,我们分别解读下我们设置的。</p>
<p>第一行,<code>resolv-file...</code> 主要是指向另一个文件 我们这里指向的是自定义<code>/etc/resolv.dnsmasq.conf</code>,如果这里不指定会自动生成一个默认的 <code>resolv</code> 配置,会导致解析问题。</p>
<pre class="highlight"><code>#文件 /etc/resolv.dnsmasq.conf
# google的CDN
nameserver 8.8.8.8
nameserver 8.8.4.4
# 运营商的114
nameserver 114.114.114.114
# 阿里的
nameserver 223.5.5.5
# 腾讯的
nameserver 119.29.29.29
</code></pre>
<p>这里主要配置了一些DNS服务器,记住一定要配置。否则不论是本机还是未来在局域网中被指向都无法解析域名。</p>
<p>第二行</p>
<p><code>strict-order</code> 是指按照制定顺序解析DNS</p>
<p>第三行、第四行分别是监听本机、局域网中本地地址(作为局域网中服务器)。</p>
<h3>
<a id="3.+%E5%90%AF%E5%8A%A8dnsmasq%E6%9C%8D%E5%8A%A1" href="#3.+%E5%90%AF%E5%8A%A8dnsmasq%E6%9C%8D%E5%8A%A1" class="anchor"></a>3. 启动dnsmasq服务</h3>
<p>这里使用systemd的方式</p>
<pre class="highlight"><code># 启动服务
sudo systemctl start dnsmasq
# 查看服务状态
sudo systemctl status dnsmasq
</code></pre>
<p>以本机为例,输出如下</p>
<pre class="highlight"><code>➜ ~ sudo systemctl status dnsmasq
● dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
Loaded: loaded (/lib/systemd/system/dnsmasq.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2021-11-24 23:41:46 CST; 3s ago
Process: 2075 ExecStartPre=/usr/sbin/dnsmasq --test (code=exited, status=0/SUCCESS)
Process: 2076 ExecStart=/etc/init.d/dnsmasq systemd-exec (code=exited, status=0/SUCCESS)
Process: 2085 ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf (code=exited, status=0/SUCCESS)
Main PID: 2084 (dnsmasq)
Tasks: 1 (limit: 4915)
CGroup: /system.slice/dnsmasq.service
└─2084 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -r /run/dnsmasq/resolv.conf -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=.,20326,8,2,e06d44b80b8f1d39a95c0b0d7c65d08458e880409bbc6834571
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify dumpfile
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: reading /etc/resolv.dnsmasq.conf
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: using nameserver 8.8.8.8#53
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: using nameserver 8.8.4.4#53
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: using nameserver 114.114.114.114#53
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: using nameserver 223.5.5.5#53
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: using nameserver 119.29.29.29#53
11月 24 23:41:46 mark-pi400 dnsmasq[2084]: read /etc/hosts - 10 addresses
11月 24 23:41:46 mark-pi400 dnsmasq[2085]: Too few arguments.
11月 24 23:41:46 mark-pi400 systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
</code></pre>
<p>systemd的展示非常友好,可以看到成功了解析了我们设置的 DNS server并且也解析了 <code>/etc/hosts</code></p>
<p>我们可以在 hosts文件中定义一些局域网的静态IP和我们想要赋予的域名,这样子可以在局域网通过域名访问设备</p>
<p>让我们开始编辑hosts</p>
<pre class="highlight"><code>sudo vim /etc/hosts
</code></pre>
<p>比如我把 29地址 命名为 my-linux-server.home</p>
<pre class="highlight"><code>127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
# custom LAN dns
192.168.31.29 my-linux-server.home
</code></pre>
<p>然后我们重启下 dnsmasq 服务</p>
<pre class="highlight"><code># 重启服务
sudo systemctl restart dnsmasq
# 如果我们希望每次开启也启动服务
sudo systemctl enable dnsmasq
# 顺带提议下关闭
# 关闭开启启动服务
sudo systemctl disable dnsmasq
# 关闭服务
sudo systemctl stop dnsmasq
</code></pre>
<h1>
<a id="%E8%AE%BE%E7%BD%AE%E4%BD%A0%E7%9A%84%E8%B7%AF%E7%94%B1%E5%99%A8" href="#%E8%AE%BE%E7%BD%AE%E4%BD%A0%E7%9A%84%E8%B7%AF%E7%94%B1%E5%99%A8" class="anchor"></a>设置你的路由器</h1>
<p>我们已经设置好一个DNS服务器</p>
<p>接下来,进入你的路由器管理界面,使用你的设置,把你路由器的DNS服务器指向刚才机器在局域网的静态IP。</p>
<p>也可以配置你的电脑的DNS指向这个地址。</p>
<p>一个建议是,保留原始的DNS主机地址比如我这里</p>
<pre class="highlight"><code>192.168.1.1
192.168.31.223
</code></pre>
<p>第一个是我的原始路由器的DNS地址</p>
<p>第二个是我设置的地址,这样子可以作为补充。</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/1450ccac-8ce0-4ed4-8359-cdca5d209ab9.png!large" alt=""></p>
<h2>
<a id="Tip" href="#Tip" class="anchor"></a>Tip</h2>
<ol>
<li>如果你dnsmasq设置没有继承 路由器主机的 DNS服务,可以设置第二个DNS服务器为路由器主机</li>
</ol>
<p>这样保证了原来的状态。</p>
<h1>
<a id="%E5%B0%8F%E7%BB%93" href="#%E5%B0%8F%E7%BB%93" class="anchor"></a>小结</h1>
<p>假设我们前面设置DNS的机器是A。我们通过路由器设置了DNS服务器指向A。</p>
<p>我们可以通过修改A的hosts文件和重启dnsmasq服务,让整个局域网读取我们的配置,完成一个ip和域名的映射。这样我们可以在局域网内使用域名命名设备然后访问。</p>
<h1>
<a id="%E6%A3%80%E9%AA%8C" href="#%E6%A3%80%E9%AA%8C" class="anchor"></a>检验</h1>
<p>我们可以ping下设备域名</p>
<pre class="highlight"><code>ping my-linux-server.home
</code></pre>
<p>也可以安装</p>
<pre class="highlight"><code> sudo apt install dnsutils
</code></pre>
<p>之后使用</p>
<pre class="highlight"><code>dig my-linux-server.home
</code></pre>
<p>来查看DNS解析情况</p>
<h1>
<a id="%E8%A1%A5%E5%85%85%E8%AF%B4%E6%98%8E" href="#%E8%A1%A5%E5%85%85%E8%AF%B4%E6%98%8E" class="anchor"></a>补充说明</h1>
<ol>
<li>缓存</li>
</ol>
<p>A做好设置,局域网内部 B电脑 ping不到定义的主机怎么办? 可能是DNS缓存。
参考如下:</p>
<p><a href="https://ubuntututorials.org/flush-dns-cache-ubuntu-20-04/">https://ubuntututorials.org/flush-dns-cache-ubuntu-20-04/</a></p>
<p>基本上等待一会就生效了。</p>
<ol>
<li>net-tools</li>
</ol>
<p>包含 ifconfig 可以查看ip</p>
<ol>
<li>dnsutils</li>
</ol>
<p>包含 dig 可以查看dns情况</p>
<hr>
<p>有更好的方法,也可以告诉我~<img src="/attachments/meoqiA8VnWiQCJ3UUbUQj5NZ/dns_ip_setting.png" alt="dns_ip_setting.png"></p>
我的博客地址
https://mark24code.github.io/%E5%88%86%E4%BA%AB%E4%B8%8E%E5%88%9B%E9%80%A0/2021/11/23/%E7%...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/324
2021-10-13T06:13:18Z
2022-10-28T14:40:48Z
并行并发进程线程协程 GIL 概念简明解释笔记
<p>根据参考文章做了一些简要的笔记和概括
更多请参考引用部分</p>
<h1>
<a id="%E4%B8%80%E3%80%81%E5%B9%B6%E5%8F%91%28Concurrency%29%E5%92%8C%E5%B9%B6%E8%A1%8C%28Parallel%29%E7%9A%84%E5%8C%BA%E5%88%AB" href="#%E4%B8%80%E3%80%81%E5%B9%B6%E5%8F%91%28Concurrency%29%E5%92%8C%E5%B9%B6%E8%A1%8C%28Parallel%29%E7%9A%84%E5%8C%BA%E5%88%AB" class="anchor"></a>一、并发(Concurrency)和并行(Parallel)的区别</h1>
<p>并发和并行是相近的概念。和并发所描述的情况一样,并行也是指两个或多个任务被同时执行。但是严格来讲,并发和并行的概念并是不等同的,两者存在很大的差别。</p>
<p>简单的一张图可以简单明了的理解 并行和并发</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/bed60b84-8679-4af7-9359-9b0e37ff8066.png!large" alt=""></p>
<p>直观来讲,并发是两个等待队列中的人同时去竞争一台咖啡机。现实中可能是两队人轮流交替使用、也可能是争抢使用——这就是竞争。</p>
<p>而并行是每个队列拥有自己的咖啡机,两个队列之间并没有竞争的关系,队列中的某个排队者只需等待队列前面的人使用完咖啡机,然后再轮到自己使用咖啡机。</p>
<p>可以这样理解:
并发是一个处理器同时处理多个任务,而并行多个处理器或者是多核的处理器同时处理多个不同的任务。前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生。</p>
<h1>
<a id="%E4%BA%8C%E3%80%81%E8%BF%9B%E7%A8%8B" href="#%E4%BA%8C%E3%80%81%E8%BF%9B%E7%A8%8B" class="anchor"></a>二、进程</h1>
<p>进程(英语:process),是指计算机中已运行的程序。进程曾经是分时系统的基本运作单位。</p>
<p>它包括一些具体内容比如 独立的内存、系统中独立的PID等。</p>
<p>在时分复用系统中,操作系统在不同的进程之间进行上下文切换,来达到 “并发”的效果。</p>
<p>我们只需要简单的知道,他是一个基本单位,它的切换在操作系统之中,并且他的上下文切换相当的占用时间、和占据内存。</p>
<p>Ruby的一些框架是通过 进程+fork的方式工作的,比如 Sidekiq。以fork进程工作的会存在一切缺点:</p>
<ul>
<li>切换上下文时间相对较长</li>
<li>上下文的内存相对较大</li>
<li>fork的子进程,当父进程死掉,会成为僵尸进程,等待系统回收(占用内存)</li>
</ul>
<h1>
<a id="%E4%B8%89%E3%80%81%E7%BA%BF%E7%A8%8B" href="#%E4%B8%89%E3%80%81%E7%BA%BF%E7%A8%8B" class="anchor"></a>三、线程</h1>
<p>线程(英语:thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。</p>
<p>然后操作系统也会在进程之中,调度内部的线程,假设是多个线程,会在其中切换不同线程执行。</p>
<p>相对进程,线程弥补了进程切换的一切问题</p>
<h2>
<a id="3.1+%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84%E6%84%8F%E4%B9%89" href="#3.1+%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%9A%84%E6%84%8F%E4%B9%89" class="anchor"></a>3.1 多线程的意义</h2>
<p>多线程的优点</p>
<ul>
<li>共享内存</li>
<li>切换上下文时间段</li>
<li>占用内存少</li>
<li>父进程关闭,子进程自动关闭</li>
<li>任务分片</li>
</ul>
<p>多线程的意义和操作系统的时间切片其实是相当的。
如果没有多线程,我们的程序会是如何?</p>
<p>以小汽车移动举例子,如下两个颜色的小汽车,A、B,如果我们想移动他们,只能顺序移动。</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/abfba4a0-3393-4441-bebc-f45079e45c2d.gif!large" alt=""></p>
<p>如果是支持多线程程序,A、B 可以同时移动。比如一些游戏的坦克大战多个坦克运动;比如你可以在一个程序中既能聊天还能播放音乐比如浏览器等等。</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/8a7a139f-c59d-407c-926f-08d2b34b94d8.gif!large" alt=""></p>
<h2>
<a id="3.2+%E7%BA%BF%E7%A8%8B%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98" href="#3.2+%E7%BA%BF%E7%A8%8B%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98" class="anchor"></a>3.2 线程存在的问题</h2>
<ul>
<li>竞争带来的复杂问题,涉及到锁</li>
</ul>
<p>就像前面,并行并发 提到的咖啡机模型,多个线程,他们在使用CPU会产生竞争问题,分配给谁?一般是交给操作系统来调度。但是当他们去访问同一个内存读写的时候,由于无法保证顺序,常常会出现问题——也就是线程不安全。</p>
<p>举个Ruby的例子</p>
<pre class="highlight"><code class="language-ruby"><span class="n">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">threads</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">a</span>
<span class="nb">sleep</span><span class="p">(</span><span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mi">1</span><span class="p">))</span>
<span class="n">c</span> <span class="o">+=</span> <span class="mi">10</span>
<span class="nb">sleep</span><span class="p">(</span><span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mi">1</span><span class="p">))</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">c</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">threads</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">join</span> <span class="p">}</span>
<span class="nb">puts</span> <span class="n">a</span>
</code></pre>
<p>这段代码要实现的功能很简单,只是把变量a累加10次,每次加10,并且开了10个线程去完成这个任务。正常情况下我们期望的值是a == 100,然而事实却是</p>
<pre class="highlight"><code class="language-ruby">
<span class="o">></span> <span class="n">ruby</span> <span class="n">a</span><span class="p">.</span><span class="nf">rb</span>
<span class="mi">10</span>
<span class="o">></span> <span class="n">ruby</span> <span class="n">a</span><span class="p">.</span><span class="nf">rb</span>
<span class="mi">10</span>
<span class="o">></span> <span class="n">ruby</span> <span class="n">a</span><span class="p">.</span><span class="nf">rb</span>
<span class="mi">30</span>
<span class="o">></span> <span class="n">ruby</span> <span class="n">a</span><span class="p">.</span><span class="nf">rb</span>
<span class="mi">20</span>
</code></pre>
<p>出现这种情况的原因是,当我们的操作执行到一半的时候其他线程介入了,导致了数据混乱。这里为了突出问题,我们采用了sleep方法来把控制权让给其他线程,而在现实中,线程间的上下文切换是由操作系统来调度,我们很难分析出它的具体行为。</p>
<p>现实中为了解决问题,我们需要加锁</p>
<pre class="highlight"><code class="language-ruby">
<span class="n">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">mutex</span> <span class="o">=</span> <span class="no">Mutex</span><span class="p">.</span><span class="nf">new</span>
<span class="n">threads</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="c1"># 加锁</span>
<span class="n">mutex</span><span class="p">.</span><span class="nf">synchronize</span> <span class="k">do</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">a</span>
<span class="nb">sleep</span><span class="p">(</span><span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mi">1</span><span class="p">))</span>
<span class="n">c</span> <span class="o">+=</span> <span class="mi">10</span>
<span class="nb">sleep</span><span class="p">(</span><span class="nb">rand</span><span class="p">(</span><span class="mi">0</span><span class="o">..</span><span class="mi">1</span><span class="p">))</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">c</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">threads</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">t</span><span class="o">|</span> <span class="n">t</span><span class="p">.</span><span class="nf">join</span> <span class="p">}</span>
<span class="nb">puts</span> <span class="n">a</span>
</code></pre>
<p>这样可以保证结果</p>
<pre class="highlight"><code class="language-ruby"><span class="o">></span> <span class="n">ruby</span> <span class="n">a</span><span class="p">.</span><span class="nf">rb</span>
<span class="mi">100</span>
<span class="o">></span> <span class="n">ruby</span> <span class="n">a</span><span class="p">.</span><span class="nf">rb</span>
<span class="mi">100</span>
</code></pre>
<h1>
<a id="%E5%9B%9B%E3%80%81GIL" href="#%E5%9B%9B%E3%80%81GIL" class="anchor"></a>四、GIL</h1>
<p>在Python、Ruby中都存在一个东西叫做 GIL(全局解析器锁)。</p>
<p>GIL起到什么作用呢,以Ruby的MRI解释器为例,存在GIL的MRI只能够实现并发,并无法充分利用CPU的多核特征,实现并行任务,进而减少程序运行时间。</p>
<p>在MRI里面线程只有在拿到GIL锁的时候才能够运行,即便我们创建了多个线程,本质上也就只有一个线程实例能够拿到GIL,如此看来某一时刻便只能有一个线程在运行。</p>
<p>可以考虑下面这样的场景:</p>
<blockquote>
<p>老师给小蓝安排了除草任务,小蓝为了加快速度呼唤了好友小张,然而除草任务需要有锄头才能进行。为此,即便有好友相助但锄头却只有一把所以两个人无法同时完成除草的任务,只有拿到锄头使用权的一方才能够进行除草。这把锄头就像是解析器中的GIL,把小张跟小蓝想象成被创建的两个线程,当两个人的工作效率一样的时候,受限于锄头这个约束并无法同时进行除草任务,只能够交替使用锄头,本质上并不会减少工作时间,反而会在换人的时候(上下文切换)耗费掉一定的时间。</p>
</blockquote>
<p>在一些场景下创建更多的线程并不能真正地减少程序的运行时间,反而有可能会随着进程数量的增加而增加切换上下文的开销,从而导致程序变得更慢。</p>
<h1>
<a id="%E4%BA%94%E3%80%81%E5%8D%8F%E7%A8%8B" href="#%E4%BA%94%E3%80%81%E5%8D%8F%E7%A8%8B" class="anchor"></a>五、协程</h1>
<p>线程是由操作系统来控制切换的。而系统的切换是一种通用的策略,在一些场景下会没必要的浪费时间。</p>
<p>协程 就是一种让程序员去手动切换,这个过程就像 线程之间 可以互相协作 来完成任务,避免不必要的切换。具体如何切换、交给谁,这个根据实际的任务,程序员来判断。</p>
<p>比如 小明做 语文、数学、英语三节课。系统调度采用的是通用策略,他可能的选择是在三个任务之间均衡。
系统不停地在切换,实际上这种切换浪费了大量的时间。</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/7e5d4b64-4229-4330-b67a-0641e603df3b.gif!large" alt=""></p>
<p>我们现实中会这样做,因为这样是更优的选择。这样就减少了无意义的切换提高了效率。</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/495560fa-a789-492d-97ba-c95588db73a8.gif!large" alt=""></p>
<p>比如IO,当我们遇到IO的时候,有几种情况:</p>
<ol>
<li>
<p>单线程就只能等待IO完成再继续。</p>
</li>
<li>
<p>系统无差别调度,工作线程和IO线程之间切换。</p>
</li>
<li>
<p>协程手动调度,遇到IO之后,直接转移控制权给其他代码。 这样可以有目标的去编写非阻塞、吞吐量大的程序。</p>
</li>
</ol>
<h1>
<a id="%E5%85%AD%E3%80%81%E8%A7%A3%E6%94%BEGIL%E5%85%85%E5%88%86%E5%88%A9%E7%94%A8%E5%A4%9A%E6%A0%B8" href="#%E5%85%AD%E3%80%81%E8%A7%A3%E6%94%BEGIL%E5%85%85%E5%88%86%E5%88%A9%E7%94%A8%E5%A4%9A%E6%A0%B8" class="anchor"></a>六、解放GIL充分利用多核</h1>
<p>以Ruby为例,想要去除 4个人干活 共用一个锄头的 GIL的尴尬事情。可以选择使用去除GIL的解释器。
除了GIL的实现,其中包括Rubinius 以及 jruby他们的底层分别用的是C++以及Java实现的,除了去除GIL锁之外,他们还做了其他方面的优化。某些场景下他们都有着比MRI更好的性能。</p>
<p>这就需要你的程序里面避免出现竞争条件。</p>
<p>一些好的例子是 比如Python的Flask框架,他使用对不同线程ID建立起一个map保存request、response上下文巧妙地实现了线程隔离,在处理web的这块可以做到线程安全。</p>
<p>还有一些专门解决多线程的模型</p>
<h2>
<a id="6.1+Actor%E6%A8%A1%E5%9E%8B+Ractor%EF%BC%88%E5%90%88%E6%88%90%E8%AF%8DRuby+Actor%EF%BC%89%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B" href="#6.1+Actor%E6%A8%A1%E5%9E%8B+Ractor%EF%BC%88%E5%90%88%E6%88%90%E8%AF%8DRuby+Actor%EF%BC%89%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B" class="anchor"></a>6.1 Actor模型 Ractor(合成词Ruby Actor)并发模型</h2>
<ul>
<li><a href="https://ruby-china.org/topics/40583">Ractor 下多线程 Ruby 程序指南 </a></li>
</ul>
<p>Ractor 像 Go、Erlang的并发模型看齐</p>
<h3>
<a id="%E5%85%B7%E4%BD%93%E7%9A%84%E5%AE%9E%E7%8E%B0" href="#%E5%85%B7%E4%BD%93%E7%9A%84%E5%AE%9E%E7%8E%B0" class="anchor"></a>具体的实现</h3>
<ul>
<li>
<p><a href="https://github.com/ruby-concurrency/concurrent-ruby">concurrent-ruby</a></p>
</li>
<li>
<p><a href="https://github.com/celluloid/celluloid">celluloid</a></p>
</li>
</ul>
<h2>
<a id="6.2+Guilds+%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B" href="#6.2+Guilds+%E5%B9%B6%E5%8F%91%E6%A8%A1%E5%9E%8B" class="anchor"></a>6.2 Guilds 并发模型</h2>
<ul>
<li><a href="https://ruby-china.org/topics/31348"> Ruby 3 Guilds 并发模型</a></li>
</ul>
<h2>
<a id="6.3+%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B" href="#6.3+%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B" class="anchor"></a>6.3 事件驱动模型</h2>
<p>事件驱动这是一个像JavaScript的原理的一个实现,使用了单线程eventloop的方式进行工作,可以进行大量的IO,而不需要担心线程问题。</p>
<h3>
<a id="%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0" href="#%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0" class="anchor"></a>具体实现</h3>
<ul>
<li><a href="https://github.com/eventmachine/eventmachine">EventMachine</a></li>
</ul>
<h2>
<a id="6.4+%E8%BF%9B%E7%A8%8B%2Bfork%2C+%E8%AE%A9%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AE%8C%E6%88%90%E8%B0%83%E5%BA%A6" href="#6.4+%E8%BF%9B%E7%A8%8B%2Bfork%2C+%E8%AE%A9%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%AE%8C%E6%88%90%E8%B0%83%E5%BA%A6" class="anchor"></a>6.4 进程+fork, 让操作系统完成调度</h2>
<p>这是一种补充,这种方案扎根于Unix、Linux操作系统。
可以充分利用多核新。可以通过 标准库 实现。但是会比 上述线程方案要重。因为进程的内存是隔离的所以不会竞争。</p>
<p>原理可以参考</p>
<ul>
<li><a href="https://book.douban.com/subject/24298701/">理解Unix进程</a></li>
</ul>
<h3>
<a id="%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0" href="#%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0" class="anchor"></a>具体实现</h3>
<p>大名鼎鼎的 unicorn</p>
<ul>
<li><a href="https://github.com/defunkt/unicorn">unicorn</a></li>
</ul>
<p>puma也使用了这个模型。但是puma同样也拥有多线程</p>
<ul>
<li><a href="https://github.com/puma/puma">puma</a></li>
</ul>
<h2>
<a id="%E5%8F%82%E8%80%83" href="#%E5%8F%82%E8%80%83" class="anchor"></a>参考</h2>
<p>并发并行部分</p>
<ul>
<li><a href="https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter1-Introduction/1.1%20What%20is%20concurrency.md">《1.1 什么是并发》</a></li>
</ul>
<p>进程部分</p>
<ul>
<li><a href="https://zh.wikipedia.org/wiki/%E8%A1%8C%E7%A8%8B">进程wiki</a></li>
</ul>
<p>线程部分</p>
<ul>
<li><a href="https://zh.wikipedia.org/wiki/%E7%BA%BF%E7%A8%8B">线程wiki</a></li>
<li><a href="https://www.beansmile.com/blog/posts/thread-talk-by-lan">谈谈ruby的线程</a></li>
</ul>
<p>GIL部分</p>
<ul>
<li><a href="https://ruby-china.org/topics/28415">无人知晓的 GIL</a></li>
<li><a href="www.jstorimer.com/blogs/workingwithcode/8085491-nobody-understands-the-gil">nobody-understands-the-gil</a></li>
</ul>
<p>协程部分</p>
<ul>
<li><a href="https://www.beansmile.com/blog/posts/thread-talk-by-lan">谈谈ruby的线程</a></li>
</ul>
<h2>
<a id="%E5%85%B6%E4%BB%96" href="#%E5%85%B6%E4%BB%96" class="anchor"></a>其他</h2>
<ul>
<li>
<p>Ruby Puma 允许在每个进程中使用多线程, 每个进程都有各自的线程池. 大部分时候不会遇到上面说的竞争问题, 因为每个 HTTP 请求都是在不同的线程处理.</p>
</li>
<li>
<p>Python的Flask设计巧妙在于使用一个map做到了线程隔离,每个线程都有独立的request、response上下文也可以做到巧妙地不同现场处理。</p>
</li>
<li>
<p>Python的Tornado 使用了协程来写出非阻塞的吞吐量更高的web server。</p>
</li>
<li>
<p>Ruby中去除GIL的解释器:除了GIL的实现,其中包括Rubinius 以及 jruby他们的底层分别用的是C++以及Java实现的,除了去除GIL锁之外,他们还做了其他方面的优化。某些场景下他们都有着比MRI更好的性能。</p>
</li>
</ul>
<p><a href="https://mark24code.github.io/%E7%AC%94%E8%AE%B0/2021/10/13/%E5%B9%B6%E8%A1%8C%E5%B9%B6%E5%8F%91%E8%BF%9B%E7%A8%8B%E7%BA%BF%E7%A8%8B%E5%8D%8F%E7%A8%8BGIL%E6%A6%82%E5%BF%B5%E7%AE%80%E6%98%8E%E8%A7%A3%E9%87%8A.html">我的BLOG</a></p>
根据参考文章做了一些简要的笔记和概括
更多请参考引用部分
一、并发(Concurrency)和并行(Parallel)的区别
并发和并行是相近的概念。和并发所描述的情况一样,并行也是指两个或多...
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/283
2021-09-14T11:26:42Z
2022-10-28T14:39:59Z
Sinatra 的 app 模板,提供一些胶水代码支持类似 Rails 的体验
<h3>
<a id="Sinatra%E7%9A%84app%E6%A8%A1%E6%9D%BF%EF%BC%8C%E6%8F%90%E4%BE%9B%E4%B8%80%E4%BA%9B%E8%83%B6%E6%B0%B4%E4%BB%A3%E7%A0%81%E6%94%AF%E6%8C%81%E7%B1%BB%E4%BC%BCRails%E7%9A%84%E4%BD%93%E9%AA%8C" href="#Sinatra%E7%9A%84app%E6%A8%A1%E6%9D%BF%EF%BC%8C%E6%8F%90%E4%BE%9B%E4%B8%80%E4%BA%9B%E8%83%B6%E6%B0%B4%E4%BB%A3%E7%A0%81%E6%94%AF%E6%8C%81%E7%B1%BB%E4%BC%BCRails%E7%9A%84%E4%BD%93%E9%AA%8C" class="anchor"></a>Sinatra的app模板,提供一些胶水代码支持类似Rails的体验</h3>
<p>如果你想灵活的开展工作,又觉得Rails过于庞大(比如Rails6+ 携带一个Node)、文档要读很久,正在犹豫当中。</p>
<p>你恰巧知道Sinatra的存在,15分钟读完的 <code>Sinatra/README</code> 又觉得自己行了,可是Sinatra似乎太简单了,你想要是Sinatra有 MVC和开箱即用的 ORM就好了。</p>
<p>这是最近做一个简单后端项目的沉淀,可以作为一个简单的起点。</p>
<p>一切基于Sinatra+Rack,用一些胶水代码 把 Rack/Sinatra + 配置 + 文件目录 联系在一起开始工作。容易更改,简单明了。</p>
<p>分享一下,可能不够成熟,欢迎碰撞,让我可以学习更多~</p>
<p><a href="https://github.com/Mark24Code/sinatra-app-template">https://github.com/Mark24Code/sinatra-app-template</a></p>
<h1>
<a id="Sinatra+App+Template" href="#Sinatra+App+Template" class="anchor"></a>Sinatra App Template</h1>
<p>Lightweight web framework codebase. Just clone and develop on it.</p>
<p>Tech component: Rack+Sinatra+Sequel and default use Postgresql database.</p>
<p>Add rails-like migration command line helpers.</p>
<h2>
<a id="Openbox+Features" href="#Openbox+Features" class="anchor"></a>Openbox Features</h2>
<h3>
<a id="Apps" href="#Apps" class="anchor"></a>Apps</h3>
<ul>
<li>
<input type="checkbox" checked disabled> Multi Env Configuration</li>
<li>
<input type="checkbox" checked disabled> Multi router DSL base on Rack</li>
<li>
<input type="checkbox" checked disabled> CORS support</li>
<li>
<input type="checkbox" checked disabled> Hot reload</li>
<li>
<input type="checkbox" checked disabled> Custom logger</li>
<li>
<input type="checkbox" checked disabled> ORM base on Sequel'</li>
</ul>
<h3>
<a id="Tasks" href="#Tasks" class="anchor"></a>Tasks</h3>
<ul>
<li>
<input type="checkbox" checked disabled> Rails-like migration helpers</li>
<li>
<input type="checkbox" checked disabled> Test</li>
<li>
<input type="checkbox" checked disabled> Seed</li>
</ul>
<h3>
<a id="CI%26CD" href="#CI%26CD" class="anchor"></a>CI&CD</h3>
<ul>
<li>
<input type="checkbox" checked disabled> Dockerfile</li>
</ul>
<h2>
<a id="Find+helpful+rake+tasks" href="#Find+helpful+rake+tasks" class="anchor"></a>Find helpful rake tasks</h2>
<p><code>rake</code> or <code>rake -T</code></p>
<h2>
<a id="Run+server+%26+develop" href="#Run+server+%26+develop" class="anchor"></a>Run server & develop</h2>
<p><code>rake server:run</code></p>
<h2>
<a id="Production+Server+%26+deploy" href="#Production+Server+%26+deploy" class="anchor"></a>Production Server & deploy</h2>
<p><code>APP_ENV=production bundle exec rake server:run</code></p>
<p>you can also use docker</p>
<p><code>docker built -t <what your docker image label> .</code></p>
<h2>
<a id="Custom+server+%26+database" href="#Custom+server+%26+database" class="anchor"></a>Custom server & database</h2>
<p>You can use DSL to config <code>Key:Value</code> , then you application just use.</p>
<pre class="highlight"><code class="language-ruby"><span class="no">Config</span><span class="o">::</span><span class="no">Default</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span>
<span class="n">set</span> <span class="ss">:app_env</span><span class="p">,</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'APP_ENV'</span><span class="p">){</span> <span class="s1">'development'</span> <span class="p">}</span>
<span class="n">set</span> <span class="ss">:bind</span><span class="p">,</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'HOST'</span><span class="p">)</span> <span class="p">{</span> <span class="s1">'0.0.0.0'</span> <span class="p">}</span>
<span class="n">set</span> <span class="ss">:port</span><span class="p">,</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'PORT'</span><span class="p">)</span> <span class="p">{</span> <span class="mi">3000</span> <span class="p">}</span>
<span class="n">set</span> <span class="ss">:secrets</span><span class="p">,</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'SECRETS'</span><span class="p">)</span> <span class="p">{</span> <span class="s1">'YOU CANNOT GUESS ME'</span> <span class="p">}</span>
<span class="n">set</span> <span class="ss">:max_threads</span><span class="p">,</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s1">'MAX_THREADS'</span><span class="p">)</span> <span class="p">{</span> <span class="mi">5</span> <span class="p">}</span>
<span class="n">set</span> <span class="ss">:database_url</span><span class="p">,</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_URL'</span><span class="p">]</span>
<span class="k">end</span>
<span class="no">Config</span><span class="o">::</span><span class="no">Development</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span>
<span class="n">set</span> <span class="ss">:database_url</span><span class="p">,</span> <span class="s1">'ENV['</span><span class="no">DATABASE_URL</span><span class="s1">']'</span>
<span class="k">end</span>
<span class="no">Config</span><span class="o">::</span><span class="no">Test</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span>
<span class="n">set</span> <span class="ss">:database_url</span><span class="p">,</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'DATABASE_URL'</span><span class="p">]</span>
<span class="k">end</span>
<span class="no">Config</span><span class="o">::</span><span class="no">Production</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span>
<span class="c1"># set :database_url, ENV['DATABASE_URL']</span>
<span class="k">end</span>
</code></pre>
<p>They have an inheritance relationship</p>
<pre class="highlight"><code>Development < Default
Test < Default
Production < Default
</code></pre>
<p>In your code, just use <code>Config</code> directly. <code>core/bootstrap</code> do a work that loaded all necessery mods before your code.</p>
<pre class="highlight"><code class="language-Ruby">Config.current # current env configuration
Config::Development.database_url
Config::Development
Config::Development.database_url
</code></pre>
<p>You can also create your own <code>Config</code> for your single Application:</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">MyConfig</span> <span class="o"><</span> <span class="no">Config</span><span class="o">::</span><span class="no">Base</span>
<span class="k">end</span>
<span class="no">MyConfig</span><span class="p">.</span><span class="nf">configure</span> <span class="k">do</span>
<span class="c1"># set :database_url, ENV['DATABASE_URL']</span>
<span class="k">end</span>
</code></pre>
<h2>
<a id="Mount+different+Sinatra+web+application" href="#Mount+different+Sinatra+web+application" class="anchor"></a>Mount different Sinatra web application</h2>
<p>Edit <code>config.ru</code></p>
<p>Lark also is Rack application. We can use Rack middlewares.</p>
<pre class="highlight"><code class="language-ruby"><span class="nb">require_relative</span> <span class="s1">'./cores/bootstrap'</span>
<span class="no">Bootstrap</span><span class="p">.</span><span class="nf">rack</span>
<span class="c1"># you can load Rack middleware here</span>
<span class="c1"># mount applications</span>
<span class="nb">require</span> <span class="s1">'controllers/root_controller'</span>
<span class="c1"># routers(handy config)</span>
<span class="n">map</span> <span class="s1">'/'</span> <span class="k">do</span>
<span class="n">run</span> <span class="no">RootController</span>
<span class="k">end</span>
</code></pre>
<h1>
<a id="Base" href="#Base" class="anchor"></a>Base</h1>
<p><code>bases</code> directory are use for Application Base Class.</p>
<p>You can make different Configured Sinatra Application class here, then your application/controller just inherit the Base Class to create Application.</p>
<p>It will share Config, and make less code.</p>
<pre class="highlight"><code class="language-ruby"><span class="c1"># Sinatra Doc http://sinatrarb.com/intro.html</span>
<span class="nb">require</span> <span class="s1">'sinatra/base'</span>
<span class="nb">require</span> <span class="s1">'json'</span>
<span class="k">class</span> <span class="nc">BaseController</span> <span class="o"><</span> <span class="no">Sinatra</span><span class="o">::</span><span class="no">Base</span>
<span class="c1"># Inject config</span>
<span class="c1"># Config & register Sinatra Extensions</span>
<span class="c1"># Rewrite Views dir</span>
<span class="n">settings</span><span class="p">.</span><span class="nf">views</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="vg">$PROJECT_DIR</span><span class="p">,</span> <span class="s1">'views'</span><span class="p">))</span>
<span class="n">configure</span> <span class="ss">:development</span> <span class="k">do</span>
<span class="nb">require</span> <span class="s1">'sinatra/reloader'</span>
<span class="n">register</span> <span class="no">Sinatra</span><span class="o">::</span><span class="no">Reloader</span>
<span class="k">end</span>
<span class="c1"># mount Sinatra Helpers</span>
<span class="c1"># mount Sinatra middlewares</span>
<span class="k">end</span>
<span class="c1"># Share Configuration</span>
<span class="k">class</span> <span class="nc">MyPageServer</span> <span class="o"><</span> <span class="no">BaseController</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">MyApiServer</span> <span class="o"><</span> <span class="no">BaseController</span>
<span class="k">end</span>
</code></pre>
<h1>
<a id="ORM+%26+Tools" href="#ORM+%26+Tools" class="anchor"></a>ORM & Tools</h1>
<p>Provide rails-like rake task help you build app quickly.</p>
<pre class="highlight"><code>rake db:check # Checking for current migrations
rake db:connect # Connect database
rake db:console # Database Console
rake db:create[database_name] # Create database
rake db:create_migration[name] # Create a migration
rake db:drop[database_name] # Drop database
rake db:ls # List database tables
rake db:migrate[version] # Run migrations
rake db:rollback[version] # Rollback to migration
rake db:version # Prints current schema version
rake list # List all tasks
rake seed:all # Seed: run all seeds
rake seed:run[seed_name] # Seed: run seed
rake server:run # Run server
rake test # Run tests
</code></pre>
<h1>
<a id="Project+Structure" href="#Project+Structure" class="anchor"></a>Project Structure</h1>
<pre class="highlight"><code>.
├── Dockerfile # Common Dockerfile
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile # Rake Task Index File.
├── bases # Base configured class. You can make different BaseClasses then reuse them.
│ └── base_controller.rb # You contoller can inherit it or write yourself.
├── config.ru # Application index. You can mount controllers and routes here.
├── configs # You can make different configs for applications
│ └── config.rb # Base config
├── controllers
│ └── root_controller.rb
├── cores # Inject ENVS and autoloads files, make MVC works
│ ├── 01_config.rb # Names can controller mount order
│ └── bootstrap.rb
├── dbs # You can make multi database here
│ ├── default_db.rb # default database connect instance
│ └── migrations # save database migrations
├── docs
│ └── good.feature
├── log # Directory for save logs by default
│ └── development.log
├── loggers # Loggers for application
│ └── default_logger.rb
├── public # Public resources
│ └── favicon.svg
├── seeds # Seeds
├── tasks # Rake helpful tasks
│ ├── db_task.rb
│ ├── seed_task.rb
│ ├── server_task.rb
│ └── test_task.rb
├── tests # Test cases
│ └── test_demo.rb
└── views # views template
├── base.erb
└── root.erb
</code></pre>
<h1>
<a id="Bootstrap+%26+Load+orders" href="#Bootstrap+%26+Load+orders" class="anchor"></a>Bootstrap & Load orders</h1>
<h2>
<a id="For+Rake" href="#For+Rake" class="anchor"></a>For Rake</h2>
<pre class="highlight"><code>require_relative './cores/bootstrap'
Bootstrap.rake
</code></pre>
<p>It will auto load files make sure rake task can work.</p>
<p>In rake we can use <code>Config.current</code> to read configuration.</p>
<p><code>DB</code> also available.</p>
<h2>
<a id="For+Rack%2FApplications" href="#For+Rack%2FApplications" class="anchor"></a>For Rack/Applications</h2>
<p>In the same way</p>
<pre class="highlight"><code>require_relative './cores/bootstrap'
Bootstrap.rack
# OR
# Bootstrap.apps
</code></pre>
<p>It will autoload all dep mods. Share with a context.</p>
<h2>
<a id="Change+load+orders" href="#Change+load+orders" class="anchor"></a>Change load orders</h2>
<p><code>cores/bootstrap.rb</code> defines different load orders, you can change.</p>
<p>In anther way, you can change filename to e.g <code>00_before_all.rb</code> 、<code>01_first_load.rb</code> to control mods load order.</p>
Sinatra 的 app 模板,提供一些胶水代码支持类似 Rails 的体验
Mark24
https://geeknote.net/mark24
https://geeknote.net/mark24/posts/213
2021-07-29T10:31:11Z
2022-10-28T14:38:50Z
Ruby 的方法查找再往前一步
<p>有更好的方法可以告诉我,我最新在学习Ruby</p>
<p>最新的修改会更新在BLOG</p>
<p><a href="https://mark24code.github.io/ruby/2021/07/29/Ruby%E7%9A%84%E6%96%B9%E6%B3%95%E6%9F%A5%E6%89%BE%E5%86%8D%E5%BE%80%E5%89%8D%E4%B8%80%E6%AD%A5.html">我的博客</a></p>
<p><a href="https://ruby-china.org/topics/41505">RubyChina讨论帖</a></p>
<hr>
<h1>
<a id="%E8%83%8C%E6%99%AF" href="#%E8%83%8C%E6%99%AF" class="anchor"></a>背景</h1>
<p>《Ruby元编程(第二版)》 5.4节 单件类 在 Page125 这页,讲了一种情况:</p>
<pre class="highlight"><code class="language-ruby">
<span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">a_method</span>
<span class="s1">'C#a_method()'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">C</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">a_class_method</span>
<span class="s1">'#C.a_class_method() #singleton'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span> <span class="o"><</span> <span class="no">C</span><span class="p">;</span><span class="k">end</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">D</span><span class="p">.</span><span class="nf">new</span>
<span class="no">D</span><span class="p">.</span><span class="nf">a_class_method</span> <span class="c1"># => '#C.a_class_method() #singleton'</span>
</code></pre>
<p>D.a_class_method 他是如何查找的呢?</p>
<p>本文就是寻找这个的答案。讲的是Ruby的方法查找再往前走一步。</p>
<p>为了说明这个问题,先要啰嗦的做一些铺垫。</p>
<p>下文中,此书简称为《元编程》</p>
<h1>
<a id="%E4%B8%80%E3%80%81+Ruby%E7%9A%84%E7%BB%A7%E6%89%BF%E7%BB%93%E6%9E%84" href="#%E4%B8%80%E3%80%81+Ruby%E7%9A%84%E7%BB%A7%E6%89%BF%E7%BB%93%E6%9E%84" class="anchor"></a>一、 Ruby的继承结构</h1>
<p><img src="https://l.ruby-china.com/photo/Mark24/f255ac04-b557-4b93-bc51-041d9d769f5c.png!large" alt=""></p>
<p>这张图实在总结的太美丽了。先放在这里。图片的出处,可以参考文末。</p>
<h1>
<a id="%E4%BA%8C%E3%80%81Ruby%E4%B8%80%E8%88%AC%E6%96%B9%E6%B3%95%E6%9F%A5%E6%89%BE%E8%A7%84%E5%88%99" href="#%E4%BA%8C%E3%80%81Ruby%E4%B8%80%E8%88%AC%E6%96%B9%E6%B3%95%E6%9F%A5%E6%89%BE%E8%A7%84%E5%88%99" class="anchor"></a>二、Ruby一般方法查找规则</h1>
<p>《元编程》里面里面总结了Ruby的查找规则:</p>
<blockquote>
<p>“向右一步,然后向上查找”。</p>
</blockquote>
<p>意思就是,向右寻找他的父,然后开始往上寻找继承关系,通过这种方式查找方法。</p>
<p>比如以下代码</p>
<pre class="highlight"><code class="language-ruby"><span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">a_method</span>
<span class="s1">'C#a_method()'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span> <span class="o"><</span> <span class="no">C</span><span class="p">;</span><span class="k">end</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">D</span><span class="p">.</span><span class="nf">new</span>
<span class="n">obj</span><span class="p">.</span><span class="nf">a_method</span>
</code></pre>
<p>obj如何查找 a_method 方法呢?</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/2229f594-96d4-4bb8-b8cf-219d09742522.jpeg!large" alt=""></p>
<p>如果我们给 obj对象添加单例类,他会如何查找呢?</p>
<pre class="highlight"><code class="language-ruby">
<span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">a_method</span>
<span class="s1">'C#a_method()'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span> <span class="o"><</span> <span class="no">C</span><span class="p">;</span><span class="k">end</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">D</span><span class="p">.</span><span class="nf">new</span>
<span class="c1"># 定义单例类</span>
<span class="k">class</span> <span class="o"><<</span> <span class="n">obj</span>
<span class="k">def</span> <span class="nf">a_singleton_method</span>
<span class="s2">"obj#a_singleton_method"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">obj</span><span class="p">.</span><span class="nf">a_singleton_method</span>
</code></pre>
<p>obj.a_singleton_method 会如何查找方法呢?</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/37d0a87c-f8ca-4aa8-884b-b84db0ccc493.jpeg!large" alt=""></p>
<p>可以通过一下方式检验</p>
<pre class="highlight"><code class="language-ruby">
<span class="n">obj</span><span class="p">.</span><span class="nf">singleton_class</span><span class="p">.</span><span class="nf">superclass</span> <span class="c1"># => D</span>
</code></pre>
<p>他会按照如图的方式,其实实例对象创造了一个 单例类 可以标记为 #obj ,用#表示单例类。
#obj会出现在对象和真正的类中间。</p>
<p>我们也能用上面</p>
<blockquote>
<p>“向右一步,然后向上查找”。</p>
</blockquote>
<p>来指导我们查找,只不过对象存在一个单例类罢了。</p>
<h1>
<a id="%E4%B8%89%E3%80%81%E6%96%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%87%BA%E7%8E%B0" href="#%E4%B8%89%E3%80%81%E6%96%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%87%BA%E7%8E%B0" class="anchor"></a>三、新的问题出现</h1>
<p>但是问题来了,回到文章的最开头。</p>
<pre class="highlight"><code class="language-ruby">
<span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">a_method</span>
<span class="s1">'C#a_method()'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">C</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">a_class_method</span>
<span class="s1">'#C.a_class_method() #singleton'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span> <span class="o"><</span> <span class="no">C</span><span class="p">;</span><span class="k">end</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">D</span><span class="p">.</span><span class="nf">new</span>
<span class="no">D</span><span class="p">.</span><span class="nf">a_class_method</span> <span class="c1"># => '#C.a_class_method() #singleton'</span>
</code></pre>
<p>这个例子。在类C上定义了单例方法,并且我们指导所有东西在Ruby里都是对象,都可以定义单例方法。</p>
<p>这就是文章开头最先的图片。所有的类都可以定义单例类。这种情况下,D.a_class_method应该如何查找呢?</p>
<blockquote>
<p>“向右一步,然后向上查找”。</p>
</blockquote>
<p>似乎帮不了我们了。因为我们面临一个问题,让我来描述下:</p>
<p>我们把D当做一个对象,开始寻找他的方法。</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/5aa37d39-2815-4838-903d-7413982be067.png!large" alt=""></p>
<p>拿这幅图做例子:</p>
<p>Dog开始寻找定义的方法,向右一步,进入自己的 单例类 #Dog,然后应该做什么,选择向上么?是走 他的父类Class,还是 应该往 单例类的继承链往上找呢?</p>
<p>《元编程》文末的几句话,似乎在暗示黄色这条线的寻找方向,但是作者并没有真正说清楚:</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/2f72104e-8835-4bde-a722-afc0fa7a7810.png!large" alt=""></p>
<h1>
<a id="%E4%B8%89%E3%80%81%E5%AF%BB%E6%89%BE%E7%AD%94%E6%A1%88" href="#%E4%B8%89%E3%80%81%E5%AF%BB%E6%89%BE%E7%AD%94%E6%A1%88" class="anchor"></a>三、寻找答案</h1>
<p>我先放出答案,如下图所示:</p>
<p><img src="https://l.ruby-china.com/photo/Mark24/1ccdcb33-7370-4e11-813e-179d8afe6047.jpeg!large" alt=""></p>
<p>对象的方法,遵循</p>
<blockquote>
<p>“向右一步,然后向上查找”。</p>
</blockquote>
<p>类方法的查找是我们关心的,可以看到实际结果是,它沿着继承的单例类一路向上,然后再进入父类。</p>
<p>寻找这个答案的过程中,我看了挺多资料和文字,还有问一些Ruby方面的朋友都没有真正分析到这一步。</p>
<p>我最后是怎么找到答案的呢? 这就得借助Ruby自身完善的自省机制。(吐槽,其他语言可能都没有实现的那么细致)。</p>
<p>其实Ruby自身的很多属性都绑定在自身了,直接向Ruby问答案就好了</p>
<pre class="highlight"><code class="language-ruby">
<span class="k">class</span> <span class="nc">C</span>
<span class="k">def</span> <span class="nf">a_method</span>
<span class="s1">'C#a_method()'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">C</span>
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
<span class="k">def</span> <span class="nf">a_class_method</span>
<span class="s1">'#C.a_class_method() #singleton'</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">D</span> <span class="o"><</span> <span class="no">C</span><span class="p">;</span><span class="k">end</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">D</span><span class="p">.</span><span class="nf">new</span>
<span class="no">D</span><span class="p">.</span><span class="nf">a_class_method</span> <span class="c1"># => '#C.a_class_method() #singleton'</span>
</code></pre>
<p>我们知道 obj.ancestors 可以打印继承关系,但是这个很遗憾的是它不会打印 单例类。</p>
<p>单例类实际上是一个隐藏的存在。这也就是研究这个问题很难得地方,因为隐藏,似乎只能通过源码和外部资料去查看。</p>
<p>实际上我们是可以拿到 obj.singleton_class的,然后我们前面分析了一些结论,大致给出了一个对象的继承模型。</p>
<pre class="highlight"><code class="language-ruby"><span class="n">obj</span><span class="p">.</span><span class="nf">singleton_class</span><span class="p">.</span><span class="nf">ancestors</span>
<span class="c1"># => [#<Class:#<D:0x00007feae092efc8>>, D, C, Object, Kernel, BasicObject]</span>
</code></pre>
<p>就可以打印出,对象查找的顺序。</p>
<p>同理,我们想要知道D的方法的查找顺序</p>
<pre class="highlight"><code class="language-ruby"><span class="no">D</span><span class="p">.</span><span class="nf">singleton_class</span><span class="p">.</span><span class="nf">ancestors</span>
<span class="c1"># => [#<Class:D>, #<Class:C>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]</span>
</code></pre>
<p>这个其实就是D查找方法的顺序,可以看到,他先是把所有的单例类走了一遍,然后开始进入自己的父。</p>
<h1>
<a id="%E5%9B%9B%E3%80%81%E6%80%BB%E7%BB%93" href="#%E5%9B%9B%E3%80%81%E6%80%BB%E7%BB%93" class="anchor"></a>四、总结</h1>
<p>最后,这句话</p>
<blockquote>
<p>“向右一步,然后向上查找”。</p>
</blockquote>
<p>有了新内涵, 向右一步的过程中,优先的走单例类(如果有的话)以及单例的继承,结束后,开始进入自己真正的父,即向上在继承关系中寻找。</p>
<p>单例类也可以看成是一种外挂方法(比喻不严谨但是很好理解),先在外挂方法里找,也可以顺着外挂继承链找。找不到再到继承关系里面找。</p>
<h1>
<a id="%E9%A2%98%E5%A4%96%E8%AF%9D" href="#%E9%A2%98%E5%A4%96%E8%AF%9D" class="anchor"></a>题外话</h1>
<p>有人可能会问,为啥继承体系要搞得那么复杂?</p>
<p>借用 《元编程》里面的一句</p>
<blockquote>
<p>这样你就可以在 D中 调用 C的方法了。</p>
</blockquote>
<p>把对象穿成链表,然后相当于你可以拥有和复用这个链条上所有的方法。</p>
<p>元编程的一部分思想也就是动态的修改、创造、转发方法。还有 《元编程》里面提到的 “自由方法”我的理解就像是把继承链中某些方法复制,然后粘贴到当前对象执行,在继承链上跳跃执行方法……</p>
<p>这一切都是为了极大地自由。</p>
<p>我以前一致不太理解“Ruby是快乐优先”这句话是什么意思,现在我的理解——这种快乐就是自由,拥有自由的快乐。</p>
<h1>
<a id="%E5%85%B6%E4%BB%96" href="#%E5%85%B6%E4%BB%96" class="anchor"></a>其他</h1>
<p>图片来自文章</p>
<ul>
<li><a href="https://draveness.me/metaprogramming/">谈元编程与表达能力</a></li>
</ul>
<p>安利一波作者图片配色</p>
<ul>
<li><a href="https://draveness.me/sketch-and-sketch/">技术文章配图指南</a></li>
</ul>
<h1>
<a id="%E5%8F%82%E8%80%83" href="#%E5%8F%82%E8%80%83" class="anchor"></a>参考</h1>
<p>书籍推荐 <a href="https://book.douban.com/subject/26575429/">《Ruby元编程(第2版)》</a></p>
<h1>
<a id="BLOG" href="#BLOG" class="anchor"></a>BLOG</h1>
<p>有更好的方法可以告诉我,我最新在学习Ruby</p>
<p>最新的修改会更新在BLOG</p>
<p><a href="https://mark24code.github.io/ruby/2021/07/29/Ruby%E7%9A%84%E6%96%B9%E6%B3%95%E6%9F%A5%E6%89%BE%E5%86%8D%E5%BE%80%E5%89%8D%E4%B8%80%E6%AD%A5.html">我的博客</a></p>
<p><a href="https://ruby-china.org/topics/41505">RubyChina讨论帖</a></p>
Ruby方法查找更进一步探索
Mark24
https://geeknote.net/mark24