<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Mark24 (MARK24)</title>
    <link>https://ruby-china.org/Mark24</link>
    <description>多喝热水，重启试试</description>
    <language>en-us</language>
    <item>
      <title>【RubyConf 2023】The Future of Understanding Ruby Code</title>
      <description>&lt;h2 id="The Future of Understanding Ruby Code by Kevin Newton"&gt;The Future of Understanding Ruby Code by Kevin Newton&lt;/h2&gt;
&lt;p&gt;理解 Ruby 的未来：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个由运行时组成的生态系统，每个运行时都可以专注于使其独特的功能。&lt;/li&gt;
&lt;li&gt;一个由工具组成的生态系统，所有工具都对 Ruby 表示达成一致。&lt;/li&gt;
&lt;li&gt;降低参与整个生态系统贡献的门槛。&lt;/li&gt;
&lt;li&gt;一个携手合作的社区，共同赋能每一个工具和每一个人。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/f19e8e9c-3102-4943-93b3-057aa87766a0.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;个人看完这个还挺有启发的，OP 的观点十分动人，社区应该是以人为本的，降低贡献门槛。所有困难领域并不应该是 高级学位的人专属。每个人都可以做出贡献。
如果一个 BUG 你不知道如何解决，这本身就是一个 BUG，说明资料匮乏，文档缺失，想做贡献的人无从下手。
Ruby 社区在快速萎靡，应该增强文档的建设。&lt;/p&gt;

&lt;p&gt;最后感谢 OP 的工作，他在这个领域坚持了 6 年。Respect 🐂🍺&lt;/p&gt;

&lt;p&gt;&lt;span class="embed-responsive embed-responsive-16by9"&gt;&lt;iframe class="embed-responsive-item" src="//www.youtube.com/embed/n3h2uPDZ2LY" allowfullscreen=""&gt;&lt;/iframe&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Wed, 12 Feb 2025 11:54:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/44045</link>
      <guid>https://ruby-china.org/topics/44045</guid>
    </item>
    <item>
      <title>【翻译】为什么每个人都讨厌 fork(2) ？</title>
      <description>&lt;ul&gt;
&lt;li&gt;作者：&lt;a href="https://github.com/byroot" rel="nofollow" target="_blank" title=""&gt;Jean Boussier&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者：&lt;a href="https://github.com/Mark24Code" rel="nofollow" target="_blank" title=""&gt;Mark24&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文：&lt;a href="https://byroot.github.io/ruby/performance/2025/01/25/why-does-everyone-hate-fork.html" rel="nofollow" target="_blank" title=""&gt;why-does-everyone-hate-fork&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我想写一篇关于 &lt;a href="https://rubygems.org/gems/pitchfork" rel="nofollow" target="_blank" title=""&gt;Pitchfork&lt;/a&gt; 的文章，解释它的起源、为什么它会是现在这个样子，以及我对其未来的看法。但在达到这一点之前，我认为我需要解释一些事情，fork 被认为是一种过时的旧物，甚至可以说是“恶魔的创造”。然而，在 Ruby 生态系统中，它却无处不在。&lt;/p&gt;

&lt;p&gt;请注意，如果您有一些系统编程经验，您在这里可能学不到太多。&lt;/p&gt;

&lt;p&gt;如果您曾经部署过 Ruby 应用程序到生产环境，那么您几乎肯定已经与 &lt;code&gt;fork(2)&lt;/code&gt; 打过交道，无论您是否意识到。您是否配置过 Puma 的 worker 设置？嗯，Puma 使用 &lt;code&gt;fork(2)&lt;/code&gt; 来启动这些工作进程，更准确地说，是 Ruby 的 &lt;a href="https://devdocs.io/ruby~3.4/process#method-c-_fork" rel="nofollow" target="_blank" title=""&gt;Process.fork&lt;/a&gt; 方法，这是 Ruby API 对底层 &lt;code&gt;fork(2)&lt;/code&gt; 系统调用的封装。&lt;/p&gt;

&lt;p&gt;即使你不是 Ruby 开发者，如果你使用过 PHP、Nginx、Apache HTTPd、Redis 等，你也使用了一个高度依赖 &lt;code&gt;fork(2)&lt;/code&gt; 的系统。&lt;/p&gt;

&lt;p&gt;然而，许多人会认为 &lt;code&gt;fork(2)&lt;/code&gt; 是邪恶的，不应该被使用。我个人有点既同意又不同意这种观点，我将尝试解释原因。&lt;/p&gt;
&lt;h2 id="一点历史"&gt;一点历史&lt;/h2&gt;
&lt;p&gt;根据维基百科，fork 概念首次出现可以追溯到 1962 年，由提出&lt;a href="https://en.wikipedia.org/wiki/Conway%27s_law" rel="nofollow" target="_blank" title=""&gt;康威定律 (Conway’s law)&lt;/a&gt;的同一个人提出，后来在 UNIX 的第一个版本中引入。&lt;/p&gt;

&lt;p&gt;最初，它被设计为一种用于创建新进程的原语。你会调用 &lt;code&gt;fork(2)&lt;/code&gt; 来复制当前进程，然后从那里开始将这个新进程修改为你想要的样子，紧接着调用 &lt;code&gt;exec(2)&lt;/code&gt; ，我们一般这样使用。直到今天，你仍然可以在 Ruby 中这样做。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：一切不懂这方面的读者可能觉得莫名其妙。这地方介绍的不清楚，我补充一点。这是 Unix/Linux 操作系统，创建子进程的标准方法。先调用 fork 这样会迅速的从当前进程复制一份出来，然后紧接着执行 exec 传入具体 bash 的命令。这样就创建了一个子进程，并且关联了当前进程为父进程。exec 执行内容会占据 之前 fork 的进程，作为一个独立进程执行。这样设计存在历史原因，也相当聪明，等于复制模版，再更改模板内的内容。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# We're in the parent process, and we know the child process ID.&lt;/span&gt;
  &lt;span class="c1"&gt;# We can wait for the child to exit or send signals etc.&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="c1"&gt;# We're in the child process.&lt;/span&gt;
  &lt;span class="c1"&gt;# We can change the current user and other attributes.&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="c1"&gt;# And then we can replace the current program with another.&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"echo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从某种意义上说，这种设计相当优雅。你拥有少量简单的基础组件，可以将它们组合在一起以获得你所需要的精确行为，而不是一个庞大的函数，需要传递无数个参数。&lt;/p&gt;

&lt;p&gt;但这种方法也非常低效，因为完全复制一个进程来创建一个新的进程通常是小题大做。在上面的例子中，如果你想象我们的父程序有数 GB 字节的可寻址内存，那么将所有这些内存复制过来，然后几乎立刻将其全部丢弃，以便用一个极其小的程序（如/bin/echo）来替换，这是一种巨大的浪费。&lt;/p&gt;

&lt;p&gt;当然，现代操作系统实际上并不会复制所有这些内容，而是使用写时复制（Copy-on-Write），但这仍然非常昂贵，如果父进程很大，很容易就需要数百毫秒。&lt;/p&gt;

&lt;p&gt;这是因为使用 &lt;code&gt;fork(2)&lt;/code&gt; 来启动其他程序的历史用法现在大多被认为是过时的，大多数新的软件将使用更现代的 API，如 &lt;code&gt;posix_spawn(3)&lt;/code&gt; 或 &lt;code&gt;vfork(2)+exec(2)&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;但 &lt;code&gt;fork(2)&lt;/code&gt; 的用途并不仅限于此。我不知道这是否从一开始就被设计好了，还是后来才逐渐形成的一种用法，但我前面提到的所有软件都使用了 &lt;code&gt;fork(2)&lt;/code&gt;，而且从未在其后调用过 &lt;code&gt;exec(2)&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="Fork 作为并行原语"&gt;Fork 作为并行原语&lt;/h2&gt;
&lt;p&gt;再次，我甚至不是在七十年代初出生的，所以我不太确定这种做法究竟是从什么时候开始的，但某个时候 &lt;code&gt;fork(2)&lt;/code&gt; 开始被用作并行性原语，尤其是在服务器方面。&lt;/p&gt;

&lt;p&gt;让我们假设您想从头开始实现一个简单的“echo”服务器，在 Ruby 中可能看起来像这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'socket'&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TCPServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gets&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该脚本首先在端口 8000 上打开一个监听套接字，然后阻塞在 &lt;code&gt;accept(2)&lt;/code&gt; 系统调用上等待客户端连接。当该方法返回时，它给我们一个双向套接字，我们可以从中读取，在这种情况下使用 &lt;code&gt;#gets&lt;/code&gt; ，也可以向客户端写回。&lt;/p&gt;

&lt;p&gt;虽然这使用了现代 Ruby，那与当时各种服务器的编写方式非常相似，但过于简化。&lt;/p&gt;

&lt;p&gt;如果您想玩它，可以使用 &lt;code&gt;telnet localhost 8000&lt;/code&gt; 开始编写内容。&lt;/p&gt;

&lt;p&gt;但是那个服务器有一个大问题：它只支持单个并发用户。如果你尝试同时开启两个 &lt;code&gt;telnet&lt;/code&gt; 会话，你会看到第二个无法连接。&lt;/p&gt;

&lt;p&gt;所以人们开始利用 &lt;code&gt;fork(2)&lt;/code&gt; 来支持更多用户：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'socket'&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TCPServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;
  &lt;span class="c1"&gt;# prune exited children&lt;/span&gt;
  &lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WNOHANG&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;child_pid&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gets&lt;/span&gt;
      &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
    &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;逻辑与之前相同，但现在一旦 &lt;code&gt;accept(2)&lt;/code&gt; 返回一个套接字，我们不再在它上面阻塞，而是 &lt;code&gt;fork(2)&lt;/code&gt; 一个新的子进程，并让那个子进程执行阻塞操作，直到客户端关闭连接。&lt;/p&gt;

&lt;p&gt;如果您是一位敏锐的读者（或者您已经对 &lt;code&gt;fork(2)&lt;/code&gt; 语义有所了解），您可能已经注意到在调用 &lt;code&gt;fork&lt;/code&gt; 之后，父进程和新的子进程都可以访问套接字。这是因为，在 UNIX 中，套接字是“文件”，因此由“文件描述符”表示，而 &lt;code&gt;fork(2)&lt;/code&gt; 语义的一部分是所有文件描述符都可以继承。&lt;/p&gt;

&lt;p&gt;这就是为什么重要的是让父进程关闭套接字，否则，它将在父进程中永远保持打开状态 (技术上，一旦对象被垃圾回收，Ruby 会自动关闭它，但你明白了这个意思)，这也是许多人讨厌 fork(2) 的第一个原因之一。&lt;/p&gt;
&lt;h2 id="一把双刃剑"&gt;一把双刃剑&lt;/h2&gt;
&lt;p&gt;如上所示，子进程继承所有打开的文件描述符的事实允许实现一些非常有用的事情，但如果你忘记关闭一个你不想共享的文件描述符，这也可能导致灾难性的错误。&lt;/p&gt;

&lt;p&gt;例如，如果您正在 fork 一个与 SQL 数据库有活动连接的进程，并且您在两个进程中都继续使用该连接，会发生奇怪的事情：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/inline"&lt;/span&gt;
&lt;span class="n"&gt;gemfile&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"trilogy"&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"bigdecimal"&lt;/span&gt; &lt;span class="c1"&gt;# for trilogy&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trilogy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;child_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fork&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="c1"&gt;# Give some time to the child&lt;/span&gt;

  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:KILL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;child_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child_pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SELECT "oops"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里脚本使用 trilogy 客户端连接到 MySQL，然后在一个循环中无限查询 SELECT "oops" ，然后创建一个子进程。一旦子进程被创建，父进程发出 5 个查询，每个查询应该返回一个从 0 到 4 的单个数字，并打印其结果。&lt;/p&gt;

&lt;p&gt;如果您运行此脚本，您将得到一些随机的输出，类似于这样：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"oops"&lt;/span&gt;
1
&lt;span class="s2"&gt;"oops"&lt;/span&gt;
&lt;span class="s2"&gt;"oops"&lt;/span&gt;
3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里发生的情况是，两个进程都在同一个套接字内写入。对于 MySQL 服务器来说，这不是什么大问题，因为我们的查询很小，所以它们会被“原子性地”写入套接字。如果我们发出更大的查询，两个查询可能会交错，这会导致服务器以某种协议错误的形式关闭连接。&lt;/p&gt;

&lt;p&gt;但是对客户来说，这真的很糟糕。因为两个进程的响应都通过同一个套接字发送回来，每个客户端都在发出 &lt;code&gt;read(2)&lt;/code&gt; ，可能会收到它刚刚发出的查询的响应，但也可能收到另一个由其他进程发出的无关查询的响应。&lt;/p&gt;

&lt;p&gt;当两个进程尝试在同一个套接字上 &lt;code&gt;read(2)&lt;/code&gt; 时，它们各自获取部分数据，但你无法正确控制哪个进程获取什么，尝试同步这两个进程以使它们各自获得预期的响应是不切实际的。&lt;/p&gt;

&lt;p&gt;考虑到这一点，你可以想象在调用 &lt;code&gt;fork(2)&lt;/code&gt; 之前正确关闭应用程序的所有套接字和其他打开的文件会有多大的麻烦。也许你在自己的代码中会非常勤奋，但你可能正在使用一些可能不会期望调用 &lt;code&gt;fork(2)&lt;/code&gt; 并且不允许你关闭它们的文件描述符的库。&lt;/p&gt;

&lt;p&gt;对于 &lt;code&gt;fork+exec&lt;/code&gt; 用例，有一个很棒的功能让这变得容易得多，你可以在调用 &lt;code&gt;exec&lt;/code&gt; 时标记一个文件描述符需要关闭，操作系统会为你处理这个， &lt;code&gt;O_CLOEXEC&lt;/code&gt; （在 exec 时关闭），在 Ruby 中方便地作为 IO 类上的一个方法公开：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;STDIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close_on_exec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，当它后面没有跟随着一个 &lt;code&gt;exec&lt;/code&gt; 时， &lt;code&gt;fork&lt;/code&gt; 系统调用就没有这样的标志。或者更准确地说，有一个， &lt;code&gt;O_CLOFORK&lt;/code&gt; ，它存在于一些 UNIX 系统上，主要是 IBM 的系统，并在 2020 年添加到了 POSIX 规范中。但今天它并不被广泛支持，最重要的是 Linux 不支持它。有人在 2011 年提交了一个补丁，将其添加到 Linux 中，但似乎对此没有太多兴趣，另一个人在 2020 年又尝试了一次，但遇到了一些强烈的反对，这很遗憾，因为它会非常有用。&lt;/p&gt;

&lt;p&gt;相反，大多数想要实现分支安全的代码所做的是，它尝试通过持续检查当前进程 ID 来检测是否发生了分支：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="vi"&gt;@old_pid&lt;/span&gt;
    &lt;span class="vi"&gt;@connection.close&lt;/span&gt;
    &lt;span class="vi"&gt;@connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="vi"&gt;@old_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pid&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="vi"&gt;@connection&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;
  &lt;span class="vi"&gt;@connection.query&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者依赖某些 &lt;code&gt;at_fork&lt;/code&gt; 回调，在 C 语言中通常是指 &lt;code&gt;pthread_atfork&lt;/code&gt; ，自从 Ruby 3.1 以来，你可以封装 &lt;code&gt;Process._fork&lt;/code&gt; （注意 _）：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MyLibraryAtFork&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_fork&lt;/span&gt;
    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="c1"&gt;# in child&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;# in parent&lt;/span&gt;
      &lt;span class="no"&gt;MyLibrary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close_all_ios&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;pid&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;singleton_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;MyLibraryAtFork&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于 &lt;code&gt;fork(2)&lt;/code&gt; 在 Ruby 中非常普遍，许多处理套接字的流行库，如 &lt;code&gt;Active Record&lt;/code&gt; 或 &lt;code&gt;redis&lt;/code&gt; gem，都尽力透明地处理这个问题，所以你不必担心。因此，在大多数 Ruby 程序中，它只是正常工作。&lt;/p&gt;

&lt;p&gt;但是，对于本地语言来说，这可能会相当繁琐，这也是许多人绝对讨厌 &lt;code&gt;fork(2)&lt;/code&gt; 的原因之一。任何使用文件或套接字的代码在调用 &lt;code&gt;fork(2)&lt;/code&gt; 之后可能会完全损坏，除非特别关注了 fork 安全性，而这很少是情况。&lt;/p&gt;
&lt;h2 id="一些您的线程可能会死亡"&gt;一些您的线程可能会死亡&lt;/h2&gt;
&lt;p&gt;回到我们的 echo 服务器，你可能想知道为什么在这里使用 &lt;code&gt;fork(2)&lt;/code&gt; 而不是线程。再次强调，我当时并不在那里，但我的理解是线程在后来的某个时候才成为了一件事（八十年代末？），而且即使它们存在了，也需要相当长的时间才能标准化和解决，因此才能跨平台使用。&lt;/p&gt;

&lt;p&gt;也存在这样的观点，使用 &lt;code&gt;fork(2)&lt;/code&gt; 进行多进程处理更容易理解。每个进程都有自己的内存空间，因此你不必过多担心竞态条件和其他线程陷阱，所以我明白为什么即使线程成为了一种选择，有些人可能还是更喜欢坚持使用 &lt;code&gt;fork(2)&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;但是，由于线程是在 fork(2) 之后很久才被创造的，因此负责实现和标准化它们的人遇到了一些麻烦，没有找到让它们两者都能良好协作的方法。&lt;/p&gt;

&lt;p&gt;这里 &lt;a href="https://pubs.opengroup.org/onlinepubs/009696799/functions/fork.html" rel="nofollow" target="_blank" title=""&gt;POSIX 标准 fork 条目&lt;/a&gt;关于该内容的说明是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;一个进程应使用单个线程创建。如果一个多线程进程调用 &lt;code&gt;fork()&lt;/code&gt;，新进程应包含调用线程的副本及其整个地址空间，可能包括互斥锁和其他资源的状态。因此，为了避免错误，子进程只能在调用 &lt;code&gt;exec&lt;/code&gt; 函数之前执行异步信号安全的操作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;换句话说，标准承认经典的 &lt;code&gt;fork + exec&lt;/code&gt; 模式可以在多线程进程中实现，但对于不带着 &lt;code&gt;exec&lt;/code&gt; 的 &lt;code&gt;fork&lt;/code&gt; 使用，标准则显得有些置身事外。他们建议仅使用异步信号安全的操作，而这实际上只是很小一部分功能。&lt;strong&gt;所以，根据标准，如果你在创建了一些线程之后调用 &lt;code&gt;fork(2)&lt;/code&gt;，且并不打算立即调用 &lt;code&gt;exec&lt;/code&gt; ，那么这里就充满了潜在的危险&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;原因在于，只有调用 &lt;code&gt;fork(2)&lt;/code&gt; 的线程在子进程中保持存活，其他线程虽然存在，但已经死亡。如果另一个线程曾经锁定了一个互斥锁（mutex）或其他类似的资源，那么这个锁将永远保持锁定状态，如果新线程尝试获取它的话，这可能会导致死锁。&lt;/p&gt;

&lt;p&gt;该标准还包括一个关于为什么是这样的原因说明部分，这部分内容有点长但很有趣：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;在多线程世界中使 fork() 工作通常存在的问题是如何处理所有线程。有两种选择。一种是将所有线程复制到新进程中。这导致程序员或实现必须处理那些在系统调用上挂起的线程，或者那些可能即将执行不应该在新进程中执行的系统调用的线程。另一种选择是只复制调用 fork() 的线程。这造成了一个困难，即进程本地资源的状态通常保存在进程内存中。如果一个不调用 fork() 的线程持有一个资源，那么在子进程中这个资源永远不会被释放，因为负责释放资源的线程在子进程中不存在。&lt;/p&gt;

&lt;p&gt;当程序员编写多线程程序时， […] fork() 函数仅用于运行新程序，而在调用 fork() 和调用 exec 函数之间调用需要某些资源的函数的效果是未定义的。&lt;/p&gt;

&lt;p&gt;将 forkall() 函数加入标准中被考虑过并拒绝了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以他们确实考虑了拥有另一个版本的 &lt;code&gt;fork(2)&lt;/code&gt; ，称为 &lt;code&gt;forkall()&lt;/code&gt; ，这个版本也会复制其他线程，但他们无法想出一个清晰的语义（semantic）来解释在某些情况下会发生什么。&lt;/p&gt;

&lt;p&gt;相反，他们为用户提供了一种方法，在 &lt;code&gt;fork&lt;/code&gt; 附近调用回调以恢复状态，例如，重新初始化互斥锁。然而，如果你去看那个回调手册页 &lt;a href="https://man7.org/linux/man-pages/man3/pthread_atfork.3.html" rel="nofollow" target="_blank" title=""&gt;pthread_atfork(3)&lt;/a&gt; ，你可以读到：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pthread_atfork() 的最初意图是允许子进程恢复到一个一致的状态。 […] 实际上，这项任务通常过于困难，难以实现。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以尽管 &lt;code&gt;pthread_atfork&lt;/code&gt; 仍然存在并且可以使用，但标准承认正确使用它是非常困难的。&lt;/p&gt;

&lt;p&gt;这就是为什么许多系统程序员会告诉你永远不要将 &lt;code&gt;fork(2)&lt;/code&gt; 与多线程程序混合使用，或者至少在创建线程后永远不要调用 &lt;code&gt;fork(2)&lt;/code&gt; ，因为那时，一切都不确定了。因此，你多少必须选择你的阵营，看来线程明显赢了。&lt;/p&gt;

&lt;p&gt;但这是针对 C 或 C++ 程序员的。&lt;/p&gt;

&lt;p&gt;在今天的 Ruby 程序员的情况下，使用 &lt;code&gt;fork(2)&lt;/code&gt; 而不是线程的原因是，这是在 MRI 上获得真正并行性的唯一方式 (是的，从某种程度上来说也有 Ractors，但这将是下一篇帖子的主题) ，MRI 是 Ruby 的默认且最常用的实现。由于臭名昭著的 GVL，Ruby 线程只允许并行化 IO 操作，不能并行化 Ruby 代码执行，因此几乎所有的 Ruby 应用服务器都以某种方式集成了 &lt;code&gt;fork(2)&lt;/code&gt; ，以便它们可以利用超过单个 CPU 核心。&lt;/p&gt;

&lt;p&gt;幸运的是，Ruby 缓解了将线程与 &lt;code&gt;fork(2)&lt;/code&gt; 混合使用的一些陷阱。例如，由于它们的实现方式，Ruby 互斥锁在所有者死亡时会自动释放。在伪 Ruby 代码中，它们看起来像这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Mutex&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lock&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@owner&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
      &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ThreadError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"deadlock; recursive locking"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="vi"&gt;@owner&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alive?&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="vi"&gt;@owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，在现实中它们并不是在循环中睡眠以等待，它们使用一种更高效的方式来阻塞，但这只是为了给你一个大致的概念。重要的一点是，Ruby 互斥锁会保留对获取锁的 纤维（因此是线程）的引用，并在其死亡时自动忽略它。因此，在 &lt;code&gt;fork&lt;/code&gt; 之后，所有由后台线程持有的互斥锁会立即释放，这避免了大多数死锁场景。&lt;/p&gt;

&lt;p&gt;当然，这并不完美，如果一个线程在持有互斥锁时死亡，它很可能留下了由互斥锁保护的资源处于不一致的状态，在实践中我从未遇到过这样的情况，当然，这可能是因为全局解释器锁（GVL）的存在在一定程度上减少了对互斥锁的需求。&lt;/p&gt;

&lt;p&gt;现在，Ruby 线程并非完全不受这些陷阱的影响，因为归根结底在 MRI 上，Ruby 线程是由本地线程支持的，所以如果另一个线程释放了 GVL 并调用了一个锁定互斥锁的 C API，你最终可能会遇到一个棘手的死锁问题。&lt;/p&gt;

&lt;p&gt;尽管我从未得到确凿的证据，但我怀疑这对一些 Ruby 用户来说正在发生，因为据我了解，Ruby 用来解析主机名的 glibc 的 &lt;a href="https://man7.org/linux/man-pages/man3/getaddrinfo.3.html" rel="nofollow" target="_blank" title=""&gt;getaddrinfo(3)&lt;/a&gt; 确实使用了全局互斥锁，而 Ruby 在释放 GVL 的情况下调用它，允许并发发生 fork。&lt;/p&gt;

&lt;p&gt;为了防止这种情况，&lt;a href="https://bugs.ruby-lang.org/issues/20590" rel="nofollow" target="_blank" title=""&gt;我在 MRI 中增加了另一个锁&lt;/a&gt;，以防止在进行 &lt;code&gt;getaddrinfo(3)&lt;/code&gt; 调用时发生 &lt;code&gt;Process.fork&lt;/code&gt; 。这远非完美，但考虑到 Ruby 多么依赖 &lt;code&gt;Process.fork&lt;/code&gt; ，这似乎是一个明智的做法。&lt;/p&gt;

&lt;p&gt;依赖 fork 的 Ruby 程序在 macOS 上崩溃并不罕见，因为许多 macOS 系统 API 会隐式地创建线程或锁定互斥锁，而 macOS 选择在发生这种情况时一致性地崩溃。&lt;/p&gt;

&lt;p&gt;所以即使使用纯 Ruby 代码，你偶尔也会遇到 &lt;code&gt;fork(2)&lt;/code&gt; 的陷阱，你不能随意使用它。&lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;所以回答标题中的问题， &lt;code&gt;fork(2)&lt;/code&gt; 被讨厌的原因是它组合性不好，特别是在原生代码中。如果你想使用它，你必须非常小心你正在编写和链接的代码。每当你使用一个库时，你必须确保它不会生成一些线程，或者持有文件描述符，并且在 &lt;code&gt;fork(2)&lt;/code&gt; 和线程之间选择时，大多数系统程序员会选择线程。它们有自己的陷阱，但它们组合性更好，而且很可能你正在调用的 API 在后台使用线程，所以这个选择在某种程度上已经为你做好了。&lt;/p&gt;

&lt;p&gt;但 Ruby 代码的情况远没有这么糟糕，因为它使得编写安全的代码变得更加容易，而且 Ruby 的理念使得像 Active Record 这样的库会为你处理这些复杂的细节。所以问题主要出现在你想要绑定到一些会生成线程的本地库时，比如 grpc 或 libvips，因为它们通常不期望 &lt;code&gt;fork(2)&lt;/code&gt; 会发生，并且通常不接受它作为一个约束。&lt;/p&gt;

&lt;p&gt;尤其是因为 fork 大多在应用程序初始化结束时使用，即使技术上不是 fork 安全的库也会工作，因为它们通常在第一次请求时才懒洋洋地初始化它们的线程和文件描述符。&lt;/p&gt;

&lt;p&gt;无论如何，即使你仍然认为 &lt;code&gt;fork(2)&lt;/code&gt; 是邪恶的，但在 Ruby 提供另一个可用的原语来实现真正的并行性（这应该是下一篇文章的主题）之前，它将仍然是一个必要的邪恶。&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Sat, 08 Feb 2025 18:56:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/44039</link>
      <guid>https://ruby-china.org/topics/44039</guid>
    </item>
    <item>
      <title>【翻译】所以，你想移除 GVL？</title>
      <description>&lt;ul&gt;
&lt;li&gt;作者：&lt;a href="https://github.com/byroot" rel="nofollow" target="_blank" title=""&gt;Jean Boussier&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者：&lt;a href="https://github.com/Mark24Code" rel="nofollow" target="_blank" title=""&gt;Mark24&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文：&lt;a href="https://byroot.github.io/ruby/performance/2025/01/29/so-you-want-to-remove-the-gvl.html" rel="nofollow" target="_blank" title=""&gt;so-you-want-to-remove-the-gvl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我想写一篇关于 &lt;a href="https://rubygems.org/gems/pitchfork" rel="nofollow" target="_blank" title=""&gt;Pitchfork&lt;/a&gt; 的文章，解释它的起源、为什么它会是这个样子，以及我对其未来的看法。但在达到这一点之前，我认为我需要分享我对一些事情的思维模型，在这个例子中，是 Ruby 的 GVL。&lt;/p&gt;

&lt;p&gt;长期以来，人们常说 Rails 应用程序主要是 I/O 密集型，因此 Ruby 的 GVL（全局解释器锁）并不是什么大问题，这也影响了 Ruby 基础设施中一些基础组件的设计，如 Puma 和 Sidekiq。正如我在&lt;a href="https://byroot.github.io/ruby/performance/2025/01/23/the-mythical-io-bound-rails-app.html" rel="nofollow" target="_blank" title=""&gt;之前的文章中解释的那样&lt;/a&gt;，我认为对于大多数 Rails 应用程序来说，这并不完全正确。不管怎样，&lt;a href="https://byroot.github.io/ruby/performance/2025/01/25/why-does-everyone-hate-fork.html" rel="nofollow" target="_blank" title=""&gt;GVL 的存在仍然要求这些线程化系统使用 fork(2)&lt;/a&gt; 才能充分利用服务器的所有核心：每个核心一个进程。为了避免所有这些问题，有些人呼吁简单地移除 GVL。&lt;/p&gt;

&lt;p&gt;但这真的这么简单吗？&lt;/p&gt;
&lt;h2 id="GVL 和线程安全"&gt;GVL 和线程安全&lt;/h2&gt;
&lt;p&gt;如果你阅读有关 GVL 的帖子，你可能听说过它不是为了保护你的代码免受竞态条件的影响，而是为了保护 Ruby 虚拟机免受你的代码影响。换句话说，无论是否有 GVL，你的代码都可能受到竞态条件的影响，这是绝对正确的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;但这并不意味着 GVL 不是您应用程序中 Ruby 代码线程安全的重要组件。&lt;/strong&gt; 让我们用一个简单的代码示例来说明：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：这里表达很英语，比较绕口。中文的意思就是想表达：GVL 其实也会影响到你 Ruby 代码的线程安全。下面举例说明。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quote_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您说这段代码是线程安全的吗？还是不是？&lt;/p&gt;

&lt;p&gt;嗯，如果你回答“它是线程安全的”，你并不完全正确。但如果你回答“它不是线程安全的”，你也不完全正确。&lt;/p&gt;

&lt;p&gt;实际答案是：“视情况而定”。&lt;/p&gt;

&lt;p&gt;首先，这取决于你对线程安全的定义有多严格，然后取决于那个 quote 方法是否是幂等的，最后还取决于你使用的 Ruby 解释器的实现。&lt;/p&gt;

&lt;p&gt;让我解释一下。&lt;/p&gt;

&lt;p&gt;首先， &lt;code&gt;||=&lt;/code&gt; 是一种语法糖，它隐藏了这段代码实际工作方式的一些细节，所以让我们去掉它的语法糖：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quote_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;quoted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Ruby 可以在这里切换线程&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quoted&lt;/span&gt;
    &lt;span class="n"&gt;quoted&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个形式下，更容易看出 &lt;code&gt;||=&lt;/code&gt; 并不是一个单一的操作，而是多个操作。因此，即使在 MRI（即 CRuby 解释器）上，存在全局解释器锁（GVL），从技术上来说，Ruby 在计算 &lt;code&gt;quoted = ...&lt;/code&gt; 之后，也有可能抢占一个线程，并恢复另一个线程，而这个线程可能会带着相同的参数进入同一个方法。&lt;/p&gt;

&lt;p&gt;换句话说，即使有 GVL，此代码也受竞态条件影响。更准确地说，它受“检查 - 执行（check-then-act）”竞态条件影响。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：“Check-then-act”是一种常见的操作模式，指的是先检查某个条件，然后根据检查结果执行相应操作。然而，这种模式在多线程环境下容易引发竞态条件（Race Condition），因为检查和执行之间存在时间间隔，在此期间其他线程可能改变相关状态，导致基于过时的检查结果执行操作。作者这里就想表达这个经典的情况。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果它存在竞态条件，你可以逻辑上推断出它不是线程安全的。但在这里，情况又有所不同。如果 &lt;code&gt;quote(name)&lt;/code&gt; 是幂等的，技术上确实存在竞态条件，但它又没有实际的负面影响。&lt;code&gt;quote(name)&lt;/code&gt; 可能会被执行两次而不是一次，其中一个结果会被丢弃，谁会在乎呢？这就是为什么在我看来，上述代码实际上仍然是线程安全的，不管怎样。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：“幂等”（Idempotent）是一个数学和计算机科学中的概念，指的是一个操作或函数在多次执行后，其效果与执行一次相同。换句话说，无论执行多少次，结果都不会改变。幂等性在很多领域都有重要的应用，尤其是在分布式系统、数据库操作和网络协议中。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们可以通过使用几个线程来实验验证这一点：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_h&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;quote_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'`'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'``'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quote_column_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"`foo`"&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"There was a bug"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="no"&gt;QUOTED_COLUMN_NAMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果您使用 MRI 运行此脚本，它将正常运行，不会崩溃，并且 &lt;code&gt;quote_column_name&lt;/code&gt; 将始终返回您预期的结果。&lt;/p&gt;

&lt;p&gt;然而，如果您尝试使用 TruffleRuby 或 JRuby 运行它，它们是 Ruby 的替代实现，没有 GVL，您将得到&lt;a href="https://gist.github.com/byroot/1470a8fc71c2712a1f3ae875a9a40710" rel="nofollow" target="_blank" title=""&gt;大约 300 行错误&lt;/a&gt;：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ruby &lt;span class="nt"&gt;-v&lt;/span&gt; /tmp/quoted.rb
truffleruby 24.1.2, like ruby 3.2.4, Oracle GraalVM Native &lt;span class="o"&gt;[&lt;/span&gt;arm64-darwin20]
java.lang.RuntimeException: Ruby Thread &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;51 from /tmp/quoted.rb:20 terminated with internal error:
    at org.truffleruby.core.thread.ThreadManager.printInternalError&lt;span class="o"&gt;(&lt;/span&gt;ThreadManager.java:316&lt;span class="o"&gt;)&lt;/span&gt;
    ... 20 more
Caused by: java.lang.NullPointerException
    at org.truffleruby.core.hash.library.PackedHashStoreLibrary.getHashed&lt;span class="o"&gt;(&lt;/span&gt;PackedHashStoreLibrary.java:78&lt;span class="o"&gt;)&lt;/span&gt;
    ... 120 more
java.lang.RuntimeException: Ruby Thread &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;52 from /tmp/quoted.rb:20 terminated with internal error:
    at org.truffleruby.core.thread.ThreadManager.printInternalError&lt;span class="o"&gt;(&lt;/span&gt;ThreadManager.java:316&lt;span class="o"&gt;)&lt;/span&gt;
    ... 20 more
... etc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误并不总是完全相同，有时似乎比其他时候更严重。但总的来说，它会在 TruffleRuby 或 JRuby 解释器内部深处崩溃，因为对同一哈希的并发访问导致它们遇到 &lt;code&gt;NullPointerException&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;因此，我们可以说在 Ruby 的参考实现中这段代码是线程安全的，但在 Ruby 的所有实现中并不都是线程安全的。&lt;/p&gt;

&lt;p&gt;该方式之所以如此，是因为在 MRI 中，线程调度器只能在执行纯 Ruby 代码时切换运行中的线程。每次调用实现于 C 的内置方法时，你都会隐式地受到 GVL 的保护。因此，所有实现于 C 的方法本质上都是“原子的”，除非它们明确释放 GVL。但一般来说，只有 IO 方法会释放它。&lt;/p&gt;

&lt;p&gt;这就是为什么，这段从 &lt;a href="https://github.com/rails/rails/blob/0643592211dec558f93e57451a34393941144c8e/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb#L9" rel="nofollow" target="_blank" title=""&gt;Active Record 摘取的代码&lt;/a&gt;，没有使用 &lt;code&gt;Hash&lt;/code&gt; 但使用了 &lt;code&gt;Concurrent::Map&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在 MRI 中，&lt;code&gt;Concurrent::Map&lt;/code&gt; 几乎只是 Hash 的一个别名，但在 JRuby 和 TruffleRuby 中，它被定义为带有互斥锁的散列表。官方 Rails 不支持 TruffleRuby 或 JRuby，但在实际生产中，我们倾向于通过这种小改动来完成支持。&lt;/p&gt;
&lt;h2 id="直接移除不就好了么"&gt;直接移除不就好了么&lt;/h2&gt;
&lt;p&gt;这就是为什么会有“移除 GVL”和“真的移除 GVL”。&lt;/p&gt;

&lt;p&gt;简单的方法可以像 TruffleRuby 和 JRuby 那样：什么也不做，或者说是几乎什么也不做。&lt;/p&gt;

&lt;p&gt;由于 TruffleRuby、JRuby 实现是基于 Java 虚拟机（JVM）的，而 JVM 是内存安全的，因此它们将这种情况下“失败但不会直接崩溃”的艰巨任务委托给了 JVM 运行时。鉴于 MRI 是用 C 语言实现的，而 C 语言以“不支持内存安全”而闻名，如果仅仅移除 GVL，当你的代码触发这种竞态条件时，虚拟机可能会遇到段错误（segmentation fault）或者更糟糕的情况，因此事情并没有那么简单。&lt;/p&gt;

&lt;p&gt;Ruby 需要在每个可能发生竞态条件的对象上实现类似于 JVM 的做法，为每个对象设置某种原子计数器。每次访问对象时，你都会增加它并检查它是否设置为 1，以确保没有其他人正在使用它。&lt;/p&gt;

&lt;p&gt;这本身是一项相当具有挑战性的任务，因为它意味着要检查 C 语言中实现的所有方法（包括 Ruby 本身以及流行的 C 扩展），以插入所有这些原子递增和递减操作。&lt;/p&gt;

&lt;p&gt;它还需要在大多数 Ruby 对象中为那个新计数器额外占用一些空间，可能是 4 或 8 个字节，因为原子操作在小整数类型上不容易完成。除非当然有一些我不知情的巧妙技巧。&lt;/p&gt;

&lt;p&gt;这也会导致虚拟机的速度变慢，因为所有这些原子递增和递减很可能会有明显的开销，因为原子操作意味着 CPU 必须确保所有核心同时看到这个操作，所以它实际上锁定了 CPU 缓存的那部分。我不会尝试猜测这种开销在实践中会有多少，但肯定不是免费的。&lt;/p&gt;

&lt;p&gt;然后结果就是，很多原本是线程安全的纯 Ruby 代码，将不再具备这种特性。因此，除了 ruby-core 需要做的工作之外，Ruby 用户可能还需要在他们的代码、gem 等中调试大量线程安全问题。&lt;/p&gt;

&lt;p&gt;因此，尽管 JRuby 和 TruffleRuby 团队努力使其与 MRI 尽可能兼容，但由于缺少 GVL 这一特性，大多数非平凡代码库在它们之上运行前可能至少需要进行一些调试。这并不一定需要大量努力，这取决于情况，但比您平均每年的 Ruby 升级要麻烦得多。&lt;/p&gt;
&lt;h2 id="移除GVL 的替代品方案"&gt;移除 GVL 的替代品方案&lt;/h2&gt;
&lt;p&gt;但是，这并不是移除 GVL 的唯一方法，另一种常见的设想是用无数的小锁来替换一个全局锁，每个可变对象一个锁。&lt;/p&gt;

&lt;p&gt;关于需要完成的工作，它与之前的方法相当相似，你需要遍历所有 C 代码，并在每次接触可变对象时显式插入锁定和解锁语句。这还需要在每个对象上占用一些空间，可能比仅仅一个计数器要多一些。&lt;/p&gt;

&lt;p&gt;采用这种方法，C 扩展可能仍需要一些工作，但纯 Ruby 代码将保持完全兼容。&lt;/p&gt;

&lt;p&gt;如果您听说过最近半途而废的尝试移除 Python 的 GIL（相当于 Python 版本的 GVL），那么他们就是用的这种方法。那么，让我们看看他们做了哪些改动，&lt;a href="https://github.com/python/cpython/blob/180ee43bde99b8ce4c4f1d5237ab191e26118061/Include/object.h#L109-L162" rel="nofollow" target="_blank" title=""&gt;从他们定义在 object.h 的基础对象布局开始&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;它有很多仪式性代码（Ceremonial Code），所以这里有一个简化版本：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：“仪式代码”（Ceremonial Code）是指在编程过程中，为了满足某些框架、语言特性或规范要求而必须编写的一些额外代码，这些代码本身对核心功能的实现并没有直接帮助，但却是必要的步骤。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built by hand.
 */&lt;/span&gt;
&lt;span class="cp"&gt;#ifndef Py_GIL_DISABLED
&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;_object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;ob_refcnt&lt;/span&gt;
    &lt;span class="n"&gt;PyTypeObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;&lt;span class="c1"&gt;// Objects that are not owned by any thread use a thread id (tid) of zero.&lt;/span&gt;
&lt;span class="c1"&gt;// This includes both immortal objects and objects whose reference count&lt;/span&gt;
&lt;span class="c1"&gt;// fields have been merged.&lt;/span&gt;
&lt;span class="cp"&gt;#define _Py_UNOWNED_TID             0
&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;_object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ob_tid stores the thread id (or zero). It is also used by the GC and the&lt;/span&gt;
    &lt;span class="c1"&gt;// trashcan mechanism as a linked list pointer and by the GC to store the&lt;/span&gt;
    &lt;span class="c1"&gt;// computed "gc_refs" refcount.&lt;/span&gt;
    &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;ob_tid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;ob_flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PyMutex&lt;/span&gt; &lt;span class="n"&gt;ob_mutex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// per-object lock&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;ob_gc_bits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// gc-related state&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;ob_ref_local&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// local reference count&lt;/span&gt;
    &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;ob_ref_shared&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// shared (atomic) reference count&lt;/span&gt;
    &lt;span class="n"&gt;PyTypeObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ob_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那里有相当多的内容，让我来概括下。简单起见，我的整个解释都将假设 64 位架构。&lt;/p&gt;

&lt;p&gt;也请注意，虽然我曾经是 Pythonista，那是在 15 年前，而现在我只是从远处观察 Python 的发展。总之，我会尽力准确描述他们正在做的事情，但完全有可能我会有些地方描述错误。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：Pythonista 是指那些对 Python 编程语言非常热爱和精通的人，通常是对代码质量和编程风格有较高追求的开发者。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;无论如何，当 GIL（Python 的全局解释器锁）没有被编译禁用的时候，每个 Python 对象都以 &lt;code&gt;16B&lt;/code&gt; 开头，第一个 &lt;code&gt;8B&lt;/code&gt; 称为 &lt;code&gt;ob_refcnt&lt;/code&gt; 用于引用计数，正如其名，但实际上只使用 &lt;code&gt;4B&lt;/code&gt; 作为计数器，其他 &lt;code&gt;4B&lt;/code&gt; 用作位图来设置对象上的标志，就像在 Ruby 中一样。然后剩余的 &lt;code&gt;8B&lt;/code&gt; 只是一个指向对象类的指针。&lt;/p&gt;

&lt;p&gt;与比较，Ruby 的对象头称为 &lt;code&gt;struct RBasic&lt;/code&gt; 也是 &lt;code&gt;16B&lt;/code&gt; 。同样，它有一个指向类的指针，另一个 &lt;code&gt;8B&lt;/code&gt; 用作存储许多不同的大位图 (big bitmap)。&lt;/p&gt;

&lt;p&gt;然而，当在编译期间禁用 GIL 时，对象头现在是 &lt;code&gt;32B&lt;/code&gt; ，大小加倍。它以 &lt;code&gt;8B ob_tid&lt;/code&gt; 开头，用于线程 &lt;code&gt;ID&lt;/code&gt;，存储哪个线程拥有该特定对象。然后 &lt;code&gt;ob_flags&lt;/code&gt; 被显式布局，但已缩减到 &lt;code&gt;2B&lt;/code&gt; ，为 &lt;code&gt;1B ob_mutex&lt;/code&gt; 腾出空间，并为一些我不太了解的 GC 状态腾出另一个 &lt;code&gt;1B&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;该 &lt;code&gt;4B ob_refcnt&lt;/code&gt; 字段仍然存在，但这次命名为 &lt;code&gt;ob_ref_local&lt;/code&gt; ，并且还有一个 &lt;code&gt;8B ob_ref_shared&lt;/code&gt; ，最后是对象类的指针。&lt;/p&gt;

&lt;p&gt;仅通过对象布局的改变，你就能感受到额外的复杂性，以及内存开销。每个对象额外 16 个字节不是微不足道的。&lt;/p&gt;

&lt;p&gt;现在，正如你可能从 &lt;code&gt;refcnt&lt;/code&gt;(ref count) 字段中猜到的，Python 的内存主要通过引用计数来管理。它们还有一个标记和清除收集器，但它只是为了处理循环引用。在这方面，它与 Ruby 相当不同，但看看他们为了使这个线程安全而必须做的事情仍然很有趣。&lt;/p&gt;

&lt;p&gt;让我们看看在 &lt;a href="https://github.com/python/cpython/blob/180ee43bde99b8ce4c4f1d5237ab191e26118061/Include/refcount.h#L245-L294" rel="nofollow" target="_blank" title=""&gt;refcount.h 中定义的 Py_INCREF&lt;/a&gt; 。在这里，它充满了针对各种架构的 ifdef，所以这里有一个简化版本，只包含当 GIL 激活时执行的代码，并移除了一些调试代码：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;
&lt;span class="cp"&gt;#define _Py_IMMORTAL_MINIMUM_REFCNT ((Py_ssize_t)(1L &amp;lt;&amp;lt; 30))
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;Py_ALWAYS_INLINE&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;_Py_IsImmortal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_refcnt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_Py_IMMORTAL_MINIMUM_REFCNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;Py_ALWAYS_INLINE&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Py_IsImmortal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_refcnt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它非常简单，即使你不熟悉 C 语言，也应该能够读懂它。但基本上，它会检查引用计数是否设置为标记永生对象的魔法值，如果不是永生的，它就简单地执行一个常规的、非原子的、因此非常便宜的计数器递增。&lt;/p&gt;

&lt;p&gt;关于“永生对象”（Immortal Objects）的补充说明，这是一个由 &lt;a href="https://instagram-engineering.com/copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf" rel="nofollow" target="_blank" title=""&gt;Instagram 工程师引入的非常酷的概念&lt;/a&gt;，我也一直想将其引入到 Ruby 中。如果你对类似“写时复制”（Copy-on-Write）和内存节省这类话题感兴趣，那么它绝对值得一读。&lt;/p&gt;

&lt;p&gt;现在让我们看看移除 GIL 后的相同 &lt;code&gt;Py_INCREF&lt;/code&gt; 函数：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define _Py_IMMORTAL_REFCNT_LOCAL UINT32_MAX
# define _Py_REF_SHARED_SHIFT        2
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;Py_ALWAYS_INLINE&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;_Py_IsImmortal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Py_atomic_load_uint32_relaxed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_ref_local&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;
            &lt;span class="n"&gt;_Py_IMMORTAL_REFCNT_LOCAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;Py_ALWAYS_INLINE&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;_Py_IsOwnedByCurrentThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_tid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;_Py_ThreadId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;Py_ALWAYS_INLINE&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Py_INCREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_Py_atomic_load_uint32_relaxed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_ref_local&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;new_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_local&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// local is equal to _Py_IMMORTAL_REFCNT_LOCAL: do nothing&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_Py_IsOwnedByCurrentThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_Py_atomic_store_uint32_relaxed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_ref_local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_local&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_Py_atomic_add_ssize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ob_ref_shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_Py_REF_SHARED_SHIFT&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是现在更加复杂。首先，需要原子地加载 &lt;code&gt;ob_ref_local&lt;/code&gt; ，正如之前提到的，这比正常加载要昂贵，因为它需要 CPU 缓存同步。然后，我们仍然有对不朽对象的检查，没有新内容。&lt;/p&gt;

&lt;p&gt;有趣的部分是最后的 if，因为有两种不同的情况，一种是对象由当前线程拥有，另一种则不是。因此，第一步是比较 &lt;code&gt;ob_tid&lt;/code&gt; 和 &lt;code&gt;_Py_ThreadId()&lt;/code&gt; 。这个函数太大，无法在这里包含，但你可以检查 &lt;code&gt;object.h&lt;/code&gt; 中的实现，在大多数平台上，这基本上是免费的，因为线程 ID 总是存储在 CPU 寄存器中。&lt;/p&gt;

&lt;p&gt;当对象由当前线程拥有时，Python 可以通过先进行非原子性增加后进行原子性存储来避免问题。而在相反的情况下，整个增加操作必须原子性，这要昂贵得多，因为它涉及到比较和交换操作。这意味着在发生竞态条件的情况下，CPU 将重试增加操作，直到在没有竞态条件的情况下完成。&lt;/p&gt;

&lt;p&gt;用 Ruby 伪代码描述，它可能看起来像这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;atomic_compare_and_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 假设这个方法是一个 原子性 CPU 操作&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@memory&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt;
    &lt;span class="vi"&gt;@memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;atomic_increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;atomic_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;atomic_compare_and_swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，您可以看到，曾经是一个非常平凡的操作，即一个主要的 Python 热点，变成了一个明显更复杂的过程。Ruby 不使用引用计数，所以如果尝试移除 GVL，这个特定的情况不会立即翻译成 Ruby，但 Ruby 仍然有一系列非常频繁调用的类似例程，会受到类似的影响。&lt;/p&gt;

&lt;p&gt;例如，因为 Ruby 的垃圾回收是代际和增量式的，当两个对象之间创建新的引用时，比如从 A 到 B，Ruby 可能需要标记 A 为需要重新扫描，这是通过在位图中翻转一个位来完成的。这是需要使用原子操作进行更改的一个例子。&lt;/p&gt;

&lt;p&gt;但我们还没有谈到实际的锁定。当我第一次听说 Python 试图移除它们的 GIL 时，我本以为他们会利用现有的引用计数 API 来将锁定放入其中，但显然，他们并没有这样做。我不确定为什么，但我猜因为语义并不完全匹配。&lt;/p&gt;

&lt;p&gt;相反，他们必须做我之前提到的事情，即检查 C 中实现的所有方法，以添加显式的加锁和解锁调用。为了说明，我们可以看看 &lt;code&gt;list.clear()&lt;/code&gt; 方法，它是 &lt;code&gt;Array#clear&lt;/code&gt; 的 Python 等价方法。&lt;/p&gt;

&lt;p&gt;在移除 GIL 的努力之前，它看起来是这样的：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;PyList_Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyList_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;list_clear&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PyListObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它看起来比实际要简单，因为大部分复杂性都在 &lt;code&gt;list_clear&lt;/code&gt; 例程中，但无论如何，它相当直接。&lt;/p&gt;

&lt;p&gt;项目开始一段时间后，&lt;a href="https://github.com/python/cpython/issues/127536" rel="nofollow" target="_blank" title=""&gt;Python 开发者注意到他们忘记给 list.clear 和其他几个方法添加锁&lt;/a&gt;，因此他们进行了修改：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;PyList_Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyList_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Py_BEGIN_CRITICAL_SECTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;list_clear&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PyListObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_END_CRITICAL_SECTION&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不太糟糕，他们设法将其全部封装在两个宏中，当 Python 启用 GIL 时，这些宏只是空操作。&lt;/p&gt;

&lt;p&gt;我不会解释 &lt;code&gt;Py_BEGIN_CRITICAL_SECTION&lt;/code&gt; 中发生的一切，有些东西我无论如何也理解不了，但简而言之，它最终会进入 &lt;code&gt;_PyCriticalSection_BeginMutex&lt;/code&gt; ，其中有一个快速路径和一个慢速路径：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;_PyCriticalSection_BeginMutex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyCriticalSection&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PyMutex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyMutex_LockFast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyThreadState&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tstate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_PyThreadState_GET&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;_cs_mutex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;_cs_prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tstate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;critical_section&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tstate&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;critical_section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_PyCriticalSection_BeginSlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;快速路径所做的，是假设对象的 ob_mutex 字段设置为 0，并尝试通过原子比较和交换将其设置为 1：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;//_Py_UNLOCKED is defined as 0 and _Py_LOCKED as 1 in Include/cpython/lock.h&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;PyMutex_LockFast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyMutex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_Py_UNLOCKED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lock_bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;_bits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_Py_atomic_compare_exchange_uint8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lock_bits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_Py_LOCKED&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果那样可以工作，它知道物体已被解锁，因此只需进行一点账目管理即可。&lt;/p&gt;

&lt;p&gt;如果这种方法不起作用，那么它就会进入慢速路径，而在这里情况开始变得相当复杂。但为了快速描述一下，它首先会使用一个自旋锁（spin-lock），并且进行 40 次迭代。所以，在某种程度上，它会连续不断地执行 40 次比较和交换逻辑，寄希望于最终能够成功。如果这仍然不起作用，它就会将线程“挂起”（park），并等待一个信号来恢复运行。如果你对了解更多感兴趣，可以查看 &lt;code&gt;Python/lock.c&lt;/code&gt; &lt;code&gt;中的_PyMutex_LockTimed&lt;/code&gt; 函数，并从那里跟踪代码。然而，对于我们的当前话题来说，互斥锁代码本身并没有那么有趣，因为假设大多数对象只被单个线程访问，所以快速路径才是最重要的。&lt;/p&gt;

&lt;p&gt;但除了这条快速路径的成本之外，如何将锁定和解锁语句集成到现有代码库中也很重要。如果你忘记了一个 &lt;code&gt;lock()&lt;/code&gt; ，可能会导致虚拟机崩溃，而如果你忘记了一个 &lt;code&gt;unlock()&lt;/code&gt;，可能会导致虚拟机死锁，这可以说是更糟糕的情况。&lt;/p&gt;

&lt;p&gt;所以，让我们回到那个 &lt;code&gt;list.clear()&lt;/code&gt; 例子：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;PyList_Clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyList_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;Py_BEGIN_CRITICAL_SECTION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;list_clear&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PyListObject&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Py_END_CRITICAL_SECTION&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您可能已经注意到 Python 是如何进行错误检查的。当发现一个不良的前置条件时，它通过一个 &lt;code&gt;PyErr_*&lt;/code&gt; 函数生成一个异常，并返回 -1。这是因为 &lt;code&gt;list.clear()&lt;/code&gt; 总是返回 None（Python 的 &lt;code&gt;nil&lt;/code&gt; ），所以其 C 实现的返回类型只是一个 int。对于一个返回 Ruby 对象的函数，在错误条件下，它会返回一个 &lt;code&gt;NULL&lt;/code&gt; 指针。&lt;/p&gt;

&lt;p&gt;例如 &lt;code&gt;list.__getitem__&lt;/code&gt; ，它是 Python 中的 &lt;code&gt;Array#fetch&lt;/code&gt; 的等价物，定义为：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="nf"&gt;PyList_GetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Py_ssize_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PyList_Check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_BadInternalCall&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;valid_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Py_SIZE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_Py_DECLARE_STR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"list index out of range"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;PyErr_SetObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PyExc_IndexError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;_Py_STR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PyListObject&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ob_item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您可以在尝试使用越界索引访问 Python 列表时看到该错误：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; a &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; a[12]
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"&amp;lt;stdin&amp;gt;"&lt;/span&gt;, line 1, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
IndexError: list index out of range
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您可以识别相同的 &lt;code&gt;IndexError&lt;/code&gt; 和相同的 &lt;code&gt;list index out of range&lt;/code&gt; 消息。&lt;/p&gt;

&lt;p&gt;所以在这两种情况下，当用 C 实现的 Python 方法需要抛出异常时，它们会构建异常对象，将其存储在某些线程局部状态中，然后返回一个特定的值以让解释器知道发生了异常。当解释器注意到函数的返回值是这些特殊值之一时，它开始回溯堆栈。从某种意义上说，Python 异常是经典 &lt;code&gt;if (error) { return error }&lt;/code&gt; 模式的语法糖。&lt;/p&gt;

&lt;p&gt;现在让我们看看 Ruby 的 &lt;code&gt;Array#fetch&lt;/code&gt; ，看看你是否注意到在处理越界情况时有什么不同：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt;
&lt;span class="nf"&gt;rb_ary_fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VALUE&lt;/span&gt; &lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// snip...&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NUM2LONG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;RARRAY_LEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_given&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rb_yield&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rb_raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb_eIndexError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"index %ld outside of..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* snip... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ifnone&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RARRAY_AREF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你注意到在 rb_raise 之后没有明确的 &lt;code&gt;return&lt;/code&gt; 吗？&lt;/p&gt;

&lt;p&gt;这是因为 Ruby 异常与 Python 异常非常不同，因为它们依赖于 &lt;code&gt;setjmp(3)&lt;/code&gt;和 &lt;code&gt;longjmp(3)&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;不深入细节，这两个函数本质上允许你为堆栈设置一个“保存点”并跳转回它。当它们被使用时，有点像非局部跳转 &lt;code&gt;goto&lt;/code&gt; ，你直接跳转回父函数，所有中间函数都不会返回。&lt;/p&gt;

&lt;p&gt;因此，Ruby 中的等效操作需要调用 &lt;code&gt;setjmp&lt;/code&gt; ，并使用 &lt;code&gt;EC_PUSH_TAG&lt;/code&gt; 宏将相关的检查点推送到执行上下文（本质上当前纤程），因此本质上每个核心方法现在都需要一个 &lt;code&gt;rescue&lt;/code&gt; 子句，这并非免费。这是可行的，但可能比 &lt;code&gt;Py_BEGIN_CRITICAL_SECTION&lt;/code&gt; 更昂贵。&lt;/p&gt;
&lt;h2 id="我们继续"&gt;我们继续&lt;/h2&gt;
&lt;p&gt;但我们过于专注于是否能够移除 GVL，以至于我们没有停下来思考是否应该这么做。&lt;/p&gt;

&lt;p&gt;在 Python 的情况下，据我所知，推动移除 GIL 的努力主要来自机器学习社区，很大程度上是因为高效地喂养显卡需要相当高的并行度，而 &lt;code&gt;fork(2)&lt;/code&gt; 并不适合。&lt;/p&gt;

&lt;p&gt;然而，根据我的理解，Python Web 社区，如 Django 用户，似乎对 &lt;code&gt;fork(2)&lt;/code&gt; 满意，尽管 Python 在 Copy-on-Write（写时复制）方面相对于 Ruby 处于重大劣势，因为正如我们之前所看到的，它的引用计数实现意味着大多数对象不断被写入，因此 CoW 页面很快就会失效。&lt;/p&gt;

&lt;p&gt;另一方面，Ruby 的标记 - 清除 GC 对写时复制（Copy-On-Write）非常友好，因为几乎所有 GC 跟踪数据都不是存储在对象本身中，而是在外部位图中。因此，GVL 无锁线程的主要论点之一，即减少内存使用，在 Ruby 的情况下就不那么重要了。&lt;/p&gt;

&lt;p&gt;鉴于 Ruby（无论好坏）主要用于 Web 应用，这至少可以部分解释为什么移除 GVL 的压力不像 Python 那样强烈。同样，Node.js 和 PHP 也没有自由线程 (free threading)，但据我所知，它们各自的社区对此并没有太多抱怨，除非我错过了什么。&lt;/p&gt;

&lt;p&gt;如果 Ruby 要采用某种形式的自由线程，它可能需要在所有对象中添加某种形式的锁，并且会频繁地修改它，这可能会严重降低写时复制（Copy-on-Write）的效率。因此，这不会是一个纯粹的附加功能。&lt;/p&gt;

&lt;p&gt;类似地，移除 Python GIL 的主要障碍之一一直是其对单线程性能的负面影响。当你处理易于并行化的算法时，即使单线程性能下降，通过使用更多的并行性，你可能仍然能够取得优势。但如果你使用 Python 的场景并行化困难，那么自由线程可能对你来说并不特别有吸引力。&lt;/p&gt;

&lt;p&gt;历史上，Guido van Rossum 对移除 GIL 的立场是，只要它不影响单线程性能，他就欢迎这样做，这就是为什么它从未发生。现在，随着 Guido 不再是 Python 的仁慈独裁者，Python 指导委员会似乎愿意接受单线程性能的一些退步，但还不清楚这实际上会有多大。有一些数字在流传，但大多是来自合成基准测试等。我个人很想知道这种变化对 Web 应用的影响，在对此类变化发生在 Ruby 上感到热情之前。同时，需要注意的是，&lt;a href="https://peps.python.org/pep-0703/" rel="nofollow" target="_blank" title=""&gt;移除已被接受，但有一些前提条件&lt;/a&gt;，所以它还没有完成，他们可能在某个时候决定回头也是有可能的。&lt;/p&gt;

&lt;p&gt;另一个需要考虑的问题是，对 Ruby 的性能影响可能比对 Python 更严重，因为需要额外开销的对象是可变对象，而与 Python 不同的是，Ruby 中的字符串也属于可变对象。想想一个普通的 Web 应用程序会执行多少次字符串操作。&lt;/p&gt;

&lt;p&gt;另一方面，我想到的一个支持移除 GVL 的论点就是 YJIT。鉴于 YJIT 生成的本地代码及其关联的元数据仅限于进程范围，不再依赖 &lt;code&gt;fork(2)&lt;/code&gt; 进行并行处理，仅通过共享所有这些内存，就能节省相当多的内存。然而，移除 GVL 也会让 YJIT 的工作变得更加困难，因此这也可能阻碍其进展。&lt;/p&gt;

&lt;p&gt;另一个支持自由线程的论点是，派生的进程难以共享连接。因此，当您开始将 Rails 应用程序扩展到大量 CPU 核心时，您将比具有自由线程的堆栈拥有更多连接到您的数据存储，这可能会成为一个大瓶颈，尤其是在一些像 PostgreSQL 这样的具有昂贵连接的数据库中。目前，这主要通过使用外部连接池器来解决，如 PgBouncer 或 ProxySQL，我知道它们并不完美。这又是一个可能出错的新组件，但我认为这比自由线程要少很多麻烦。&lt;/p&gt;

&lt;p&gt;最后，我想指出，GVL 并不是全部。如果目标是替换 fork(2) 为多线程，即使移除了 GVL，我们可能仍然不完全达到目标，因为 Ruby 的 GC 是“暂停世界（stop the world）”，所以随着单个进程中代码执行量的增加，因此分配也会更多，我们可能会发现它将成为新的竞争点。所以，我个人更愿意在希望移除 GVL 之前，先实现一个完全并发的 GC。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：暂停世界（stop the world） ：因为 GC（垃圾回收）的时候会暂停所有程序的执行，进行对游离变量的盘点、回收，再恢复执行。所以使用 GC 语言可能会很慢、甚至无法预测的卡住。高性能的游戏领域会用 C、C++ 这种手动控制内存回收的语言，避免这种特点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="所以，保持现状？"&gt;所以，保持现状？&lt;/h2&gt;
&lt;p&gt;在这个时候，有些人可能觉得我好像在试图洗脑人们，让他们认为 GVL 永远不会成为问题，但那并不是我的真实想法。&lt;/p&gt;

&lt;p&gt;我绝对认为 GVL 目前在实际应用中造成了一些非常真实的问题，即竞争。但这与想要移除 GVL 是截然不同的，我相信情况可以通过其他方式显著改善。&lt;/p&gt;

&lt;p&gt;如果您已经阅读了我&lt;a href="https://byroot.github.io/ruby/performance/2025/01/23/io-instrumentation.html" rel="nofollow" target="_blank" title=""&gt;关于如何在 Ruby 中正确测量 IO 时间&lt;/a&gt;的短文，您可能已经熟悉了 GVL 竞争问题，但让我在这里包含相同的测试脚本：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"bundler/inline"&lt;/span&gt;

&lt;span class="n"&gt;gemfile&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"bigdecimal"&lt;/span&gt; &lt;span class="c1"&gt;# for trilogy&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"trilogy"&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"gvltools"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;GVLTools&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LocalTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enable&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;measure_time&lt;/span&gt;
  &lt;span class="n"&gt;realtime_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clock_gettime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CLOCK_MONOTONIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:float_millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;gvl_time_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GVLTools&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LocalTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic_time&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;

  &lt;span class="n"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clock_gettime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CLOCK_MONOTONIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:float_millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;realtime_start&lt;/span&gt;
  &lt;span class="n"&gt;gvl_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GVLTools&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;LocalTimer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic_time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gvl_time_start&lt;/span&gt;
  &lt;span class="n"&gt;gvl_time_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gvl_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1_000_000.0&lt;/span&gt;
  &lt;span class="n"&gt;io_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;realtime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gvl_time_ms&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"io: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;io_time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms, gvl_wait: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;gvl_time_ms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;trilogy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Trilogy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="c1"&gt;# Measure a first time with just the main thread&lt;/span&gt;
&lt;span class="n"&gt;measure_time&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;trilogy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Spawn 5 CPU-heavy threads&lt;/span&gt;
&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Measure again with the background threads&lt;/span&gt;
&lt;span class="n"&gt;measure_time&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;trilogy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果您运行它，您应该会得到类似的结果：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;realtime: 0.22ms, gvl_wait: 0.0ms, io: 0.2ms
realtime: 549.29ms, gvl_wait: 549.22ms, io: 0.1ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本脚本演示了 GVL 竞争如何对应用程序的延迟造成破坏。即使您使用像 Unicorn 或 Pitchfork 这样的单线程服务器，这也并不意味着应用程序只使用单个线程。拥有各种后台线程来执行一些服务任务，如监控，是非常常见的。其中一个例子是 &lt;code&gt;statsd-instrument&lt;/code&gt; gem。当您发出一个指标时，它会在内存中收集，然后一个后台线程负责批量序列化和发送这些指标。它应该主要是 IO 工作，因此不应该对主线程有太大影响，但在实践中，可能会发生这些类型的后台线程比您希望的更长时间地持有 GVL。&lt;/p&gt;

&lt;p&gt;所以，尽管我的演示脚本非常极端，你绝对可以在生产环境中体验到一定程度的 GVL 竞争，无论你使用什么服务器。&lt;/p&gt;

&lt;p&gt;但我认为尝试移除 GVL 并不一定是解决这个问题的最佳方法，因为这需要多年的泪水和汗水，才能获得点好处。&lt;/p&gt;

&lt;p&gt;在 2006 年之前，多核 CPU 基本上不存在，然而，你仍然能够以相对顺畅的方式在电脑上多任务处理，比如在 Excel 中处理数字的同时在 Winamp 中播放音乐，而且这一切都不需要并行处理。&lt;/p&gt;

&lt;p&gt;那是因为即使是 Windows 95 也有一个相当不错的线程调度器，但 Ruby 还没有。当 Ruby 中的线程准备好执行并需要等待 GVL 时，它会将其放入一个 FIFO 队列中，每当正在运行的线程释放 GVL，无论是由于进行了某些 I/O 操作还是因为运行了分配的 100 毫秒后，Ruby 的线程调度器就会弹出下一个线程。&lt;/p&gt;

&lt;p&gt;没有任何优先级的概念。一个半不错的调度器应该能够注意到一个线程主要是 IO，打断当前线程来更快地调度 IO 密集型线程可能是值得的。&lt;/p&gt;

&lt;p&gt;在尝试移除 GVL 之前，尝试实现一个合适的线程调度器是值得的。这个想法归功于 &lt;a href="https://github.com/jhawthorn/" rel="nofollow" target="_blank" title=""&gt;John Hawthorn&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;与此同时，&lt;a href="https://github.com/tenderlove" rel="nofollow" target="_blank" title=""&gt;Aaron Patterson(tenderlove)&lt;/a&gt; 在 &lt;a href="https://bugs.ruby-lang.org/issues/20861" rel="nofollow" target="_blank" title=""&gt;Ruby 3.4 中发布了一个更改，允许通过环境变量减少 100 毫秒的量子&lt;/a&gt;。这并不能解决所有问题，但可能已经在某些情况下有所帮助，所以这是一个开始。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：量子（quantum）是 Ruby 解释器中的一个超时时间，默认 100 毫秒，Ruby 3.4 可以被轻松设置。解释器在执行线程的时候，如果超过了这个时间，就会回收 GVL，切换另一个线程执行。主要用来调度多个线程工作使用。当降低这个时间，可以更精细的切分正在执行的函数，加快多个线程排队轮转执行的速度，可以提高 IO 密集型应用的性能。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;另一个约翰在我们的一次对话中分享的想法是，允许在 GVL 释放时进行更多的 CPU 操作。目前，大多数数据库客户端只在 IO 时真正释放 GVL，把它想象成这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
  &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_network_packet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;release_gvl&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;parse_db_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于返回大量数据的简单查询，很可能你在持有 GVL（全局解释器锁）的情况下构建 Ruby 对象所花费的时间，比在释放 GVL 的情况下等待数据库响应的时间要多得多。&lt;/p&gt;

&lt;p&gt;这是因为非常非常少的 Ruby C API 可以使用 GVL 释放，特别是任何分配对象或可能抛出异常的内容都必须获取 GVL。&lt;/p&gt;

&lt;p&gt;如果取消这一限制，使得你可以在释放 GVL 的情况下创建基本的 Ruby 对象（如字符串、数组和哈希表），那么很可能会让 GVL 释放的时间更长，并显著减少线程竞争。&lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;我本人并不真正支持取消 GVL，我认为这种权衡并不值得，至少目前还不值得，我也不认为它将像一些人想象的那样成为一个巨大的变革。&lt;/p&gt;

&lt;p&gt;如果它对经典（主要是单线程）性能没有影响，我可能不会介意，但它几乎肯定会显著降低单线程性能，因此这感觉有点像“多得不如现得”的论点。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;译者注：a bird in the hand is worth two in the bush（一鸟在手胜过双鸟在林）。这里翻译为：多得不如现得。到手才是真的，落袋为安的意思。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;相反，我认为我们可以对 Ruby 进行一些更容易和更小的改动，这将能在更短的时间内以及更少的努力下改善情况，既对 Ruby 核心也对 Ruby 用户来说都是如此。&lt;/p&gt;

&lt;p&gt;当然，这只是单一 Ruby 用户的观点，主要考虑的是我自己的使用场景，最终决定权在 Matz 手中，根据他认为社区想要和需要什么来决定。&lt;/p&gt;

&lt;p&gt;目前，Matz 不想移除 GVL，而是接受了 Ractor 的提议。也许他的观点有一天会改变，我们拭目以待。&lt;/p&gt;

&lt;p&gt;Ractor 我本想在这篇帖子中讨论的，但已经太长了，所以可能下次再说。&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Sat, 08 Feb 2025 15:30:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/44038</link>
      <guid>https://ruby-china.org/topics/44038</guid>
    </item>
    <item>
      <title>【翻译】Ruby 的“线程竞争”就是 GVL 排队！</title>
      <description>&lt;ul&gt;
&lt;li&gt;作者：&lt;a href="https://island94.org/about" rel="nofollow" target="_blank" title=""&gt;Ben Sheldon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者：&lt;a href="https://github.com/Mark24Code" rel="nofollow" target="_blank" title=""&gt;Mark24&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文：&lt;a href="https://island94.org/2025/01/ruby-thread-contention-simply-gvl-queuing" rel="nofollow" target="_blank" title=""&gt;博客地址&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最近 Jean Boussier 发布了很多精彩的帖子：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://byroot.github.io/ruby/performance/2025/01/23/the-mythical-io-bound-rails-app.html" rel="nofollow" target="_blank" title=""&gt;《应用程序形状（application shapes）》&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://byroot.github.io/ruby/performance/2025/01/23/the-mythical-io-bound-rails-app.html" rel="nofollow" target="_blank" title=""&gt;《监控 GVL（instrumenting the GVL (Global VM Lock)）》&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;以及 &lt;a href="https://byroot.github.io/ruby/performance/2025/01/29/so-you-want-to-remove-the-gvl.html" rel="nofollow" target="_blank" title=""&gt;《关于移除 GVL 的想法（thoughts on removing the GVL）》&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;它们都是值得一读的！&lt;/p&gt;

&lt;p&gt;长期以来，我一直误解了“线程竞争”这个词语。作为 GoodJob（👍）的作者和 &lt;a href="https://github.com/ruby-concurrency/concurrent-ruby" rel="nofollow" target="_blank" title=""&gt;Concurrent Ruby&lt;/a&gt; 的维护者，以及做了十多年的 Ruby 和 Rails 相关工作，这一点确实有点尴尬。但确实如此。&lt;/p&gt;

&lt;p&gt;我已经阅读了很久关于线程竞争的内容。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;我可能最初是在 Nate Berkopec 的 &lt;a href="https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html" rel="nofollow" target="_blank" title=""&gt;Speedshop 博客&lt;/a&gt;中了解到线程竞争的。&lt;/li&gt;
&lt;li&gt;线程竞争问题从 Maciej Mensfeld &lt;a href="https://mensfeld.pl/2022/01/reduce-your-method-calls-by-99-9-by-replacing-threadpass-with-queuepop/" rel="nofollow" target="_blank" title=""&gt;《关于 Thread.pass 问题 (problems with Thread.pass
)》&lt;/a&gt;的帖子开始闯入我的脑海。&lt;/li&gt;
&lt;li&gt;关于 Rail &lt;a href="https://github.com/rails/rails/issues/50450" rel="nofollow" target="_blank" title=""&gt;“默认 puma 线程数”&lt;/a&gt; 的激烈讨论。&lt;/li&gt;
&lt;li&gt;Ivo Anjo 对 &lt;a href="https://ivoanjo.me/blog/2023/07/23/understanding-the-ruby-global-vm-lock-by-observing-it/" rel="nofollow" target="_blank" title=""&gt;GVL 精彩的深入研究&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通过这一切，我把线程竞争看作是竞争：一场斗争，一堆线程都在互相推搡着运行，乱糟糟地踩在彼此身上，这是一个低效、令人不悦且杂乱无章的混乱局面。但实际情况根本不是这样！&lt;/p&gt;

&lt;p&gt;相反：当你有任意数量的线程在 Ruby 中时，每个线程都会有序地排队等待获取 Ruby GVL，然后它们会温和地持有 GVL，直到它们优雅地放弃它或者它被礼貌地从他们那里拿走，然后线程回到队列的末尾，在那里它们再次耐心地等待。&lt;/p&gt;

&lt;p&gt;这是 Ruby 中“线程竞争”的含义：GVL 的有序排队。并不那么疯狂。&lt;/p&gt;
&lt;h2 id="让我们更进一步"&gt;让我们更进一步&lt;/h2&gt;
&lt;p&gt;我是在研究 &lt;a href="https://github.com/bensheldon/good_job/issues/1554" rel="nofollow" target="_blank" title=""&gt;“是否应该降低 GoodJob 的线程优先级”&lt;/a&gt;（我确实降低了）时意识到这一点的。这个问题是在 GitHub（我的日常工作场所）进行了一些探索之后出现的。在 GitHub，我们有一个用于维护的后台线程，如果这个后台线程执行时机恰好与 Web 服务器（Unicorn）响应 Web 请求的时间重合，就会偶尔导致我们无法达到某个 Web 请求的性能目标。&lt;/p&gt;

&lt;p&gt;Ruby 线程是操作系统线程。而操作系统线程是抢占式的，这意味着操作系统负责在活动线程之间切换 CPU 执行。但是，Ruby 控制着它的全局虚拟机锁（GVL）。Ruby 在线程执行方面扮演了重要角色，Ruby 通过选择将 GVL 交给哪个 Ruby 线程以及何时收回 GVL 来决定操作系统正在执行哪个线程。&lt;/p&gt;

&lt;p&gt;（旁白：Ruby 3.3 引入了 M:N 线程，这解耦了 Ruby 线程与操作系统线程的映射，但在这里忽略这个细节。）&lt;/p&gt;

&lt;p&gt;Ruby VM 内部发生的事情在&lt;a href="https://ruby-hacking-guide.github.io/thread.html" rel="nofollow" target="_blank" title=""&gt;《Ruby 黑客指南》&lt;/a&gt;中有非常好的 C 语言级别的解释。但我会尽力在这里简要解释：&lt;/p&gt;

&lt;p&gt;当线程到达队列的顶部并获得 GVL 时，该线程将开始运行其 Ruby 代码，直到它放弃 GVL。放弃 GVL 可能出于以下两个原因：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;当线程从执行 Ruby 代码转向进行 IO 操作时，它会释放 GVL（通常情况下；如果 IO 库没有这样做，通常被认为是一个 bug）。当线程完成其 IO 操作后，线程会排到队列的末尾。&lt;/li&gt;
&lt;li&gt;当线程执行时间超过线程“量子 (quantum)”的长度时，Ruby VM 会收回 GVL，线程再次回到队列的末尾。Ruby 线程“量子”默认为 100ms（这可以通过 Thread#priority 配置，或者从 &lt;a href="https://bugs.ruby-lang.org/issues/20861" rel="nofollow" target="_blank" title=""&gt;Ruby 3.4 开始直接通过环境变量配置&lt;/a&gt;）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;那个第二种情况相当有趣。当一个 Ruby 线程开始运行时，Ruby 虚拟机使用另一个后台线程（在虚拟机级别），该线程休眠 10 毫秒（“滴答（Tick）”），然后检查 Ruby 线程已经运行了多长时间。如果线程运行的时间超过了量子的长度，Ruby 虚拟机就会从活跃线程中收回 GVL（“抢占”），并将 GVL 交给在 GVL 队列中等待的下一个线程。之前正在执行的线程现在会排到队列的末尾。换句话说：&lt;/p&gt;
&lt;h5 id="“线程量子(quantum) 决定了线程通过队列的速度，且不会比滴答(Tick) 更快。”"&gt;“线程量子 (quantum) 决定了线程通过队列的速度，且不会比滴答 (Tick) 更快。”&lt;/h5&gt;
&lt;p&gt;就是这样！这就是 Ruby 线程争用的情况。一切都井然有序，只是可能比预期或希望的要花费更长的时间。&lt;/p&gt;
&lt;h2 id="有什么问题"&gt;有什么问题&lt;/h2&gt;
&lt;p&gt;多线程行为中令人畏惧的“尾部延迟（Tail Latency）”可能会发生，这与“Ruby 线程量子”（Ruby Thread Quantum）有关。&lt;/p&gt;

&lt;p&gt;比如：当你有一个时间非常短请求时，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个可能需要 10 毫秒请求，比如向 Memcached/Redis 发起十个 1 毫秒的调用以获取一些缓存值，然后返回它们（I/O 密集型线程）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是它相邻的运行线程是这样：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一个需要 1000 毫秒的请求，大部分时间都花在字符串操作上，例如一个后台线程正在处理一堆复杂的哈希和数组，并将它们序列化成一个要发送到埋点服务器的数据。或者为 Turbo Broadcasts 渲染慢速/大型/复杂的视图（CPU 密集型线程）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在这种情况下，CPU 密集型线程将非常贪婪地持有 GVL，它看起来会是这样：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IO 密集线程：启动 1 毫秒网络请求并释放 GVL&lt;/li&gt;
&lt;li&gt;CPU 密集线程：在 GVL 被取回之前，在 CPU 上执行 100 毫秒的工作&lt;/li&gt;
&lt;li&gt;IO 密集线程：再次获取 GVL 并启动下一个 1 毫秒网络请求并释放 GVL&lt;/li&gt;
&lt;li&gt;CPU 密集线程：在 GVL 被取回之前，在 CPU 上执行 100 毫秒的工作&lt;/li&gt;
&lt;li&gt;重复……再重复……&lt;/li&gt;
&lt;li&gt;现在 1,000 毫秒后，理论上应该只花费 10 毫秒的 I/O 密集型线程终于完成了。这非常糟糕！&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这是在这个只有两个线程的简单场景中最坏的情况。随着更多不同工作负载的线程，你可能会遇到更多的问题。Ivo Anjo 也&lt;a href="https://ivoanjo.me/blog/2023/02/11/ruby-unexpected-io-vs-cpu-unfairness/" rel="nofollow" target="_blank" title=""&gt;对此进行了讨论&lt;/a&gt;。你可以通过降低整体线程量子来加快速度，或者通过降低 CPU 密集型线程的优先级（降低这个线程的量子）来实现。这将导致 CPU 密集型线程被更细致地切分，但由于最小时间片由时钟周期 Tick（10 毫秒）决定，所以对于上面这个 I/O 密集型线程来说，其等待时间理论上永远不会低于 100 毫秒，这比优化前快了 10 倍。&lt;/p&gt;
&lt;h2 id="译者注"&gt;译者注&lt;/h2&gt;&lt;h3 id="1. 考证 quantum 的存在"&gt;1. 考证 quantum 的存在&lt;/h3&gt;
&lt;p&gt;线程的 quantum 时间是 100ms&lt;/p&gt;

&lt;p&gt;源码位置 &lt;a href="https://github.com/ruby/ruby/blob/8dbbc79e8b19f0bf000f88d0e45b26185c5fcd4b/thread.c#L119" rel="nofollow" target="_blank" title=""&gt;thread.c#L119&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .....&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;thread_default_quantum_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// .....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. 考证 Tick（10ms） 的存在"&gt;2. 考证 Tick（10ms）的存在&lt;/h3&gt;
&lt;p&gt;源码位置 &lt;a href="https://github.com/ruby/ruby/blob/8dbbc79e8b19f0bf000f88d0e45b26185c5fcd4b/thread_pthread.c#L2829" rel="nofollow" target="_blank" title=""&gt;thread_pthread.c#L2829&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;timer_thread_set_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rb_vm_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="c"&gt;#if 0
    return 10; // ms
#else&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;ractor_sched_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// .......&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ms&lt;/span&gt;
       &lt;span class="c1"&gt;// .......&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// .......&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>Mark24</author>
      <pubDate>Fri, 07 Feb 2025 16:37:20 +0800</pubDate>
      <link>https://ruby-china.org/topics/44036</link>
      <guid>https://ruby-china.org/topics/44036</guid>
    </item>
    <item>
      <title>convert2ascii：终端竟能看电影，我写了一个可以让图片/视频变成  ASCII Art 的 gem🔮</title>
      <description>&lt;p&gt;最近沉迷 ASCII Art 觉得还挺有趣的，做了一个 gem。感兴趣可以看：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Mark24Code/convert2ascii" rel="nofollow" target="_blank"&gt;https://github.com/Mark24Code/convert2ascii&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;提供两个可执行命令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; image2ascii：将图片转换为 ASCII art，并在终端中显示。&lt;/li&gt;
&lt;li&gt;video2ascii：将视频转换为 ASCII art，你可以在终端中保存或播放。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;它还作为宝石（gem）提供类：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert2Ascii::Image2Ascii&lt;/li&gt;
&lt;li&gt;Convert2Ascii::Video2Ascii&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;你可以在你的代码中使用它，制作自己的 ASCII art&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/af47bee5-124c-4817-85f4-3a729d29357e.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/f11f1d0f-5d62-4ead-99d0-2abf323541fb.gif!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我不是第一个人做这个的。最近沉迷 ASCII Art，利用一个周末制作，做的过程中很有趣，学到了很多。&lt;/p&gt;

&lt;p&gt;也支持 docker 体验。&lt;/p&gt;

&lt;p&gt;目前处在一个初始功能，可以给我反馈，非常欢迎！&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Mon, 13 Jan 2025 00:28:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/44017</link>
      <guid>https://ruby-china.org/topics/44017</guid>
    </item>
    <item>
      <title>新年快乐！写了一个彩蛋送给大家！</title>
      <description>&lt;p&gt;新年快乐！写了一个彩蛋送给大家！&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gem install firew0rks &amp;amp;&amp;amp; firew0rks&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;祝大家新年多多发财 💎&lt;/p&gt;

&lt;p&gt;项目地址：&lt;a href="https://github.com/Mark24Code/firew0rks" rel="nofollow" target="_blank"&gt;https://github.com/Mark24Code/firew0rks&lt;/a&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Wed, 01 Jan 2025 19:43:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/43996</link>
      <guid>https://ruby-china.org/topics/43996</guid>
    </item>
    <item>
      <title>MRuby Devkit 一个简单的脚手架，帮助你像 Go 一样把 Ruby 编译成可执行二进制文件</title>
      <description>&lt;p&gt;项目地址： &lt;a href="https://github.com/Mark24Code/mruby-devkit" rel="nofollow" target="_blank" title=""&gt;https://github.com/Mark24Code/mruby-devkit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MRuby Devkit 是一个开箱即用的脚手架。基于 MRuby 将你的 Ruby 代码打包成 二进制可执行文件。&lt;/p&gt;

&lt;p&gt;方便开发类似于 Golang 的二进制可执行文件。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;—— 灵感来自于 Golang 可以编译为二进制可执行文件的迷人特性。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接受简单的约定，专注于编写 Ruby 代码。轻松地 build 成二进制可执行文件。&lt;/p&gt;
&lt;h2 id="使用设计"&gt;使用设计&lt;/h2&gt;&lt;h2 id="0. 编写程序"&gt;0. 编写程序&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt; 下编写 ruby 程序&lt;/p&gt;
&lt;h2 id="1. 运行程序"&gt;1. 运行程序&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;模仿 golang 的 go run&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;rake run&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="2. 编译当前程序（默认使用当前计算机平台）"&gt;2. 编译当前程序（默认使用当前计算机平台）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;模仿 golang 的 go build&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;rake build&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="3.交叉编译的包"&gt;3.交叉编译的包&lt;/h2&gt;
&lt;p&gt;借助 Github Action 编译不同平台的可执行二进制文件。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以 fork 仓库在  Github Action 运行结果下可以看到构建产物。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;更多请查看 &lt;a href="https://github.com/Mark24Code/mruby-devkit" rel="nofollow" target="_blank" title=""&gt;https://github.com/Mark24Code/mruby-devkit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;后续：&lt;/p&gt;

&lt;p&gt;v1.1.0&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;添加 Ruby 转 WebAssembly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/e6156e57-d424-4213-aee2-f42252f4e843.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Thu, 27 Jun 2024 21:06:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/43776</link>
      <guid>https://ruby-china.org/topics/43776</guid>
    </item>
    <item>
      <title>Ruby 打包技术之旅</title>
      <description>&lt;p&gt;2025.11.15 追加&lt;/p&gt;

&lt;p&gt;homebrew 打包 portable ruby 的思路&lt;/p&gt;

&lt;p&gt;将所有依赖 全部静态化重构最后打包出静态 ruby，并且还可以安装 gem &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spinel-coop/rv-ruby/releases" rel="nofollow" target="_blank"&gt;https://github.com/spinel-coop/rv-ruby/releases&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;追加 &lt;/p&gt;

&lt;p&gt;2025 迎来了新的方案：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tamatebako/tebako" rel="nofollow" target="_blank"&gt;https://github.com/tamatebako/tebako&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;通过各种 hack 完全可以把 ruby 打包成独立的二进制执行文件。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;原文&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mark24code.github.io/ruby/2024/05/29/Ruby%E6%89%93%E5%8C%85%E6%8A%80%E6%9C%AF%E4%B9%8B%E6%97%85.html" rel="nofollow" target="_blank" title=""&gt;我的 BLOG&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;结论：&lt;/p&gt;

&lt;p&gt;似乎找到了 2 个实例；Linux 通过系统包管理即可。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.chocolatey.org/packages/ruby.portable" rel="nofollow" target="_blank" title=""&gt;Windows | Ruby (Portable) 3.3.1.1 &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Homebrew/homebrew-portable-ruby/pkgs/container/portable-ruby%2Fportable-ruby" rel="nofollow" target="_blank" title=""&gt;MacOS | homebrew/portable-ruby &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;原文：&lt;/p&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;大家好，我是 Mark24。&lt;/p&gt;

&lt;p&gt;设想一下，如果你在用 Ruby 开发一个 GUI 应用，或者是 游戏。如何把产物可以送到你用户的手中。尽可能的轻松跑起来？&lt;/p&gt;

&lt;p&gt;我目前感兴趣的是游戏应用。所以后面都是建立在游戏跑在终端的角度考虑。&lt;/p&gt;

&lt;p&gt;虽然我们在讨论 Ruby，但是对于所有动态脚本语言的思路是通用的。&lt;/p&gt;

&lt;p&gt;解决打包动态语言的问题。最后一公里，如何送到用户手中。&lt;/p&gt;
&lt;h2 id="思路一： 编译并静态链接，经典二进制包"&gt;思路一：编译并静态链接，经典二进制包&lt;/h2&gt;&lt;h2 id="1. 像静态语言一样，获得直接的二进制文件 ❌"&gt;1. 像静态语言一样，获得直接的二进制文件 ❌&lt;/h2&gt;
&lt;p&gt;比如 Go、Rust、Crystal 的构建产物。&lt;/p&gt;

&lt;p&gt;结论：&lt;/p&gt;

&lt;p&gt;Go、Rust、Crystal …… 他们依然是在有限条件下运行。只不过这种条件实际上特别宽泛，好像他们的产物可以在各种系统下运行。&lt;/p&gt;

&lt;p&gt;实际上 MacOS、Linux、Windows 的底层都是不鼓励静态链接。并且一些关键的包，也不提供静态链接需要的库。&lt;/p&gt;

&lt;p&gt;这是为了体积考虑，也是为了安全更新考虑。&lt;/p&gt;

&lt;p&gt;这些能够相对来说把自己打成静态链接的语言，实际上都做了大量的工作，自己实现了底层需要的部分。&lt;/p&gt;

&lt;p&gt;动态语言无法直接把代码打包成这样。这条路是违背原理的。&lt;/p&gt;
&lt;h2 id="2：极致的静态方向 ✅"&gt;2：极致的静态方向 ✅&lt;/h2&gt;
&lt;p&gt;这个思路是 MRuby。&lt;/p&gt;

&lt;p&gt;MRuby 是一个轻量级的 Ruby 为嵌入式设计。它可以交叉编译成不同的架构。被设计的尽可能的少依赖，多拓展。&lt;/p&gt;

&lt;p&gt;一定程度上，MRuby 就像是 Go。&lt;/p&gt;

&lt;p&gt;可以用 MRuby 来构建应用、游戏。MRuby 也有 SDL 的绑定&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cyberarm/mruby-gosu" rel="nofollow" target="_blank" title=""&gt;cyberarm/mruby-gosu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2.1 Dragon Ruby ✅"&gt;2.1 Dragon Ruby ✅&lt;/h3&gt;
&lt;p&gt;这里有篇演讲，Dragon Ruby 的游戏引擎设计者，如何使用 MRuby 来构建一个应用。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=s2rngApV1WU" rel="nofollow" target="_blank" title=""&gt;RubyConf 2022: Building a Commercial Game Engine using mRuby and SDL by Amir Rajan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dragon Ruby 从 IDE 到 游戏产物全部是静态二进制。&lt;/p&gt;

&lt;p&gt;但是具体的原理不详。依然不知道 Dragon Ruby 是如何做到的。&lt;/p&gt;
&lt;h3 id="2.2 Taylor ✅"&gt;2.2 Taylor ✅&lt;/h3&gt;
&lt;p&gt;Taylor 是个个人开源框架，试图挑战 Dragon Ruby。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/HellRok/Taylor" rel="nofollow" target="_blank" title=""&gt;HellRok/Taylor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Taylor 的思路也是经典思路，容器中构建一个可以被静态的环境，绕过系统 (MacOS 不允许静态链接系统 lib)。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/HellRok/Taylor/tree/f2ce4024efc28a82a65ab5f10769310d114d94af/scripts/export" rel="nofollow" target="_blank" title=""&gt;scripts/export&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些代码可能很难理解，在于他们究竟如何在发挥具体作用。&lt;/p&gt;

&lt;p&gt;Taylor 正在重大重构中，但是目前的版本，是完全可以工作的！&lt;/p&gt;
&lt;h2 id="思路二：解释器+项目代码 =&gt; 压缩包"&gt;思路二：解释器 + 项目代码 =&amp;gt; 压缩包&lt;/h2&gt;
&lt;p&gt;这个思路需要一个 可以移动执行的 Ruby 解释器。&lt;/p&gt;
&lt;h2 id="1. 静态编译 ruby 为 portable ruby ✅"&gt;1. 静态编译 ruby 为 portable ruby ✅&lt;/h2&gt;
&lt;p&gt;如果拥有了 Portable Ruby，那么 软件包 = （Portable Ruby + 项目代码）。&lt;/p&gt;

&lt;p&gt;这条路相对可行。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;静态链接的尝试 &lt;a href="https://mark24code.github.io/ruby/2024/05/27/%E4%BD%BF%E7%94%A8Ruby-build-%E5%9C%A8-MacOS%E4%B8%8A-%E7%BC%96%E8%AF%91-Portable-ruby.html" rel="nofollow" target="_blank" title=""&gt;使用 Ruby-build 在 MacOS 上 编译 Portable ruby&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还是前面的问题，Ruby 没有像 Go 等实现了全部的底层依赖的静态库。所以 编译！= portable。&lt;/p&gt;

&lt;p&gt;Portable 的重点就是，尽可能的不依赖。如果实在无法避开的依赖，比如 Linux 中的 glibc（系统底层），需要使用较低版本来编译。
因为 glibc 永远是高版本兼容低版本，所以这样尽可能的获得兼容性。&lt;/p&gt;

&lt;p&gt;Crystal、Go …… 他们一样。也只能工作在有限的 glibc 中。&lt;/p&gt;

&lt;p&gt;Crystal 给出了平台很好体现了这一点：&lt;a href="https://crystal-lang.org/reference/1.12/syntax_and_semantics/platform_support.html" rel="nofollow" target="_blank" title=""&gt;Crystal Platform Support&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;用户不需要安装 Ruby，但是需要安装 Ruby 需要的底层库。来获得动态链接库。&lt;/p&gt;

&lt;p&gt;这个思路获得了成功。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1）让你本地安装 lib；或者直接安装 ruby（过程中就获得了需要的 lib）&lt;/li&gt;
&lt;li&gt;2）打包 portable ruby&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3）使用 Mac 的 App 壳应用&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/gosu/ruby-app" rel="nofollow" target="_blank" title=""&gt;App 壳应用 Gosu&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以获得一个 Mac 的应用。&lt;/p&gt;
&lt;h4 id="1.1 Portable rub + Portable libs 🤔 ✅"&gt;1.1 Portable rub + Portable libs 🤔 ✅&lt;/h4&gt;
&lt;p&gt;前面说了，如果可以创造出 静态的包。Ruby 也可以像 Go、Java 一样。这里参考这样一个项目，尝试在容器中模拟一个这样的环境。尽可能把所需的依赖全部集成起来，产出 portable ruby&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/YOU54F/traveling-ruby" rel="nofollow" target="_blank" title=""&gt;YOU54F/traveling-ruby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不过这个产物我没怎么跑起来。但是这个是经典思路，是完全可行的。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app code + （Portable Ruby + lib） =  software&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="思路三： 普通思路，前置安装器 ✅"&gt;思路三：普通思路，前置安装器 ✅&lt;/h2&gt;
&lt;p&gt;用户安装 Ruby 运行游戏。由于前面无法实现彻底的静态打包，即使是安装依赖库，整个过程是差不多的。用户依然要安装。&lt;/p&gt;

&lt;p&gt;如果这样避不开。推荐常见的处理办法 —— 前置的安装器（Installer）。解决环境依赖问题。&lt;/p&gt;

&lt;p&gt;在 Windows 上 Ruby 是需要 安装包来安装。整个过程就像这样。&lt;/p&gt;

&lt;p&gt;这一点，在 Windows 上也成功实现了：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby2D 的 demo&lt;/li&gt;
&lt;li&gt;Raylib-bindings 的 demo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;构建过程和 Sample Project： &lt;a href="https://github.com/Mark24Code/ruby-windows-example" rel="nofollow" target="_blank" title=""&gt;ruby-windows-example&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="思路四： 切换可以打包的语言"&gt;思路四：切换可以打包的语言&lt;/h2&gt;&lt;h3 id="1. 使用 静态语言 Crystal ✅ 🕘"&gt;1. 使用 静态语言 Crystal ✅ 🕘&lt;/h3&gt;
&lt;p&gt;Crystal 的语法和 Ruby 非常相似，也有 游戏库、GUI 的绑定。&lt;/p&gt;

&lt;p&gt;可以做到类似的事情。这一点就像 C++&lt;/p&gt;

&lt;p&gt;但是缺点是 Crystal 目前还在建设中。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://crystal-lang.org/reference/1.12/syntax_and_semantics/platform_support.html" rel="nofollow" target="_blank" title=""&gt;crystal platform support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Crystal 对 MacOS ARM、Windows 的支持还不足。&lt;/p&gt;

&lt;p&gt;现在无法当作一个成熟方案。&lt;/p&gt;
&lt;h3 id="2. 使用 JRuby(Java) ✅"&gt;2. 使用 JRuby(Java) ✅&lt;/h3&gt;
&lt;p&gt;Java 其实采用了类似的思路，自己实现了底层。所以 Java 自身可以打包成静态的二进制。&lt;/p&gt;

&lt;p&gt;我们可以把打包工作建立在 Java 的基础上。&lt;/p&gt;

&lt;p&gt;这个实践方向是 Glimmer&lt;/p&gt;

&lt;p&gt;Glimmer DSL for SWT 能够在 JRuby 之上将 Ruby 应用程序打包到原生安装程序（如 Mac DMG/PKG/APP、Windows MSI/EXE 和 Linux RPM/DEB）中，使开发者能够给最终用户（非程序员）一个单一的文件来运行，以安装所有需要的内容，比如 JRuby（可以运行任何 Ruby 代码）、它的 JVM 依赖项，以及正在安装的应用程序：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_PACKAGING_AND_DISTRIBUTION.md" rel="nofollow" target="_blank" title=""&gt;GLIMMER_PACKAGING_AND_DISTRIBUTION&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Glimmer DSL for LibUI，它直接在 Ruby 上运行而不是 JRuby，也有一个关于打包 Ruby 应用程序的部分，你可能想要查看（它提到了 Windows 和 Mac 的打包解决方案）：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/glimmer-dsl-libui?tab=readme-ov-file#packaging" rel="nofollow" target="_blank" title=""&gt;readme-ov-file#packaging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以下是使用 Glimmer DSL for SWT 打包的应用程序示例，这些应用程序由最终用户安装，没有问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/MathBowling" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/MathBowling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/glimmer-cs-gladiator" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/glimmer-cs-gladiator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/are-we-there-yet" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/are-we-there-yet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/dcr" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/dcr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/glimmer_klondike_solitaire" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/glimmer_klondike_solitaire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/glimmer_metronome" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/glimmer_metronome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AndyObtiva/glimmer_wordle" rel="nofollow" target="_blank" title=""&gt;https://github.com/AndyObtiva/glimmer_wordle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些都是作者发来的例子。尝试跑了几个，没有成功。
还需要研究研究。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;如何把 Ruby 带到终端，其实一直不停的有人研究。项目生生死死。这里列举一些，供参考。&lt;/p&gt;

&lt;p&gt;1）容器打包，静态链接 portable ruby 思路：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/phusion/traveling-ruby" rel="nofollow" target="_blank" title=""&gt;phusion/traveling-ruby&lt;/a&gt; 已经不维护&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/YOU54F/traveling-ruby" rel="nofollow" target="_blank" title=""&gt;YOU54F/traveling-ruby&lt;/a&gt; 后继者&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/HellRok/Taylor" rel="nofollow" target="_blank" title=""&gt;HellRok/Taylor&lt;/a&gt; MRuby 容器打包&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ahogappa0613/kompo" rel="nofollow" target="_blank" title=""&gt;ahogappa0613/kompo&lt;/a&gt; 2024 新项目，拦截的方式，修改 Ruby 解释器，静态打包。我没跑起来；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2）临时文件系统思路：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pmq20/ruby-packer" rel="nofollow" target="_blank" title=""&gt;pmq20/ruby-packer&lt;/a&gt; 已经不维护&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3）JRuby 思路：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/AndyObtiva/glimmer" rel="nofollow" target="_blank" title=""&gt;AndyObtiva/glimmer&lt;/a&gt; 是 Gosu、Shoes 的继承者，还在开发&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4）Portable Ruby 思路：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/gosu/ruby-app" rel="nofollow" target="_blank" title=""&gt;gosu/ruby-app&lt;/a&gt; 不维护&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5）只打包应用脚本，指定系统 Ruby&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://sveinbjorn.org/platypus" rel="nofollow" target="_blank" title=""&gt;platypus&lt;/a&gt; 只打包你的脚本，封装成 app，只适合简单脚本&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6）静态语言&lt;/p&gt;

&lt;p&gt;使用 Crystal，Ruby 语法的 Go like 语言开发应用&lt;/p&gt;

&lt;p&gt;7）使用 Zig&lt;/p&gt;

&lt;p&gt;这是一个问号，Zig 作为一个新语言可以作为 C 的环境，而且自己实现了所有的静态库。&lt;/p&gt;

&lt;p&gt;不知道 Zig 作为 CRuby 的编译器会如何？但是 Zig 目前依然在发展中。&lt;/p&gt;

&lt;p&gt;8）使用容器&lt;/p&gt;

&lt;p&gt;容器技术是任何语言的一个打包工具。&lt;/p&gt;

&lt;p&gt;对于开发者友好，但是终端用户还是有门槛的。&lt;/p&gt;

&lt;p&gt;不适合游戏应用。&lt;/p&gt;

&lt;p&gt;9）Gem&lt;/p&gt;

&lt;p&gt;如果都能接受用户总归要自己安装 Ruby 的设定。&lt;/p&gt;

&lt;p&gt;把游戏、应用，封装成 gem，可以自动处理依赖、版本问题。&lt;/p&gt;

&lt;p&gt;10）切换 Ruby 的实现：CRuby 无法实现静态打包&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/artichoke/artichoke" rel="nofollow" target="_blank" title=""&gt;artichoke&lt;/a&gt; Rust 实现的 Ruby。开在开发中。（暂不支持 gem）&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/natalie-lang/natalie" rel="nofollow" target="_blank" title=""&gt;natalie&lt;/a&gt; C++ 实现的 Ruby。开发中。可以 编译 纯 Ruby 脚本。（暂不支持 gem）&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;补充：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.chocolatey.org/packages/ruby.portable" rel="nofollow" target="_blank" title=""&gt;Windows | Ruby (Portable) 3.3.1.1 &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Homebrew/homebrew-portable-ruby/pkgs/container/portable-ruby%2Fportable-ruby" rel="nofollow" target="_blank" title=""&gt;MacOS | homebrew/portable-ruby &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;

&lt;p&gt;补充资料：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ruby-compilers.com/" rel="nofollow" target="_blank" title=""&gt;Ruby Compilers 清单&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://mark24code.github.io/ruby/2024/05/29/Ruby%E6%89%93%E5%8C%85%E6%8A%80%E6%9C%AF%E4%B9%8B%E6%97%85.html" rel="nofollow" target="_blank" title=""&gt;我的 BLOG&lt;/a&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Wed, 29 May 2024 12:18:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/43711</link>
      <guid>https://ruby-china.org/topics/43711</guid>
    </item>
    <item>
      <title>使用 Ruby-build 在 MacOS 上 编译 Portable Ruby</title>
      <description>&lt;p&gt;&lt;a href="https://mark24code.github.io/ruby/2024/05/27/%E4%BD%BF%E7%94%A8Ruby-build-%E5%9C%A8-MacOS%E4%B8%8A-%E7%BC%96%E8%AF%91-Portable-ruby.html" rel="nofollow" target="_blank" title=""&gt;我的 Blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;大家好，我是 Mark24。&lt;/p&gt;

&lt;p&gt;分享下我的笔记，使用 Ruby-build 在 MacOS 上 编译 Portable ruby&lt;/p&gt;

&lt;p&gt;设想一下，如果 ruby 可以变成 portable 的，放在 U 盘上就可以带走，传输到任何一台电脑上就可以执行。&lt;/p&gt;

&lt;p&gt;Portable Ruby + 你的 Ruby 代码   的 zip 包，就像一个行走的独立软件。就像 Go 打包的一样。&lt;/p&gt;

&lt;p&gt;你还可以把他们塞入 一些壳软件里。就像 Electron 那样运行（内部是个浏览器）。&lt;/p&gt;

&lt;p&gt;当然 Ruby 社区曾经有很多方案 Traveling Ruby、Ruby Packer，都用各自的方式实现类似的效果，不过都不维护了。&lt;/p&gt;

&lt;p&gt;下面用一个简单的方法来制作 Portable Ruby。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;截止 2024-05-27 最新版本是 3.3.1。
每个版本因为特性的不同构建是一个动态的过程。就以 3.3.1 为例。&lt;/p&gt;

&lt;p&gt;过程偷懒，建立在 ruby-build(&lt;a href="https://github.com/rbenv/ruby-build" rel="nofollow" target="_blank"&gt;https://github.com/rbenv/ruby-build&lt;/a&gt;) 的基础上。&lt;/p&gt;

&lt;p&gt;不论是 asdf、rvm …… 他们的背后都是 ruby-build 一个方便安装的 standalone 的工具。ruby-build 解决了大部分的问题，我们只需要找到合适的构建参数。&lt;/p&gt;
&lt;h2 id="一、前置依赖"&gt;一、前置依赖&lt;/h2&gt;
&lt;p&gt;1.安装 Mac 的基础工具集&lt;/p&gt;

&lt;p&gt;终端输入 &lt;code&gt;xcode-select --install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2.安装上 homebrew&lt;/p&gt;

&lt;p&gt;&lt;a href="https://brew.sh/" rel="nofollow" target="_blank"&gt;https://brew.sh/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;获得 类似于 Linux 上的包管理工具&lt;/p&gt;

&lt;p&gt;3.安装 Ruby 编译需要的前置依赖&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 安装前置依赖
# ruby-build 是安装工具
# openssl@3 readline libyaml gmp 是必要的依赖
# rust 是 YJIT 必要的依赖，不装就不会构建 YJIT 功能

brew install ruby-build openssl@3 readline libyaml gmp rust
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="二、编译"&gt;二、编译&lt;/h2&gt;
&lt;p&gt;0.知识点&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C 语言（CRuby 是 C 语言项目）编译一般分为 3 个基本过程

1）预处理：处理一些前置的宏替换
2）编译：把 .c 代码文件翻译成 .o 机器码文件目标文件
3）链接：把 .o 文件和系统的底层库（比如标准输入输出）正确的关联起来。生成可执行文件

链接这部，有两个基本的实现

1）静态链接
2）动态链接

静态链接比较简单，就是把所有用到的代码打包成一个整体。软件就像一个 exe 文件，带到哪儿都可以执行。
优点就是，随处执行。缺点就是体积大，更新困难，比如你依赖的系统部分有安全缺陷。你必须整体替换。

动态链接，就是软件把用到公共部分（系统、上游 lib）的部分，指他们的动态库（linux 是 so 文件， windows 是 dll 文件，mac 里是 dylib 文件）。
优点：体积小， 如果公共部分有安全漏洞，系统更新，只需要更新动态链接库文件，所有引用的软件都会获得更新。
缺点：除了无法 portable，软件运行的前提是系统拥有相应的 库。

动态链接是常态，不论是 Linux、MacOS、Windows。动态链接的实践这么多年运行的一直很好。通常库都是按照动态链接库方向来设计的。没有提供静态库。

MacOS 还禁止系统动态库进行 静态链接。
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;最简单的编译&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;关键参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;$HOME/portable-ruby&lt;/code&gt; 是你存放的目录&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--enable-load-relative&lt;/code&gt; 地址是相对目录，这对我们移动很重要&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--with-static-linked-ext&lt;/code&gt; 静态链接&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUBY_CONFIGURE_OPTS="--enable-load-relative --with-static-linked-ext" ruby-build 3.2.2 $HOME/portable-ruby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.一些优化选项&lt;/p&gt;

&lt;p&gt;可以参考 &lt;a href="https://github.com/rbenv/ruby-build" rel="nofollow" target="_blank"&gt;https://github.com/rbenv/ruby-build&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;额外的选项&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--with-out-ext=win32,win32ole&lt;/code&gt; 去掉 MacOS 上不需要的拓展&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--disable-install-doc&lt;/code&gt; 关闭文档，减小体积&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable-install-rdoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--disable-dependency-tracking&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUBY_CONFIGURE_OPTS="--enable-load-relative --with-static-linked-ext --with-out-ext=win32,win32ole --disable-install-doc --disable-install-rdoc --disable-dependency-tracking " ruby-build 3.2.2 $HOME/portable-ruby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby-build 能做的更多，比如支持交叉编译&lt;/p&gt;
&lt;h2 id="三、Portable Ruby"&gt;三、Portable Ruby&lt;/h2&gt;
&lt;p&gt;编译正确完成，你应该获得了 portable ruby&lt;/p&gt;

&lt;p&gt;在拥有 依赖库的电脑上（对，我们前面解释了，系统部分是禁止 静态链接的）。&lt;/p&gt;

&lt;p&gt;你的可以把你的 ruby 代码 + portable ruby 放在一个文件夹里。用 一个 shell 脚本，通过相对路径连接起来执行。&lt;/p&gt;

&lt;p&gt;比如这样&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
./portable-ruby/bin/ruby ./main.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;某种意义上，Portable Ruby + Ruby Script 和 Go、Crystal 打包的可执行文件，是一样的。就是大了一点 :D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mark24code.github.io/ruby/2024/05/27/%E4%BD%BF%E7%94%A8Ruby-build-%E5%9C%A8-MacOS%E4%B8%8A-%E7%BC%96%E8%AF%91-Portable-ruby.html" rel="nofollow" target="_blank" title=""&gt;我的 Blog&lt;/a&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Mon, 27 May 2024 19:48:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/43710</link>
      <guid>https://ruby-china.org/topics/43710</guid>
    </item>
    <item>
      <title>提前祝大家 圣诞节快乐 Merry Christmas，带一个小彩蛋</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/Mark24Code/christmas_tree" rel="nofollow" target="_blank"&gt;https://github.com/Mark24Code/christmas_tree&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="前置依赖："&gt;前置依赖：&lt;/h3&gt;
&lt;p&gt;Ncurses &lt;/p&gt;

&lt;p&gt;有 vim 一般也会带这个。手动安装：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MacOS： &lt;code&gt;brew install ncurses&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Linux:  &lt;code&gt;apt install libncurses5-dev&lt;/code&gt; 或者 &lt;code&gt;apt install libncursesw5-dev&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ruby 3.0+&lt;/p&gt;

&lt;p&gt;MacOS, Linux 一般都会带，手动安装：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MacOS： &lt;code&gt;brew install ruby&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Linux:  &lt;code&gt;apt install ruby&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="远程执行"&gt;远程执行&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;gem exec christmas_tree&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;加上自己的名字：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gem exec christmas_tree --merry_to &amp;lt;yourname&amp;gt;&lt;/code&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Fri, 01 Dec 2023 16:40:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/43493</link>
      <guid>https://ruby-china.org/topics/43493</guid>
    </item>
    <item>
      <title>大家一般用 Ruby 都来做什么？（除了 Rails）</title>
      <description>&lt;p&gt;想看看大家一般用 Ruby 都来做什么？（除了 Rails）&lt;/p&gt;

&lt;p&gt;期待有缘人分享有趣的事情。&lt;img title=":grinning:" alt="😀" src="https://twemoji.ruby-china.com/2/svg/1f600.svg" class="twemoji"&gt; &lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Fri, 03 Nov 2023 19:24:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/43449</link>
      <guid>https://ruby-china.org/topics/43449</guid>
    </item>
    <item>
      <title>【翻译】Async Ruby（异步 Ruby）</title>
      <description>&lt;h2 id="【翻译】Async Ruby（异步 Ruby）"&gt;【翻译】Async Ruby（异步 Ruby）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原文作者：&lt;a href="https://brunosutic.com/" rel="nofollow" target="_blank" title=""&gt;Bruno Sutic&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文链接：&lt;a href="https://brunosutic.com/blog/async-ruby" rel="nofollow" target="_blank" title=""&gt;《Async Ruby》&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文时间：2021 年 10 月 30 日&lt;/li&gt;
&lt;li&gt;原文讨论：&lt;a href="https://news.ycombinator.com/item?id=29049881" rel="nofollow" target="_blank" title=""&gt;Hacker News 讨论&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者：&lt;a href="https://github.com/Mark24Code" rel="nofollow" target="_blank" title=""&gt;Mark24&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者 Email：mark.zhangyoung@gmail.com&lt;/li&gt;
&lt;li&gt;译文链接：&lt;a href="https://mark24code.github.io/ruby/2023/10/12/Async-Ruby.html" rel="nofollow" target="_blank"&gt;https://mark24code.github.io/ruby/2023/10/12/Async-Ruby.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ruby 已经有了异步实现！&lt;/p&gt;

&lt;p&gt;它现在就可使用，已经做好了投入生产的准备，而且它可能是过去十年甚至更久时间里 Ruby 发生的最令人振奋的事情。&lt;/p&gt;

&lt;p&gt;Async Ruby 给这门语言添加了新的并发特性；你可以将其视为“没有任何缺点的线程”。它已经在酝酿了几年，也终
于在 Ruby 3.0 中准备好进入主流。&lt;/p&gt;

&lt;p&gt;在这篇文章中，我希望向你展示异步 Ruby 的所有力量、可扩展性和魔力。&lt;strong&gt;如果你热爱 Ruby，那这应该会让你非常激动！&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="Async gem"&gt;Async gem&lt;/h3&gt;
&lt;p&gt;什么是 Async Ruby？&lt;/p&gt;

&lt;p&gt;首先，&lt;a href="https://github.com/socketry/async" rel="nofollow" target="_blank" title=""&gt;Async 只是一个 gem&lt;/a&gt;，可以通过 &lt;code&gt;gem install async&lt;/code&gt; 进行安装。这是一个相当特殊的 gem，因为 &lt;strong&gt;Matz( Ruby 的创始人) 请它加入 Ruby 的标准库&lt;/strong&gt;，但邀请还未被接受。&lt;/p&gt;

&lt;p&gt;Async Ruby 是由 &lt;a href="https://github.com/ioquatix" rel="nofollow" target="_blank" title=""&gt;Samuel Williams&lt;/a&gt; 创建的，他也是一个 Ruby 核心贡献者。Samuel 还实现了 Fiber Scheduler（纤程调度器），这是 Ruby 3.0 的一个重要特性。它是"库无关的"，未来可能有其他用途，但目前，&lt;strong&gt;纤程调度器的主要目的是使 async gem 与 Ruby 无缝集成&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;并不是很多 gem 能得到他们自定义的 Ruby 集成，但这个是值得的！&lt;/p&gt;

&lt;p&gt;所有这些都告诉你，&lt;code&gt;async&lt;/code&gt; 不是"只是外面的另一个 gem"。&lt;strong&gt;Ruby 核心团队，包括 Matz 本人，都在支持这个 gem，希望它能成功。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="Async 生态"&gt;Async 生态&lt;/h3&gt;
&lt;p&gt;Async 还是一个 gem 生态系统，这些 gem 能很好地一起工作。以下是一些最有用的例子：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;async-http&lt;/code&gt; 是一个功能丰富的 HTTP 客户端&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;falcon&lt;/code&gt; 是围绕 Async 核心构建的 HTTP 服务器&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;async-await&lt;/code&gt; 是 Async 的语法糖&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;async-redis&lt;/code&gt; 是 Redis 客户端&lt;/li&gt;
&lt;li&gt;...以及许多其他的&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;虽然上面列出的每一个 gem 都提供了一些有用的东西，但事实是你只需要核心 async gem 就可以获取它的大部分好处。&lt;/p&gt;
&lt;h3 id="异步模型（Asynchronous paradigm）"&gt;异步模型（Asynchronous paradigm）&lt;/h3&gt;
&lt;p&gt;Asynchronous programming（异步编程），（在任何语言中，包括 Ruby）允许同时运行许多事情。这最常见的是多个网络 I/O 操作（如 HTTP 请求），因为在这方面 &lt;code&gt;async&lt;/code&gt; 是最有效的。&lt;/p&gt;

&lt;p&gt;多任务操作经常带来混乱：“回调地狱（callback hell）”，“Promise hell（Promise 地狱）”，乃至 "async-await hell（async-await 地狱）" 是其他语言中 &lt;code&gt;async&lt;/code&gt; 接口的众所周知的缺点。&lt;/p&gt;

&lt;p&gt;但 Ruby 是不同的。&lt;strong&gt;由于其超群的设计，Async Ruby 不受任何这些 *-地狱的困扰。它允许编写出令人惊喜的干净、简单且有序的代码。&lt;/strong&gt;它是一个像 Ruby 一样优雅的 async 实现。&lt;/p&gt;

&lt;p&gt;注意：Async 不能绕过 Ruby 的全局解释器锁（GIL）。&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;译者注：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Async gem 以及 Fiber Scheduler 都是工作在当前主线程。他们受到 GIL 约束。&lt;/li&gt;
&lt;li&gt;不受 GIL 约束参考 &lt;a href="https://docs.ruby-lang.org/en/master/ractor_md.html" rel="nofollow" target="_blank" title=""&gt;Ractor&lt;/a&gt;，Ractor 被设计用来提供 Ruby 的并行执行功能，而不需要考虑线程安全问题。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h3 id="同步示例（Synchronous example）"&gt;同步示例（Synchronous example）&lt;/h3&gt;
&lt;p&gt;让我们从一个简单的例子开始：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码正在发起两个 HTTP 请求。单个 HTTP 请求的总持续时间为 2 秒，包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;大约 0.2 秒的网络延迟在进行请求时&lt;/li&gt;
&lt;li&gt;1.6 秒的服务器处理时间&lt;/li&gt;
&lt;li&gt;大约 0.2 秒的网络延迟在接收响应时&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;让我们运行这个示例：&lt;/p&gt;

&lt;p&gt;持续时间：4.010390391&lt;/p&gt;

&lt;p&gt;如预期，程序需要 2 x 2 秒 = 4 秒才能完成。&lt;/p&gt;

&lt;p&gt;这段代码还不错，但它运行速度慢。对于这两个请求，执行过程大概像这样：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;触发一个 HTTP（超文本传输协议）请求&lt;/li&gt;
&lt;li&gt;等待 2 秒以获取响应&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;问题在于程序在大部分时间里都处于等待状态；2 秒钟（对于计算机）就像永恒。&lt;/p&gt;
&lt;h3 id="Threads（线程）"&gt;Threads（线程）&lt;/h3&gt;
&lt;p&gt;提高多个网络请求速度的常用方法是使用线程。以下是一个示例：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;

&lt;span class="vi"&gt;@counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="vi"&gt;@counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:join&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码的输出是：&lt;/p&gt;

&lt;p&gt;持续时间：2.055751087&lt;/p&gt;

&lt;p&gt;我们将执行时间缩短到 2 秒钟，这表明请求在同时运行。那么，问题解决了吗？&lt;/p&gt;

&lt;p&gt;好吧，别过于着急：&lt;strong&gt;如果你做过任何真实世界的线程编程，你会知道线程很难。真的，非常难。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果你打算做任何严肃的线程工作，你最好习惯使用互斥（mutexes），条件变量（condition variables），处理语言级的竞态条件（race conditions）...甚至我们的简单示例在 &lt;a href="/counter" class="user-mention" title="@counter"&gt;&lt;i&gt;@&lt;/i&gt;counter&lt;/a&gt; += 1 这一行就有一个竞态条件错误！&lt;/p&gt;

&lt;p&gt;线程是困难的，并且毫无疑问下面的声明在 Ruby 社区一直被不断提及：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I regret adding threads.

                  — Matz
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Async 例子"&gt;Async 例子&lt;/h2&gt;
&lt;p&gt;鉴于所有的线程复杂性，Ruby 社区早就应该有一个更好的并发模式。有了 Async Ruby，我们终于有了一种。&lt;/p&gt;
&lt;h3 id="async-http"&gt;async-http&lt;/h3&gt;
&lt;p&gt;让我们看看使用 Async Ruby 来进行两次 HTTP 请求的同样的例子：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async/http/internet"&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="no"&gt;Async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;http_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Async&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例的输出是：&lt;/p&gt;

&lt;p&gt;持续时间：1.996420725&lt;/p&gt;

&lt;p&gt;看看总运行时间，我们可以看出请求是同时运行的。&lt;/p&gt;

&lt;p&gt;这个例子显示了 Async Ruby 程序的一般结构：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;你总是从一个传递任务的 &lt;code&gt;Async&lt;/code&gt; 块开始。&lt;/li&gt;
&lt;li&gt;这个主任务通常用于用 &lt;code&gt;task.async&lt;/code&gt; 生成更多的 &lt;code&gt;Async&lt;/code&gt; 任务。&lt;/li&gt;
&lt;li&gt;这些任务相互并发运行，也与主任务并发运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一旦你习惯了，你会发现这个结构实际上非常整洁。&lt;/p&gt;
&lt;h3 id="URI.open"&gt;URI.open&lt;/h3&gt;
&lt;p&gt;前一个例子中可以被认为是一个缺点的事情是，它使用了 &lt;code&gt;async-http&lt;/code&gt;，一个具有异步特性的 HTTP 客户端。我们大多数人有自己喜欢的 Ruby HTTP 客户端，我们不想再花时间去学习另一个 HTTP 库的详细情况。
让我们看收同样的例子，只是这次使用 URI.open：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="no"&gt;Async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与前一个例子的唯一区别是，我们用 Ruby 的标准库中的方法 &lt;code&gt;URI.open&lt;/code&gt; 替换了 &lt;code&gt;async-http&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;示例的输出是：&lt;/p&gt;

&lt;p&gt;持续时间：2.030451785&lt;/p&gt;

&lt;p&gt;这个持续时间显示了两个请求是并行运行的，所以我们认为 &lt;code&gt;URI.open&lt;/code&gt; 是异步运行的！&lt;/p&gt;

&lt;p&gt;这一切真的很好。我们不仅不需要忍受线程及其复杂性，而且我们可以使用 Ruby 的标准 &lt;code&gt;URI.open&lt;/code&gt; 来运行请求，&lt;/p&gt;

&lt;p&gt;无论是在 &lt;code&gt;Async&lt;/code&gt; 块的外部还是内部。这无疑可以为我们提供一些方便的代码重用。&lt;/p&gt;
&lt;h3 id="其他 HTTP clients"&gt;其他 HTTP clients&lt;/h3&gt;
&lt;p&gt;虽然 &lt;code&gt;URI.open&lt;/code&gt; 是普通的 Ruby，但可能并不是你喜欢的进行 HTTP 请求的方式。而且，你也不经常看到它被用于"serious work(正式的工作)"。&lt;/p&gt;

&lt;p&gt;你可能有你自己喜欢的 HTTP gem，你可能会问 "它能在 Async 中工作吗"？为了找出答案，这里有一个使用 &lt;code&gt;HTTParty&lt;/code&gt;（一种知名的 HTTP 客户端）的例子。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"httparty"&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="no"&gt;Async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个例子中，我们在一起运行了 &lt;code&gt;URI.open&lt;/code&gt; 和 &lt;code&gt;HTTParty&lt;/code&gt;，这完全没问题。&lt;/p&gt;

&lt;p&gt;输出是：&lt;/p&gt;

&lt;p&gt;持续时间：2.010069566&lt;/p&gt;

&lt;p&gt;它运行的时间稍微超过了 2 秒，这表明两个请求是并发运行的（同时进行）。
这里的要点是：&lt;strong&gt;你可以在一个 Async 上下文中运行任何 HTTP 客户端，它将会异步运行。Async Ruby 完全支持任何现有的 HTTP gem！&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="高级例子"&gt;高级例子&lt;/h2&gt;
&lt;p&gt;到目前为止，我们只看到 Async Ruby 用各种 HTTP 客户端进行请求。让我们揭示 Async 在 Ruby 3 中的全部能力。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"httparty"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/ssh"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"sequel"&lt;/span&gt;

&lt;span class="no"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postgres&lt;/span&gt;
&lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:fiber_concurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="no"&gt;Async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SSH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"164.90.237.21"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exec!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sleep 1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT pg_sleep(2)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="sb"&gt;`sleep 2`&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们扩展了包含 URI.open 和 HTTParty 的前一个例子，增加了五个附加操作：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Redis 请求&lt;/li&gt;
&lt;li&gt;使用 net-ssh gem 进行的 SSH 连接&lt;/li&gt;
&lt;li&gt;使用 sequel gem 进行的数据库查询&lt;/li&gt;
&lt;li&gt;Ruby 的 sleep 方法&lt;/li&gt;
&lt;li&gt;运行 sleep 可执行文件的系统命令。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个例子中的所有操作也需要恰好 2 秒才能运行。&lt;/p&gt;

&lt;p&gt;以下是示例输出：&lt;/p&gt;

&lt;p&gt;持续时间：2.083171146&lt;/p&gt;

&lt;p&gt;我们得到的输出结果和之前一样，这表明所有的操作都是并发运行的。哇，这有很多不同的 gem 可以异步运行！&lt;/p&gt;

&lt;p&gt;重点：&lt;strong&gt;任何阻塞操作（Ruby 解释器等待的方法）都与 Async 兼容，并将在 Ruby 3.0 和更高版本的 Async 代码块中异步工作。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;性能看起来很好：7 x 2 = 14 秒，但示例在 2 秒内完成 – 很容易得到 7 倍的提升。&lt;/p&gt;
&lt;h3 id="Fiber Scheduler（纤程调度器）"&gt;Fiber Scheduler（纤程调度器）&lt;/h3&gt;
&lt;p&gt;让我们花一点时间来反思一些重要的事情。这个例子中的所有操作（例如，URI.open，Redis，sleep）都会根据上下文的不同而表现不同：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;同步执行:
操作默认同步执行。整个 Ruby 程序（或者更具体的说，当前的线程）会等待一个操作完成后才会进行下一个操作。&lt;/li&gt;
&lt;li&gt;异步执行:
当操作包裹在一个 Async 块中时，操作会异步地执行。由此，多个 HTTP 或网络请求可以同时运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;但是，例如，&lt;code&gt;HTTParty&lt;/code&gt; 或 &lt;code&gt;sleep&lt;/code&gt; 方法如何能同步和异步同时存在呢？Async 库是否对所有这些 gems 和内部 Ruby 方法进行了猴子补丁？
这种魔术是由于 &lt;code&gt;Fiber Scheduler&lt;/code&gt;。这是 Ruby 3.0 的一个特性，使得 &lt;code&gt;async&lt;/code&gt; 能够很好地与现有的 Ruby gems 和方法集成 - 不需要任何 hack 或 猴子补丁 (Monkey patch) ！&lt;/p&gt;

&lt;p&gt;&lt;a href="https://brunosutic.com/blog/ruby-fiber-scheduler" rel="nofollow" target="_blank" title=""&gt;Fiber Scheduler 也可以单独使用&lt;/a&gt; (&lt;a href="https://mark24code.github.io/ruby/2023/10/12/Ruby-Fiber-Scheduler.html" rel="nofollow" target="_blank" title=""&gt;链接译文&lt;/a&gt;)！用这种方式，只需要几个内置的 Ruby 方法就能启用异步编程。&lt;/p&gt;

&lt;p&gt;如你所想，Fiber Scheduler 触及的代码范围非常广：它是 Ruby 当前所有的阻塞 API！这绝不仅仅是一个小功能。&lt;/p&gt;
&lt;h2 id="扩展例子"&gt;扩展例子&lt;/h2&gt;
&lt;p&gt;让我们提高效率，并展示一个 Async Ruby 擅长的另一方面：扩展 (scaling)。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"async/http/internet"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"sequel"&lt;/span&gt;

&lt;span class="no"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;max_connections: &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:fiber_concurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Warming up redis clients&lt;/span&gt;
&lt;span class="n"&gt;redis_clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:ping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;

&lt;span class="no"&gt;Async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;http_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Async&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Internet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

  &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;http_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/1.6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;redis_clients&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;blpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT pg_sleep(2)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="sb"&gt;`sleep 2`&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Duration: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此例子基于之前的那个例子，只是做了一些改动：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 &lt;code&gt;Async&lt;/code&gt; 区块中的所有内容都会被重复 &lt;code&gt;1000.times&lt;/code&gt; (运行 1000 次)，这将并发操作的数量增加到了 5000。&lt;/li&gt;
&lt;li&gt;出于性能考虑，我们将 &lt;code&gt;URI.open&lt;/code&gt; 和 &lt;code&gt;HTTParty&lt;/code&gt; 替换为了 &lt;code&gt;async-http&lt;/code&gt; HTTP &lt;code&gt;客户端。async-http&lt;/code&gt; 可以与 HTTP2 一起工作，当进行大量请求时，它的速度要快得多。&lt;/li&gt;
&lt;li&gt;SSH 操作被移除了，因为我找不到一种正确的配置方法可以使其高效地工作。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;就像之前一样，每个独立操作都需要 2 秒才能执行。其输出为：&lt;/p&gt;

&lt;p&gt;持续时间：13.672289712&lt;/p&gt;

&lt;p&gt;这表明累积运行时间为 10,000 秒的 5,000 个操作仅在 13.6 秒内就完成了！&lt;/p&gt;

&lt;p&gt;这个持续时间比前面的例子（2 秒）要长，这是因为创建这么多网络连接的开销。&lt;/p&gt;

&lt;p&gt;我们几乎没有进行性能调优（例如，调整垃圾收集，内存分配等），&lt;strong&gt;但我们仍然实现了 730 倍的“加速”，在我看来，这是一个相当令人印象深刻的结果！&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="扩容限制(Scaling limits)"&gt;扩容限制 (Scaling limits)&lt;/h3&gt;
&lt;p&gt;最好的部分是：我们只是初步探索了使用 Async Ruby 所能做到的事情。&lt;/p&gt;

&lt;p&gt;虽然线程（Threads）的最大数量是 2048（至少在我的机器上是这样），&lt;strong&gt;但是 Async tasks 的上限数量是百万级别的！&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;你真的可以同时运行百万个异步操作吗？是的，你可以 - 已经有些用户做到了。&lt;/p&gt;

&lt;p&gt;Async 真的为 Ruby 打开了新局面：想象一下一个 HTTP 服务器处理成千上万的客户，或者同一时间处理成百上千的 websocket 连接 ... 这都是可能的！&lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Async Ruby 经过了漫长而神秘的开发期，但现在它稳定且已经准备好投入生产。&lt;/strong&gt;已经有一些公司在生产环境下运行它并从中受益。要开始使用它，你可以去 &lt;a href="https://github.com/socketry/async" rel="nofollow" target="_blank" title=""&gt;Async 的仓库&lt;/a&gt;看看。&lt;/p&gt;

&lt;p&gt;唯一的注意点是，它不能和 Ruby on Rails 一起工作，因为 &lt;code&gt;ActiveRecord&lt;/code&gt; 不支持 &lt;code&gt;Async&lt;/code&gt; gem。但如果不涉及到 &lt;code&gt;ActiveRecord&lt;/code&gt;，你仍然可以在 Rails 中使用它。&lt;/p&gt;

&lt;p&gt;Async 的最大优势在于扩展网络 I/O 操作，比如进行或接收 HTTP 请求。对于 CPU 密集型的工作负载，线程是更好的选择，但至少我们不再需要把他们用于所有事情。&lt;/p&gt;

&lt;p&gt;Async Ruby 非常强大，可扩展性极高。它是一个游戏规则改变者，我希望这篇文章能证明这一点。Async 改变了 Ruby 的可能性，并且当我们所有人开始更多地“异步”思考时，它将对 Ruby 社区产生重大影响。&lt;/p&gt;

&lt;p&gt;最好的一点是，它不会使任何现有的代码变得过时。&lt;strong&gt;就像 Ruby 本身一样，Async 设计得很美，使用起来也很愉快。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;希望你在使用 Async Ruby 时编程愉快！&lt;/p&gt;

&lt;p&gt;Happy hacking with Async Ruby!&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Thu, 12 Oct 2023 14:35:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/43394</link>
      <guid>https://ruby-china.org/topics/43394</guid>
    </item>
    <item>
      <title>【翻译】Ruby Fiber Scheduler（Ruby 纤程调度器）</title>
      <description>&lt;h2 id="Ruby Fiber Scheduler（Ruby 纤程调度器）"&gt;Ruby Fiber Scheduler（Ruby 纤程调度器）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原文作者：&lt;a href="https://brunosutic.com/" rel="nofollow" target="_blank" title=""&gt;Bruno Sutic&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文链接：&lt;a href="https://brunosutic.com/blog/ruby-fiber-scheduler" rel="nofollow" target="_blank" title=""&gt;《Ruby Fiber Scheduler》&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文时间：2022 年 2 月 25 日&lt;/li&gt;
&lt;li&gt;原文讨论：&lt;a href="https://news.ycombinator.com/item?id=30488227" rel="nofollow" target="_blank" title=""&gt;Hacker News 讨论&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者：&lt;a href="https://github.com/Mark24Code" rel="nofollow" target="_blank" title=""&gt;Mark24&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;译者 Email：mark.zhangyoung@gmail.com&lt;/li&gt;
&lt;li&gt;译文链接：&lt;a href="https://mark24code.github.io/ruby/2023/10/12/Ruby-Fiber-Scheduler.html" rel="nofollow" target="_blank" title=""&gt;https://mark24code.github.io/ruby/2023/10/12/Ruby-Fiber-Scheduler.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fiber Scheduler(纤程调度器) 在 Ruby 中实现异步编程。该功能是 Ruby 3.0 的一大增强功能，并且也是优秀的 async gem 的核心组件之一。
最棒的一点是，你并不需要一个完整的框架就能开始！&lt;strong&gt;只需使用一对内置的 Ruby 方法，就能独立地实现纤程调度器并享受到异步编程的好处。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;纤程调度器主要包括两部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Fiber Scheduler interface（纤程调度器接口）
这是一套内置于编程语言中的阻塞操作钩子。钩子实现被委托给 &lt;code&gt;Fiber.scheduler&lt;/code&gt; 对象。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fiber Scheduler implementation（纤程调度器的实现）
实现了异步行为。这是一个需要程序员显式设置的对象，因为 Ruby 不提供默认的 Fiber Scheduler（纤程调度器）实现。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;非常感谢 Samuel Williams！他是 Ruby 的核心开发者，设计并实现了纤程调度器这一功能并整合到了语言中。&lt;/p&gt;
&lt;h2 id="Fiber Scheduler interface（纤程调度器接口）"&gt;Fiber Scheduler interface（纤程调度器接口）&lt;/h2&gt;
&lt;p&gt;Fiber Scheduler（纤程调度器）接口是一套阻塞操作的钩子，它允许在阻塞操作发生时插入异步行为。它像是带有反转的回调：当异步回调被执行时，主阻塞方法不会运行。
这些钩子在 &lt;a href="https://docs.ruby-lang.org/en/3.1/Fiber/SchedulerInterface.html" rel="nofollow" target="_blank" title=""&gt;Fiber::SchedulerInterface&lt;/a&gt; 类中有文档记录。这个 Ruby 功能背后的一些主要思想包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;钩子是低层级的。这导致了少量的钩子，每个钩子处理许多高层级方法的行为。例如，&lt;code&gt;#address_resolve&lt;/code&gt; 钩子负责处理大约 20 个方法。&lt;/li&gt;
&lt;li&gt;钩子只在 &lt;code&gt;Fiber.scheduler&lt;/code&gt; 对象设置后才会工作，钩子的实现被委托给该对象。&lt;/li&gt;
&lt;li&gt;钩子的行为应该是异步的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="Hook implementation (钩子实现)"&gt;Hook implementation (钩子实现)&lt;/h3&gt;
&lt;p&gt;让我们看一个示例，显示如何实现 &lt;code&gt;Kernel#sleep&lt;/code&gt; 钩子。在实践中，所有的钩子都是用 C 语言编写的，但为了清晰起见，这里使用了 Ruby 伪代码。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Kernel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduler&lt;/span&gt;
      &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kernel_sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;synchronous_sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上代码的阅读方式如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;如果设置了 &lt;code&gt;Fiber.scheduler&lt;/code&gt; 对象 - 运行其 &lt;code&gt;#kernel_sleep&lt;/code&gt; 方法。&lt;code&gt;#kernel_sleep&lt;/code&gt; 应该异步运行 &lt;code&gt;sleep&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;否则，执行常规的 &lt;code&gt;synchronous_sleep&lt;/code&gt;，它会阻塞当前线程直到 &lt;code&gt;sleep&lt;/code&gt; 完成。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其他的钩子的工作方式也类似。&lt;/p&gt;
&lt;h3 id="Blocking operations（阻塞操作）"&gt;Blocking operations（阻塞操作）&lt;/h3&gt;
&lt;p&gt;已经多次提到了"Blocking operations（阻塞操作）"这个概念，但它到底是什么意思呢？&lt;strong&gt;阻塞操作是指任何 Ruby 进程（更具体地说：当前线程）最终会等待的操作。&lt;/strong&gt;一个更具描述性的名称是“waiting operations（等待操作）”。
一些例子如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sleep&lt;/code&gt; 方法。&lt;/li&gt;
&lt;li&gt;I/O 操作如 &lt;code&gt;URI.open("https://brunosutic.com")&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;系统命令，例如 &lt;code&gt;curl https://www.ruby-lang.org&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;Thread#join&lt;/code&gt; 等待线程结束。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;作为一个反例，以下代码片段需要一段时间才能完成，但不包含阻塞操作：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;include?&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;

  &lt;span class="n"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取 &lt;code&gt;fibonacci(100)&lt;/code&gt; 的结果需要等待很长时间，但只有程序员在等待！整个时间 Ruby 解释器都在工作，后台进行计算。一个简单的斐波那契实现并不包含阻塞操作。&lt;/p&gt;

&lt;p&gt;发展对阻塞操作是什么（和不是什么）的直觉是值得的，&lt;strong&gt;因为异步编程的整个目标就是同时等待多个阻塞操作&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="Fiber Scheduler implementation（纤程调度器实现）"&gt;Fiber Scheduler implementation（纤程调度器实现）&lt;/h2&gt;
&lt;p&gt;纤程调度器实现是 Fiber Scheduler 功能的第二大部分。&lt;/p&gt;

&lt;p&gt;如果你想在 Ruby 中启用异步行为，你需要为当前线程设置一个 &lt;code&gt;Fiber Scheduler&lt;/code&gt; 对象。这是通过 &lt;code&gt;Fiber.set_scheduler(scheduler)&lt;/code&gt; 方法完成的。实现通常是一个定义了所有 &lt;code&gt;Fiber::SchedulerInterface&lt;/code&gt; 方法的类。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ruby 不提供默认的 &lt;code&gt;Fiber Scheduler&lt;/code&gt; 类，也没有可以用于此目的的对象&lt;/strong&gt;。这看起来不寻常，但实际上不将 &lt;code&gt;Fiber Scheduler&lt;/code&gt; 实现包含在语言中是一个好的长期决定。最好将这种相对快速演变的关注点留在 Ruby 核心之外。
从头开始编写 &lt;code&gt;Fiber Scheduler&lt;/code&gt; 类是一项复杂的任务，所以最好使用现有的解决方案。实现的列表，它们的主要区别和推荐可以在 &lt;a href="https://github.com/bruno-/fiber_scheduler_list" rel="nofollow" target="_blank" title=""&gt;Fiber Scheduler List&lt;/a&gt; 项目中找到。&lt;/p&gt;
&lt;h3 id="举个例子"&gt;举个例子&lt;/h3&gt;
&lt;p&gt;让我们来看看仅使用 &lt;code&gt;Fiber Scheduler&lt;/code&gt; 可以做什么。
所有示例都使用 Ruby 3.1 和来自 &lt;a href="https://github.com/bruno-/fiber_scheduler" rel="nofollow" target="_blank" title=""&gt;fiber_scheduler&lt;/a&gt; gem 的 &lt;code&gt;FiberScheduler&lt;/code&gt; 类，这个 gem 由我维护。这个 gem 对于示例来说不是一个硬性依赖项，因为如果将以下代码片段中的 &lt;code&gt;FiberScheduler&lt;/code&gt; 替换为另一个 &lt;code&gt;Fiber Scheduler&lt;/code&gt; 类，每个代码片段仍然应该可以工作。&lt;/p&gt;
&lt;h4 id="基本示例"&gt;基本示例&lt;/h4&gt;
&lt;p&gt;这里有一个简单的示例：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"fiber_scheduler"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_scheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FiberScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的代码创建了两个纤程，每个纤程都进行一次 HTTP 请求。这些请求并行运行，整个程序在 2 秒内完成。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fiber.set_scheduler(FiberScheduler.new)&lt;/code&gt;
在当前线程中设置一个 &lt;code&gt;Fiber Scheduler&lt;/code&gt;，这使得 &lt;code&gt;Fiber.schedule&lt;/code&gt; 方法可以工作，且 fiber 可以异步行为。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Fiber.schedule { ... }&lt;/code&gt;
这是一个内置的 Ruby 方法，用于启动新的异步 fiber。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;这个示例仅使用了标准的 Ruby 方法 - &lt;code&gt;Fiber.set_scheduler&lt;/code&gt; 和 &lt;code&gt;Fiber.schedule&lt;/code&gt; 自 Ruby 3.0 版本以来就一直可用。&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="高级例子"&gt;高级例子&lt;/h4&gt;
&lt;p&gt;我们来看看运行多种不同操作是什么样子的：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"fiber_scheduler"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"httparty"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"open-uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"sequel"&lt;/span&gt;

&lt;span class="no"&gt;DB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postgres&lt;/span&gt;
&lt;span class="no"&gt;Sequel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:fiber_concurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_scheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FiberScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Use any HTTP library&lt;/span&gt;
  &lt;span class="no"&gt;HTTParty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://httpbin.org/delay/2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Works with any TCP protocol library&lt;/span&gt;
  &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Make database queries&lt;/span&gt;
  &lt;span class="no"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT pg_sleep(2)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Run system commands&lt;/span&gt;
  &lt;span class="sb"&gt;`sleep 2`&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果我们顺序运行这个程序，它大约需要 12 秒才能完成。但是由于这些操作是并行运行的，所以总的运行时间仅仅超过 2 秒。
你并不仅限于发起 HTTP 请求。&lt;strong&gt;任何内置在 Ruby 中或由外部 gem 实现的阻塞操作都可以工作！&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id="扩展示例"&gt;扩展示例&lt;/h4&gt;
&lt;p&gt;这是一个简单的，显然是人为刻意的示例，同时运行一万个操作。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"fiber_scheduler"&lt;/span&gt;

&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_scheduler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;FiberScheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述代码的完成时间略超过 2 秒。&lt;/p&gt;

&lt;p&gt;由于其低开销，&lt;code&gt;sleep&lt;/code&gt; 方法被选择用于扩展示例。如果我们使用网络请求，由于需要建立数千个连接并进行 SSL 握手等，执行时间将会更长。&lt;/p&gt;

&lt;p&gt;异步编程的主要优势之一是能够同时等待许多阻塞操作。&lt;strong&gt;阻塞操作数量的增加将增加这种优势。幸运的是，运行大量协程 (fibers) 非常简单。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ruby 只需要一个纤程调度器（Fiber Scheduler）和一些内置方法就可以异步工作 - 不需要任何框架！&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;使其工作很容易。选择一个纤程调度器（Fiber Scheduler）实现，然后使用以下这些方法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Fiber.set_scheduler(scheduler)&lt;/code&gt;为当前线程设置一个纤程调度器（Fiber Scheduler），使阻塞操作能够异步执行。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Fiber.schedule { ... }&lt;/code&gt; 启动一个新的纤程，该纤程与其他纤程并发运行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一旦你开始运行，&lt;strong&gt;你可以通过将它包装在一个 &lt;code&gt;Fiber.schedule&lt;/code&gt; 块中来使任何代码异步化&lt;/strong&gt;。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Fiber&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;SynchronousCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整个库可以轻松地使用这种方法转换为异步，而且往往不需要比这里展示的更多努力。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;异步编程的重大好处是并行化阻塞/等待操作以减少程序运行时间。&lt;/strong&gt;这通常意味着在单个 CPU 上运行更多的操作，或者更好地，在你的 Web 服务器上处理更多的请求。&lt;/p&gt;

&lt;p&gt;祝你使用纤程调度器（Fiber Scheduler）愉快！&lt;/p&gt;

&lt;p&gt;Happy hacking with Fiber Scheduler!&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Thu, 12 Oct 2023 13:38:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/43383</link>
      <guid>https://ruby-china.org/topics/43383</guid>
    </item>
    <item>
      <title>Crystal 2023 年发展的怎么样了？</title>
      <description>&lt;p&gt;Crystal 2023 年发展的怎么样了？&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Mon, 09 Oct 2023 21:02:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/43376</link>
      <guid>https://ruby-china.org/topics/43376</guid>
    </item>
    <item>
      <title>《Programming Ruby》”镐书“即将迎来更新 -- 基于 Ruby3.2</title>
      <description>&lt;p&gt;&lt;a href="https://pragprog.com/titles/ruby5/programming-ruby-3-2-5th-edition/" rel="nofollow" target="_blank"&gt;https://pragprog.com/titles/ruby5/programming-ruby-3-2-5th-edition/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/990bcddd-ae6d-4317-9978-89e02c6f808e.jpg!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Sat, 07 Oct 2023 10:37:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/43370</link>
      <guid>https://ruby-china.org/topics/43370</guid>
    </item>
    <item>
      <title>分享一个自制 gem: VistualCall 一个生成调用图谱的 gem</title>
      <description>&lt;p&gt;做一个可视化追踪调用的 gem，比较粗糙，分享下。&lt;/p&gt;

&lt;p&gt;项目地址：&lt;a href="https://github.com/Mark24Code/vistual_call" rel="nofollow" target="_blank"&gt;https://github.com/Mark24Code/vistual_call&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="VistualCall"&gt;VistualCall&lt;/h2&gt;
&lt;p&gt;VistualCall is a gem to help you trace your code and export beautiful vistual call graph.&lt;/p&gt;
&lt;h2 id="Introduction"&gt;Introduction&lt;/h2&gt;&lt;h2 id="Dependency"&gt;Dependency&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Graphviz&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You need to install &lt;a href="https://graphviz.org/" rel="nofollow" target="_blank" title=""&gt;Graphviz&lt;/a&gt; by yourself.&lt;/p&gt;

&lt;p&gt;Go to install &lt;a href="https://graphviz.org/download/" rel="nofollow" target="_blank" title=""&gt;graphviz&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="Usage"&gt;Usage&lt;/h2&gt;&lt;h3 id="1. Install gem"&gt;1. Install gem&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;gem install vistual_call&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="2. Only the method needs to be wrapped."&gt;2. Only the method needs to be wrapped.&lt;/h3&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'vistual_call'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_c&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_b&lt;/span&gt;
  &lt;span class="n"&gt;call_c&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_a&lt;/span&gt;
  &lt;span class="n"&gt;call_b&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;VistualCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;call_a&lt;/span&gt; &lt;span class="c1"&gt;# enter call&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/3dbf75c7-1fb2-4230-909a-1d336d49eb1e.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;The method after each node is call order number. This will help your understand the order of the function call.&lt;/p&gt;
&lt;h4 id="Sinatra"&gt;Sinatra&lt;/h4&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/89ddacc4-be85-4a4f-8cf2-83aa6ec0437f.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="3. More information"&gt;3. More information&lt;/h2&gt;&lt;h2 id="configuration"&gt;configuration&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# you can pass options&lt;/span&gt;
&lt;span class="no"&gt;VistualCall&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# run your code here...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Options:&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;type&lt;/th&gt;
&lt;th&gt;required&lt;/th&gt;
&lt;th&gt;explain&lt;/th&gt;
&lt;th&gt;example&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;label&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;标题&lt;/td&gt;
&lt;td&gt;Hello&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labelloc&lt;/td&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;标题位置： :top :bottom :center&lt;/td&gt;
&lt;td&gt;:top&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;labeljust&lt;/td&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;标题对齐位置 :left, :center, :right&lt;/td&gt;
&lt;td&gt;:center&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;direction&lt;/td&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;绘制方向，依次是 :TB(从上到下)，:LR(从左到右，默认方式),:BT(从下到上),:RL(从右到左)&lt;/td&gt;
&lt;td&gt;:LR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;format&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;输出图片格式，查看 &lt;a href="https://graphviz.org/docs/outputs/" rel="nofollow" target="_blank" title=""&gt;graphviz 支持输出格式&lt;/a&gt; 'png','svg'&lt;/td&gt;
&lt;td&gt;默认 'png'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;output&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;导出图片绝对路径&lt;/td&gt;
&lt;td&gt;默认家目录下 &lt;code&gt;vistual_call_result.png&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;theme&lt;/td&gt;
&lt;td&gt;Symbol&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;配色主题 :sky, :lemon&lt;/td&gt;
&lt;td&gt;默认 :sky&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;show_dot&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;展示 dot 内容&lt;/td&gt;
&lt;td&gt;默认 false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;show_order_number&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;输出调用序号&lt;/td&gt;
&lt;td&gt;默认 true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jump_list&lt;/td&gt;
&lt;td&gt;Array(String)&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;跳过节点，默认 ["Kernel#class", "Kernel#frozen?"]&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;heightlight_match&lt;/td&gt;
&lt;td&gt;Regex&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;默认高亮匹配 label，默认 /method_missing/&lt;/td&gt;
&lt;td&gt;/method_missing/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="LICENSE"&gt;LICENSE&lt;/h2&gt;
&lt;p&gt;The gem is available as open source under the terms of the &lt;a href="https://opensource.org/licenses/MIT" rel="nofollow" target="_blank" title=""&gt;MIT License&lt;/a&gt;.&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Mon, 24 Apr 2023 22:54:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/43034</link>
      <guid>https://ruby-china.org/topics/43034</guid>
    </item>
    <item>
      <title>分享一个自己的脚本。帮助自动配置部署 Rime 输入法。Enjoy~ 🍻</title>
      <description>&lt;h2 id="Rime auto deploy"&gt;Rime auto deploy&lt;/h2&gt;
&lt;p&gt;⚠️ 参考最新 README： &lt;a href="https://github.com/Mark24Code/rime-auto-deploy" rel="nofollow" target="_blank"&gt;https://github.com/Mark24Code/rime-auto-deploy&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="系统"&gt;系统&lt;/h2&gt;
&lt;p&gt;⚠️ 支持：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MacOS ✅&lt;/li&gt;
&lt;li&gt;Linux 发行版 ✅&lt;/li&gt;
&lt;li&gt;Windows ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/b85326bb-ef9c-40de-a6c8-1444e81831b9.jpeg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/Mark24/bd3c22c5-6a2e-4001-8e80-0df1c75faa78.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="依赖"&gt;依赖&lt;/h2&gt;
&lt;p&gt;安装 &lt;code&gt;Ruby 3&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mac OS &lt;code&gt;brew install ruby&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Debian Linux distro &lt;code&gt;sudo apt install ruby&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;其他 Linux 根据自己情况判断。有些 Linux 可能自带 Ruby&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ MacOS 脚本会自动帮助安装 Rime，Linux 下由于发行版、Rime 衍生方式太多，需要自行提前安装 Rime。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For Fcitx5, install fcitx5-rime.
For Fcitx, install fcitx-rime.
For IBus, install ibus-rime.

more: https://wiki.archlinux.org/title/Rime
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用方法"&gt;使用方法&lt;/h2&gt;
&lt;p&gt;step1: 克隆到本地&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git clone --depth=1 https://github.com/Mark24Code/rime-auto-deploy.git --branch v2.2.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;step2: 执行部署脚本&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./installer.rb&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="自动部署内容："&gt;自动部署内容：&lt;/h2&gt;&lt;h2 id="step1: 确认安装 Rime 输入法，自动安装"&gt;step1: 确认安装 Rime 输入法，自动安装&lt;/h2&gt;
&lt;p&gt;需要用户自行登出，重进系统，设置 Rime 输入法为系统输入法&lt;/p&gt;
&lt;h2 id="step2: 备份 Rime 默认配置"&gt;step2: 备份 Rime 默认配置&lt;/h2&gt;&lt;h2 id="step3: 自动安装 Rime-ice 配置"&gt;step3: 自动安装 Rime-ice 配置&lt;/h2&gt;&lt;h2 id="step4: 自动追加自定义配置模板"&gt;step4: 自动追加自定义配置模板&lt;/h2&gt;&lt;h3 id="参考&amp;amp;感谢："&gt;参考&amp;amp;感谢：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;流程参考 Tiwtter： &lt;a href="/lewangdev" class="user-mention" title="@lewangdev"&gt;&lt;i&gt;@&lt;/i&gt;lewangdev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;配置来源：&lt;a href="https://github.com/iDvel/rime-ice" rel="nofollow" target="_blank"&gt;https://github.com/iDvel/rime-ice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>Mark24</author>
      <pubDate>Fri, 07 Apr 2023 00:25:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/42988</link>
      <guid>https://ruby-china.org/topics/42988</guid>
    </item>
    <item>
      <title>代码会说话之一个脚本帮你检查项目 996 状态</title>
      <description>&lt;h2 id="马上要进新组了，一看他们的仓库眼泪留下来"&gt;马上要进新组了，一看他们的仓库眼泪留下来&lt;/h2&gt;
&lt;p&gt;项目地址 &lt;a href="https://github.com/Mark24Code/check_996" rel="nofollow" target="_blank" title=""&gt;Mark24Code/check_996&lt;/a&gt;&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="Check 996"&gt;Check 996&lt;/h2&gt;
&lt;p&gt;帮助你检查项目 996 状态。😎&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="使用说明"&gt;使用说明&lt;/h2&gt;&lt;h2 id="依赖项目"&gt;依赖项目&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;确保你有  &lt;code&gt;ruby 2.7+&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;如果有 &lt;code&gt;curl&lt;/code&gt;  可以帮助远程执行&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="步骤一:"&gt;步骤一：&lt;/h3&gt;
&lt;p&gt;终端，进入你想统计的 git 仓库&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &amp;lt;/path/to/your/git_repo&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="步骤二"&gt;步骤二&lt;/h3&gt;
&lt;p&gt;终端使用如下命令&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;curl support&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Mark24Code/check_996/main/check_996.rb&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="更多建议："&gt;更多建议：&lt;/h2&gt;
&lt;p&gt;脚本下载在本地可以直接使用参数，远程执行也可以使用参数，使用 &lt;code&gt;--&lt;/code&gt; 分隔参数： &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;  -- -s 10:30 -e 19:30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如自定义理论上的工作时间：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Mark24Code/check_996/main/check_996.rb&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; 10:30 &lt;span class="nt"&gt;-e&lt;/span&gt; 19:30
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="额外参数说明"&gt;额外参数说明&lt;/h2&gt;&lt;h4 id="过滤器"&gt;过滤器&lt;/h4&gt;
&lt;p&gt;如果我不想对全量 git 进行计算，只关心一段时间，可以使用 -f 参数&lt;/p&gt;

&lt;p&gt;提供人性化语义化参数 &lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;, &lt;span class="nt"&gt;--filter&lt;/span&gt; FILTER              &lt;span class="nb"&gt;time &lt;/span&gt;range filter  e.g. last_[day|week|month|year] last_5_[day|week|month|year]   &lt;span class="s1"&gt;'2022-01-01 08:10:00,2022-10-01 08:10:00'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-f&lt;/span&gt; last_week
&lt;span class="nt"&gt;-f&lt;/span&gt; last_month
&lt;span class="nt"&gt;-f&lt;/span&gt; last_25_days
&lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'2022-01-01 08:10:00,2022-10-01 08:10:00'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="统计方式"&gt;统计方式&lt;/h3&gt;
&lt;p&gt;默认使用 &lt;code&gt;git log --all&lt;/code&gt; 会在当前分支进入可触达分支，也可以自己定义，但是检查必须是 &lt;code&gt;git log xxxx&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;, &lt;span class="nt"&gt;--git-log&lt;/span&gt; GIT_LOG_CMD        use git log &lt;span class="nb"&gt;command&lt;/span&gt;, default is &lt;span class="sb"&gt;`&lt;/span&gt;git log &lt;span class="nt"&gt;--all&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <author>Mark24</author>
      <pubDate>Tue, 16 Aug 2022 18:59:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/42599</link>
      <guid>https://ruby-china.org/topics/42599</guid>
    </item>
    <item>
      <title>用 100 行 Ruby 代码模拟 JavaScript 的 Eventloop</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是 Mark24&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;代码仓库：&lt;a href="https://github.com/Mark24Code/rb_simulate_eventloop" rel="nofollow" target="_blank" title=""&gt;Mark24Code/rb_simulate_eventloop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;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=rubychina" rel="nofollow" target="_blank" title=""&gt;本文博客地址&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;我们都知道 JavaScript 是单线程的。&lt;/p&gt;

&lt;p&gt;今天看到一个有趣的&lt;a href="https://www.v2ex.com/t/871848#reply95" rel="nofollow" target="_blank" title=""&gt;帖子 www.v2ex.com/t/871848&lt;/a&gt;，主要是争论 JavaScript 的优缺点。我看到这个评论觉得很有意思：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@qrobot:

....省略....


多线程下会消耗以下资源

1. 切换页表全局目录
2. 切换内核态堆栈
3. 切换硬件上下文（进程恢复前，必须装入寄存器的数据统称为硬件上下文）
ip(instruction pointer)：指向当前执行指令的下一条指令
bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址
sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址
cr3:页目录基址寄存器，保存页目录表的物理地址
......

4. 刷新 TLB
5. 系统调度器的代码执行

....省略.....

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这位同学列举了多线程切换的时候发生了什么。
这样给了一种很直观的感受，就是多线程切换的时候发生了很多事情，实际上会比单线程（只需要切换函数上下文）要消耗点更多的资源。&lt;/p&gt;

&lt;p&gt;实际上凡是交互的软件，最终都是 单线程模型 + 事件驱动辅助。&lt;/p&gt;

&lt;p&gt;从熟悉的浏览器、游戏、应用程序……都是如此。&lt;/p&gt;

&lt;p&gt;也有多线程实现的。这里&lt;a href="https://news.ycombinator.com/item?id=10490627" rel="nofollow" target="_blank" title=""&gt;Multithreaded toolkits: A failed dream? (2004) &lt;/a&gt; 有很多讨论。&lt;/p&gt;

&lt;p&gt;实际上单线程模型是最后的胜出者。&lt;/p&gt;

&lt;p&gt;JavaScript 内部单线程处理任务，主要是有一个 EventLoop 单线程的循环实现。&lt;/p&gt;

&lt;p&gt;我们可以通过 JavaScript 的表现，反推实现一下 EventLoop。&lt;/p&gt;
&lt;h2 id="EventLoop 实现"&gt;EventLoop 实现&lt;/h2&gt;&lt;h3 id="JavaScript 的行为"&gt;JavaScript 的行为&lt;/h3&gt;
&lt;p&gt;我们知道 &lt;code&gt;setTimeout&lt;/code&gt; 在 JavaScript 中用来推迟任务。实际上自从 Promise 出现之后，渐渐有两个概念出现在大家的视野里。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Macrotask(宏任务）&lt;/li&gt;
&lt;li&gt;Microtask（微任务）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;setTimeout 属于宏任务，而 promise 的 then 回调属于微任务。&lt;/p&gt;

&lt;p&gt;还有一个就是 JavaScript 在第一次同步执行代码的时候，是宏任务。&lt;/p&gt;

&lt;p&gt;EventLoop 的表现是，除了第一次执行结束之后，如果有更高优先级的 微任务总是先执行微任务，然后再执行宏任务。&lt;/p&gt;

&lt;p&gt;setTimeout 是一个定时器，很特别的是他在会在计时器线程工作，运行时间之后，回调函数会被插入到 宏任务中执行。计时器线程其实不是 JavaScript 虚拟的一部分，他是浏览器的部分。&lt;/p&gt;
&lt;h3 id="Ruby 模拟"&gt;Ruby 模拟&lt;/h3&gt;
&lt;p&gt;JavaScript 是单线程的。Ruby 是支持多线程的。我们可以用 Ruby 模拟一个 单线程的核心，和单独的计时器线程，这都是很轻松的事情。&lt;/p&gt;

&lt;p&gt;其实我们听到了这个行为 —— 花 1 分钟大概能想到，EventLoop 的工作模型&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先他是一个主循环，这样才能所谓的单线程&lt;/li&gt;
&lt;li&gt;其次，既然有两种任务，应该是两种队列&lt;/li&gt;
&lt;li&gt;再者，如果第一次同步代码是宏任务，多半可以代码任务也丢到队列里&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们可以用数组当做 队列。但是 由于存在时间线程，还得用 Thread#Queue 有保障一点。&lt;/p&gt;

&lt;p&gt;大概的模型可以画出来，想这个样：&lt;/p&gt;
&lt;h3 id="Eventloop Model"&gt;Eventloop Model&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;（start)
   |
  init (e.g create TimerThread )
   |
  sync task (e.g read &amp;amp; run code) 
   |                                        
   |
 ------------------&amp;gt;
|                  |                                    -------------
|               macro_task  ---  add timer task --&amp;gt;    | TimerThread |
|   (Eventloop)    |       &amp;lt;--  insertjob result ---    -------------
|                  |
|               micro_task
|                  |
|                  |
 &amp;lt;-----------------   
   |
   |
  (end)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;完整的代码仓库 &lt;a href="https://github.com/Mark24Code/rb_simulate_eventloop" rel="nofollow" target="_blank" title=""&gt;Mark24Code/rb_simulate_eventloop&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然后我们大概用 100 行不到就可以实现如下：&lt;/p&gt;
&lt;h4 id="需要说明的是:"&gt;需要说明的是：&lt;/h4&gt;
&lt;p&gt;1) settimeout 不要用每一个新的线程来模拟，因为一旦多线程，涉及到抢占式回调，其实返回的时间不确定。你的结果是不稳定的。
我们需要单独实现一个计时器线程。&lt;/p&gt;

&lt;p&gt;2) 我们通过行为封装，把两边函数写法对照，这样可以复制&lt;/p&gt;

&lt;p&gt;运行看结果&lt;/p&gt;

&lt;p&gt;&lt;img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60bb8e9548554970ab35219e17dbeae0~tplv-k3u1fbpfcp-zoom-1.image" title="" alt="result_example"&gt;&lt;/p&gt;
&lt;h2 id="具体实现"&gt;具体实现&lt;/h2&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# https://github.com/Mark24Code/rb_simulate_eventloop&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'thread'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EventLoop&lt;/span&gt;

  &lt;span class="nb"&gt;attr_accessor&lt;/span&gt; &lt;span class="ss"&gt;:macro_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:micro_queue&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="vi"&gt;@running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

    &lt;span class="vi"&gt;@macro_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="vi"&gt;@micro_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

    &lt;span class="vi"&gt;@time_thr_task_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

    &lt;span class="vi"&gt;@timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@time_thr_task_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@macro_queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 计时线程，是一个同步队列&lt;/span&gt;
    &lt;span class="c1"&gt;# 会把定时任务结果塞回宏队列&lt;/span&gt;
    &lt;span class="vi"&gt;@timer_thx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="vi"&gt;@timer.run&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;before_loop_sync_tasks&lt;/span&gt;
    &lt;span class="c1"&gt;# do sth setting&lt;/span&gt;
    &lt;span class="vi"&gt;@first_task.call&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 这里放置第一次同步任务&lt;/span&gt;
    &lt;span class="c1"&gt;# &lt;/span&gt;
    &lt;span class="c1"&gt;# 外部书写的代码，模拟读取js&lt;/span&gt;
    &lt;span class="c1"&gt;# 提供内部的api&lt;/span&gt;
    &lt;span class="vi"&gt;@first_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;instance_eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;after_loop&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"[after_loop] eventloop is quit :D"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;macro_queue_works&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@macro_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
      &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@macro_queue.shift&lt;/span&gt;
      &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;micro_queue_works&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="vi"&gt;@micro_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty?&lt;/span&gt;
      &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@micro_queue.shift&lt;/span&gt;
      &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="n"&gt;before_loop_sync_tasks&lt;/span&gt;

      &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="vi"&gt;@running&lt;/span&gt;

        &lt;span class="n"&gt;macro_queue_works&lt;/span&gt;

        &lt;span class="n"&gt;micro_queue_works&lt;/span&gt;

        &lt;span class="c1"&gt;# avoid CPU 100%&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;ensure&lt;/span&gt;
      &lt;span class="n"&gt;after_loop&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# dsl public api&lt;/span&gt;
  &lt;span class="c1"&gt;# inner api&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;macro_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@macro_queue.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;micro_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@micro_queue.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;settimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 模拟定时器线程&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# 方案1: 用独立分散的线程模拟存在问题&lt;/span&gt;
    &lt;span class="c1"&gt;# 抢占的返回顺序不是固定的&lt;/span&gt;
    &lt;span class="c1"&gt;# t = Thread.new do&lt;/span&gt;
    &lt;span class="c1"&gt;#   sleep time&lt;/span&gt;
    &lt;span class="c1"&gt;#   @micro_queue.push(block)&lt;/span&gt;
    &lt;span class="c1"&gt;# end&lt;/span&gt;
    &lt;span class="c1"&gt;## !!! 这里一定不能阻塞，一旦阻塞就不是单线程模型&lt;/span&gt;
    &lt;span class="c1"&gt;## 有外循环控制不会结束&lt;/span&gt;
    &lt;span class="c1"&gt;# t.join&lt;/span&gt;

    &lt;span class="c1"&gt;# 方案2: 时间线程也需要单独模拟&lt;/span&gt;
    &lt;span class="c1"&gt;# 建立一个时间任务&lt;/span&gt;
    &lt;span class="vi"&gt;@time_thr_task_queue.push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="ss"&gt;sleep_time: &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;job: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@micro_queue.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Timer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;macro_queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@task_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task_queue&lt;/span&gt;
    &lt;span class="vi"&gt;@macro_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;macro_queue&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vi"&gt;@task_queue.shift&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;sleep_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sleep_time&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sleep_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt;
        &lt;span class="vi"&gt;@macro_queue.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:job&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="vi"&gt;@task_queue.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;选择单线程的原因是因为&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;结果运行的更快&lt;/li&gt;
&lt;li&gt;无上下文负担&lt;/li&gt;
&lt;li&gt;任务队列清晰而又简单&lt;/li&gt;
&lt;li&gt;非 IO 密级任务，可以跑满 CPU&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nginx、Redis 内部也实现了单线程模型，来应对大量的请求，提高并发。&lt;/p&gt;

&lt;p&gt;现在我们大概知道了，浏览器、应用、app、图形界面、游戏……&lt;/p&gt;

&lt;p&gt;他们的背后大概是什么样子。破除神秘感 +1 :D&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;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=rubychina" rel="nofollow" target="_blank" title=""&gt;本文博客地址&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>Mark24</author>
      <pubDate>Thu, 11 Aug 2022 19:01:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/42590</link>
      <guid>https://ruby-china.org/topics/42590</guid>
    </item>
    <item>
      <title>管窥蠡测从思考游戏到实现 2048</title>
      <description>&lt;p&gt;大家好，我是 Mark24，可以叫我 Mark&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Mark24Code" rel="nofollow" target="_blank" title=""&gt;Github Mark24Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;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" rel="nofollow" target="_blank" title=""&gt;我的博客&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.v2ex.com/t/868773" rel="nofollow" target="_blank" title=""&gt;V2EX 同话题讨论&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://juejin.cn/post/7124553155773202439" rel="nofollow" target="_blank" title=""&gt;掘金同话题讨论&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;本文比较啰嗦，更倾向于是自言自语。不过我写完回顾，这更像是这段时间，自由思考的总结 :P&lt;/p&gt;

&lt;p&gt;不过我不是游戏领域的人，这部分都是业余摸鱼思考的记录，如果有勘误，请与我联系，非常乐意交流。&lt;/p&gt;

&lt;p&gt;文章可能需要 30 分钟。&lt;/p&gt;

&lt;p&gt;主要涉及的主题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;游戏之难&lt;/li&gt;
&lt;li&gt;游戏基本构成&lt;/li&gt;
&lt;li&gt;游戏引擎&lt;/li&gt;
&lt;li&gt;游戏与交互程序&lt;/li&gt;
&lt;li&gt;框架和库思考&lt;/li&gt;
&lt;li&gt;语言是否是游戏的瓶颈&lt;/li&gt;
&lt;li&gt;双缓冲模式&lt;/li&gt;
&lt;li&gt;线程和协程的讨论&lt;/li&gt;
&lt;li&gt;线程队列&amp;amp;中断&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用 Ruby 实现 demo。&lt;/p&gt;
&lt;h2 id="rb2048"&gt;rb2048&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;项目地址：&lt;a href="https://github.com/Mark24Code/rb2048" rel="nofollow" target="_blank" title=""&gt;rb2048&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;gem 地址：&lt;a href="https://gems.ruby-china.com/gems/rb2048" rel="nofollow" target="_blank" title=""&gt;rb2048&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;项目安装：&lt;code&gt;gem install rb2048&lt;/code&gt;&lt;/p&gt;
&lt;h4 id="进入游戏"&gt;进入游戏&lt;/h4&gt;
&lt;p&gt;帮助信息： &lt;code&gt;rb2048 --help&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rb2048&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;                    &lt;span class="n"&gt;verison&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="no"&gt;SIZE&lt;/span&gt;                  &lt;span class="no"&gt;Size&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="ss"&gt;board: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
        &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="no"&gt;LEVEL&lt;/span&gt;                &lt;span class="no"&gt;Hard&lt;/span&gt; &lt;span class="no"&gt;Level&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开始游戏 &lt;code&gt;rb2048&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          -- 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)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;升级难度 &lt;code&gt;rb2048 --size=10 --level=5&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                           -- 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)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;我觉得命令行的程序比较赛博朋克，一直想做个命令行的交互程序。
目前在游戏公司，虽然我不是游戏工程师，但是接触了一些游戏行业的优秀小伙伴，我也忍不住思考关于游戏的主题。&lt;/p&gt;

&lt;p&gt;我想做的命令行交互式程序，其实和游戏的思想内核是一致的。一拍即合。&lt;/p&gt;

&lt;p&gt;我以前做过一点点研究。记录了一些笔记。关于 Ruby 中如何实现交互式命令行程序。
本文也是建立在这个基础之上。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;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&amp;amp;CLI%E7%9B%B8%E5%85%B3%E6%94%B6%E9%9B%86.html" rel="nofollow" target="_blank" title=""&gt;命令行界面 TUI&amp;amp;CLI 相关收集&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;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" rel="nofollow" target="_blank" title=""&gt;命令行与游戏引擎初探&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用最简单的方式实现了一个 [贪吃蛇]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/Mark24Code/snakes" rel="nofollow" target="_blank" title=""&gt;https://github.com/Mark24Code/snakes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gem: &lt;a href="https://gems.ruby-china.com/gems/snakes" rel="nofollow" target="_blank" title=""&gt;https://gems.ruby-china.com/gems/snakes&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="rb2048心路历程"&gt;rb2048 心路历程&lt;/h2&gt;&lt;h2 id="rb2048 亮点"&gt;rb2048 亮点&lt;/h2&gt;
&lt;p&gt;rb2048 有趣的地方在于，在设计的时候，没有简单实现了之。毕竟有太多 2048 了，不差这一个。&lt;/p&gt;

&lt;p&gt;对于我不是完成一个任务。由于最近两天关注于线程的使用，于是我把线程方面的使用加入到 rb2048。这算是一个实验性的例子。验证我的想法：&lt;/p&gt;

&lt;p&gt;rb2048 将：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用户 I/O&lt;/li&gt;
&lt;li&gt;游戏数据计算&lt;/li&gt;
&lt;li&gt;游戏渲染&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这三部分分别用单独的线程实现，用队列通信。麻雀虽小，五脏俱全。虽然粗糙，但是代表了游戏引擎典型的设计思路。
（虽然我了解的不多）&lt;/p&gt;
&lt;h2 id="认知变化"&gt;认知变化&lt;/h2&gt;
&lt;p&gt;简单说说我最近的思考吧：&lt;/p&gt;

&lt;p&gt;1）对于计算机不同领域认识发生了变化&lt;/p&gt;

&lt;p&gt;以前会觉得：游戏是游戏，web 是 web，语言是语言，元编程就是元编程……也许还有很多概念，但是渐渐现在觉得无非是一件事 —— 编程罢了。&lt;/p&gt;

&lt;p&gt;随着看到思考的东西逐渐变多，很多计算机领域的问题，在我的角度觉得都一样。&lt;/p&gt;

&lt;p&gt;2）第一性原理 + 交流，向内习得&lt;/p&gt;

&lt;p&gt;这次摸着石头过河，比较新奇的体验就是，从当初一个想法到原理的讨论到最后实现。主要是思考推理，还有和优秀的同事的聊天中习得（这里感谢 @谷神)。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;刻意学习 VS 内在习得&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现实中有很多游戏引擎。他们也许内有乾坤，不过其实是否研究他们也不重要。&lt;/p&gt;

&lt;p&gt;我也不在乎别人的实现，或者更好地实现，是否有实现过了可以参考。其实没什么可参考的。只要我们自己想明白了，别忘了我们上面说的，他们都是一件事 —— 编程罢了。
当我们面临新问题，我们也会加强我们的“引擎”。从思想上，他们是平等的。:P&lt;/p&gt;

&lt;p&gt;可能与以前向外求知，现在会额外的向内思考。比较神奇的体验是，一些东西听个大概，也能盲猜个七八分。&lt;/p&gt;
&lt;h2 id="从游戏开始聊吧"&gt;从游戏开始聊吧&lt;/h2&gt;&lt;h2 id="游戏之难"&gt;游戏之难&lt;/h2&gt;
&lt;p&gt;其实 2048 没啥好聊，写 2048 的背后是对游戏的一些思考。&lt;/p&gt;

&lt;p&gt;其实游戏是一个比较特别的存在。他是一种比较特殊的程序，特殊在哪儿呢？&lt;/p&gt;

&lt;p&gt;1）他是持续交互程序&lt;/p&gt;

&lt;p&gt;不同于简单的脚本，跑完结束。或者传递一个初始参数，就像函数一样运行完结束。&lt;/p&gt;

&lt;p&gt;他是一个持续交互的过程，随着时间累计游戏的方方面面都在变化。&lt;/p&gt;

&lt;p&gt;2）多面平衡&lt;/p&gt;

&lt;p&gt;不同于你写一段 function 就结束了。游戏要在运行的生命周期里：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用户交互事件&lt;/li&gt;
&lt;li&gt;游戏数据计算&lt;/li&gt;
&lt;li&gt;渲染视图&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在至少这三个方面互相作用。&lt;/p&gt;

&lt;p&gt;还可能有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;网络&lt;/li&gt;
&lt;li&gt;调度&lt;/li&gt;
&lt;li&gt;硬件 CPU、GPU 加速渲染&lt;/li&gt;
&lt;li&gt;AI&lt;/li&gt;
&lt;li&gt;资源生成&lt;/li&gt;
&lt;li&gt;数据采集&lt;/li&gt;
&lt;li&gt;各种优化技术&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其他周边并不展开&lt;/p&gt;

&lt;p&gt;3）稳定的帧率&lt;/p&gt;

&lt;p&gt;如果是 60HZ 的游戏，必须在 16.6ms 内完成动作进行刷新。&lt;/p&gt;

&lt;p&gt;这也不是普通业务脚本、程序一直跑自己的线性逻辑就算了，根本不关心时间。&lt;/p&gt;

&lt;p&gt;4）密集对象计算&lt;/p&gt;

&lt;p&gt;简单的游戏还好，传统的模式是面向对象建模，一切看起来还算自然。&lt;/p&gt;

&lt;p&gt;但是也出现了万人同台的游戏，这里传统的编程模式已经满足不了游戏对象的遍历了，很快会达到性能瓶颈。&lt;/p&gt;

&lt;p&gt;这几年，出现了 ECS 架构（Entity-Component-System）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://blog.codingnow.com/2017/06/overwatch_ecs.html" rel="nofollow" target="_blank" title=""&gt;浅谈《守望先锋》中的 ECS 构架&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;小结：&lt;/p&gt;

&lt;p&gt;其实还有各种发散。如何使用 CPU、GPU 加速渲染，这就不再提了。&lt;/p&gt;

&lt;p&gt;游戏是一个非常特殊的存在，它意味着密集型计算、密集型 IO 混合出现的场景。我理解是比 Web 复杂在另一个维度上。&lt;/p&gt;

&lt;p&gt;游戏涉及到 编程架构、网络、图形学、美术设计、资源加载…… 诸多丰富的话题。&lt;/p&gt;

&lt;p&gt;这些就不是我这个门外汉靠管窥蠡测能够说得清的。我今天可以只谈谈我对游戏的理解和认识，以及构建 2048 的思考。&lt;/p&gt;
&lt;h2 id="游戏基本构成"&gt;游戏基本构成&lt;/h2&gt;
&lt;p&gt;其实一个基本游戏可以用如下代码描述：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;IOEvent&lt;/span&gt;
  &lt;span class="no"&gt;UpdateGameData&lt;/span&gt;
  &lt;span class="no"&gt;Render&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;游戏处在一个主循环中，我们依次要处理用户输入事件，根据用户输入事件进行游戏模型的变化，最后再把数据渲染在屏幕上。&lt;/p&gt;

&lt;p&gt;这是一个单线程，主循环的例子。&lt;/p&gt;

&lt;p&gt;现实中每个部分都可以额外变得复杂。也可以用线程单独实现。一切看需求。&lt;/p&gt;
&lt;h3 id="游戏与交互应用程序"&gt;游戏与交互应用程序&lt;/h3&gt;
&lt;p&gt;你会发现游戏就是交互程序。&lt;/p&gt;

&lt;p&gt;上面的三部分，你也可以和 MVC 强行扯在一起。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;M 就是 Model 游戏数据&lt;/li&gt;
&lt;li&gt;V 就是 View  负责渲染视图&lt;/li&gt;
&lt;li&gt;C 就是 Controler 可以对应事件控制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MVC 的典型程序，除了桌面软件，Web 也算是，App 也算。&lt;/p&gt;

&lt;p&gt;看似是在说游戏，实际上他们是一回事。&lt;/p&gt;
&lt;h2 id="游戏引擎的秘密"&gt;游戏引擎的秘密&lt;/h2&gt;
&lt;p&gt;游戏引擎其实就是框架，很佩服他们会起名字。&lt;/p&gt;

&lt;p&gt;框架、引擎其实是一个东西，他们的特征就是一个半成品的软件。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;IOEvent&lt;/span&gt;
  &lt;span class="no"&gt;UpdateGameData&lt;/span&gt;
  &lt;span class="no"&gt;Render&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如这个游戏循环，如果我们封装了主循环，封装了事件对象。对外暴露了一些生命周期。
这种半成品软件就是 所谓的框架，在游戏领域就是引擎。&lt;/p&gt;

&lt;p&gt;作为下游，游戏引擎/框架的使用者来说，我们写的程序就像填空一样和主循环工作在一起。&lt;/p&gt;
&lt;h3 id="主循环决定了什么是框架、什么是库"&gt;主循环决定了什么是框架、什么是库&lt;/h3&gt;
&lt;p&gt;所以我个人觉得，决定了什么是 框架 Framework 和 库 Library 的本质区别是 —— 主循环。&lt;/p&gt;

&lt;p&gt;当你的程序是一种可被调用的状态，那么基本上你的程序可以看成一个 lib
当你的程序如果拥有了主循环的状态，基本宣告了不可被直接调用。那么它其实是一个 Framework 了。除了各种 Pattern 很少见到主循环的 lib 展示，不存在的原因是因为拥有主循环的程序，一般以具体的软件形态出来：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;某种语言，比如 自带调度的 golang、自带 EventLoop 的 JavaScript 引擎 V8&lt;/li&gt;
&lt;li&gt;某种框架，比如 Web 框架自带监听循环&lt;/li&gt;
&lt;li&gt;某种引擎，比如 游戏引擎&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Framework 式的程序，你的工作任务就会转向熟悉这个程序暴露的对象，期待你的程序和主循环能一起工作。&lt;/p&gt;
&lt;h2 id="编程语言会是游戏的瓶颈么？"&gt;编程语言会是游戏的瓶颈么？&lt;/h2&gt;
&lt;p&gt;我们再来聊聊游戏引擎和编程语言。&lt;/p&gt;

&lt;p&gt;Unity 的背后是 C# 支撑；虚幻引擎的背后是 C++。他们采用了更底层的语言。那么问题来了，编程语言会成为制约游戏的瓶颈么？&lt;/p&gt;

&lt;p&gt;这也是我自己思考的一个问题。&lt;/p&gt;

&lt;p&gt;我们可能会很粗暴地觉得 动态语言普遍慢，当然是越接近底层越好。其实我更想知道，如此这样选择的标准在哪儿？&lt;/p&gt;

&lt;p&gt;其实我们可以思考下，这个结论不难获得。&lt;/p&gt;
&lt;h3 id="动态语言真的慢么？"&gt;动态语言真的慢么？&lt;/h3&gt;
&lt;p&gt;其实动态语言在执行一个命令的时候，Ruby 这种最后 C 实现；Golang 最后也落在 C（Golang 实现自举之后，那就用汇编思考吧）。其实他们在执行一个具体操作的时候，数量级一致的。&lt;/p&gt;

&lt;p&gt;他们其实差不多。&lt;/p&gt;

&lt;p&gt;速度差距在哪儿呢？&lt;/p&gt;

&lt;p&gt;1）载入环境&lt;/p&gt;

&lt;p&gt;C、Golang 这种可以打包成二进制的语言。他编译阶段会把需要执行的代码编译成二进制。&lt;/p&gt;

&lt;p&gt;所以执行的时候载入的是所需要用到的部分功能。&lt;/p&gt;

&lt;p&gt;Python、Ruby 这种其实 二进制是语言的解释器。运行的时候更多的时间花费在加载解释器。&lt;/p&gt;

&lt;p&gt;不过，当你的程序复杂到涉及大量 IO、基础库的时候，Golang 的打包结果会趋向于接近一个解释器的大小，比如 Ruby 差不多在 30M 左右。&lt;/p&gt;

&lt;p&gt;我曾经比较过：&lt;/p&gt;

&lt;p&gt;Golang 的一个项目命令行编辑器 &lt;a href="https://github.com/zyedidia/micro" rel="nofollow" target="_blank" title=""&gt;micro&lt;/a&gt; 、Ruby 的一个项目命令行编辑器 &lt;a href="https://www.ruby-toolbox.com/projects/diakonos" rel="nofollow" target="_blank" title=""&gt;diakonos&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;micro 运行内存 16M，也就是他本地大小；diakonos 运行内存 30M，也就是 Ruby 解释器差不多的大小。ruby 代码会执行才加载，所以可以忽略不计。&lt;/p&gt;

&lt;p&gt;最大的差距，在于 30-16 的载入速度差，这个量级是不同的。&lt;/p&gt;

&lt;p&gt;2）语言构件&lt;/p&gt;

&lt;p&gt;C 语言就像是一个高级一点的汇编。C 的角度一切都需要手动管理。那么其实对于底层语言，更现实一点的是会自己手动实现数据结构。&lt;/p&gt;

&lt;p&gt;Ruby 这种动态语言，内部默认会有一个数据结构。&lt;/p&gt;

&lt;p&gt;举个例子：&lt;/p&gt;

&lt;p&gt;比如 &lt;code&gt;a = "GAME"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;C 语言实际上只会手动创建  "GAME" 四个字符&lt;/p&gt;

&lt;p&gt;Python 底层可能创建一个 20 字符长度的数组。存 GAME。也有好处，可以不定长支持动态扩容。&lt;/p&gt;

&lt;p&gt;在生成语言构建的时候存在速度差。
动态语言等于多创建了很多语言在内存里的解构。&lt;/p&gt;

&lt;p&gt;3）解析时间&lt;/p&gt;

&lt;p&gt;二进制的文件，直接载入内存执行。&lt;/p&gt;

&lt;p&gt;动态语言有一个解析的过程。当然，也有优化空间，我们可以提前编译动态语言为虚拟机字节码。这样就获得了 对于解释器是二进制类似的东西。&lt;/p&gt;

&lt;p&gt;4）GC 时间&lt;/p&gt;

&lt;p&gt;和 C 语言相比，Python、Ruby 自带 GC。&lt;/p&gt;

&lt;p&gt;他们存在一个 必须 GC 暂停的那么一个问题。C 语言的策略是手动回收。&lt;/p&gt;
&lt;h3 id="双缓冲模式"&gt;双缓冲模式&lt;/h3&gt;
&lt;p&gt;我们好像列举了一大堆 动态语言的缺点似的。实际上自动管理的数据结构、自带 GC、可以动态的编译执行…… 这些都是动态语言的缺点。&lt;/p&gt;

&lt;p&gt;虽然付出了些许时间的代价。只要我们不滥用语言构件 和 特别烂的算法，真是巧妙的接近底层高效的实现。&lt;/p&gt;

&lt;p&gt;其实我想说，动态语言至少在目标上不是特别大的瓶颈。&lt;/p&gt;

&lt;p&gt;Java 也有游戏的例子；C# 也是自带 GC。GC 不会是瓶颈。&lt;/p&gt;

&lt;p&gt;语言的速度不会绝对意义上成为一个游戏组成的阻碍。&lt;/p&gt;

&lt;p&gt;EVE 这样的大型游戏，内部使用了 巨慢的 Python 就可以说明问题。&lt;/p&gt;

&lt;p&gt;之所以语言不一定构成拖慢游戏的原因，还有一个就是游戏和屏幕的刷新机制 —— 双缓冲模式。&lt;/p&gt;

&lt;p&gt;其实可以理解为一个 内存空间，我们称之为 Buffer。我们有两个 Buffer，分别叫 A Buffer、B Buffer。&lt;/p&gt;

&lt;p&gt;显示器先从 A Buffer 中读取数据渲染屏幕。我们程序写入 B Buffer，等我们真的写完了，可慢或者快，但是无所谓，反正屏幕这时候在稳定的读取 A Buffer 内容。我们计算完毕，B Buffer 中写入了我们想要的东西，这时候只要把显示器读取的指针指向 B Buffer，下次屏幕就会获得我们想要的画面。这就是双缓冲模式。由于存在双缓冲解构，算快和快慢，至少不会成为画面撕裂的原因。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;rb2048 使用了 Curses 库来绘制界面，而 Curses 内部使用了双缓冲模式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="线程和协程的讨论"&gt;线程和协程的讨论&lt;/h2&gt;
&lt;p&gt;我们自己研究了两天线程和队列。主要是 Ruby 的实现。&lt;/p&gt;

&lt;p&gt;这里不教线程和协程，只记录我觉得好玩的交流结果。&lt;/p&gt;
&lt;h2 id="Ruby线程的问题"&gt;Ruby 线程的问题&lt;/h2&gt;
&lt;p&gt;缺点：&lt;/p&gt;

&lt;p&gt;Ruby 存在线程锁，这导致每一时刻只能运行一个线程。线程就像背后虽然有很多工人，但是只能交替的一人一锤子。&lt;/p&gt;

&lt;p&gt;这背后的原因在于 Ruby 考虑安全更多一点 —— 线程安全。&lt;/p&gt;

&lt;p&gt;这样的多线程无法利用 CPU 多核心并行的特点。希望利用多核的，可以去用 JRuby，因为 Java 底层没有加锁。&lt;/p&gt;

&lt;p&gt;Ruby3 中也有了无锁线程的替代品  Ractor 也可以了解下。&lt;/p&gt;

&lt;p&gt;CRuby 如果想利用多核心可以使用进程替代线程。如果设计得当，其实差不多。Ruby 里面 Webserver 有名气的 Puma 采用的就是多进程实现。&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;p&gt;加上锁最大好处是线程安全，你可以自由的编码，Ruby 帮你加锁。这样多线程访问变量的时候，不会出错。&lt;/p&gt;

&lt;p&gt;但是你退出来想，反正你自己也要加锁啊，谁加不是加。Ruby 默认的线程其实书写起来非常友好。&lt;/p&gt;
&lt;h2 id="进程、线程、协程 傻傻分不清楚"&gt;进程、线程、协程 傻傻分不清楚&lt;/h2&gt;
&lt;p&gt;我觉得再这样介绍这三个概念，这文章太冗长了。&lt;/p&gt;

&lt;p&gt;直接说结论吧，直观上，这三者存在量级差，不仅体现在空间资源，时间资源都差不多。&lt;/p&gt;

&lt;p&gt;进程 &amp;gt;&amp;gt; 线程 &amp;gt;&amp;gt; 协程&lt;/p&gt;

&lt;p&gt;比如一台机器 4G 内存：&lt;/p&gt;

&lt;p&gt;可能只能实际生成几百个进程就不太行了。
同样，可以生成几千个线程，就动不了了。
协程可以生成几十万个。&lt;/p&gt;

&lt;p&gt;他们大概就是这个差距 (有更好数据支持的，请联系我)。&lt;/p&gt;

&lt;p&gt;他们切换上下文的时间也遵循这个比较关系。&lt;/p&gt;

&lt;p&gt;所以我们一般的策略，尽量多用协程&amp;amp;线程，少用进程。&lt;/p&gt;

&lt;p&gt;如果任务独立运行还好，就怕彼此还要通信，出现互相等待的局面。&lt;/p&gt;

&lt;p&gt;线程具有 CPU 亲和性（一般语言来讲）。&lt;/p&gt;

&lt;p&gt;比如 Golang 的 M:N 模型，主张 先生成 M 个线程，M 是机器 CPU 核心数，然后再在 M 个线程之间调度实际产生的 N 个任务。&lt;/p&gt;

&lt;p&gt;比如 Nginx 的配置也主张 配置线程核心数和 CPU 核心数一致。&lt;/p&gt;
&lt;h3 id="什么时候用线程、什么时候用协程？"&gt;什么时候用线程、什么时候用协程？&lt;/h3&gt;
&lt;p&gt;线程、协程产生的原因是什么？&lt;/p&gt;

&lt;p&gt;其实还是为了调度。&lt;/p&gt;

&lt;p&gt;线程是细分进程下共享内存的场景；协程是为了细化调度。&lt;/p&gt;

&lt;p&gt;因为进程、线程本质上是操作系统在调度。操作系统并不清楚什么时候应该调度。只能采用各种优先计算法、平均算法。再怎么算，也是盲人摸象罢了。&lt;/p&gt;

&lt;p&gt;协程给了程序员一个口子，你可以用 协程在 涉及阻塞部分进行让出控制权。&lt;/p&gt;

&lt;p&gt;简而言之，经验之谈：&lt;/p&gt;

&lt;p&gt;涉及到 计算密集型 请用线程。&lt;/p&gt;

&lt;p&gt;如果涉及到 IO 阻塞密集，请用协程。&lt;/p&gt;

&lt;p&gt;我们的目的不是为了用而用，而是使用调度，提高我们代码执行的效率，减少等待。&lt;/p&gt;
&lt;h3 id="硬件中断"&gt;硬件中断&lt;/h3&gt;
&lt;p&gt;如果说其实没有 if-else\switch\while，计算机器其实只有  goto。&lt;/p&gt;

&lt;p&gt;如果你看过汇编，大概理解我是什么意思。&lt;/p&gt;

&lt;p&gt;同样，计算机里进程、线程、协程背后调度的秘密，都来自于 CPU 的硬件中断功能。&lt;/p&gt;

&lt;p&gt;只不过是上下文快速切换，切换上下文多和少罢了。&lt;/p&gt;
&lt;h2 id="2048 的实现"&gt;2048 的实现&lt;/h2&gt;
&lt;p&gt;其实 2048 的关键就是相邻元素合并，实现这么一个算法，反复执行到无元素可以继续合并。再把这个应用到 x\y 方向所有行列就好了。&lt;/p&gt;
&lt;h2 id="具体线程"&gt;具体线程&lt;/h2&gt;
&lt;p&gt;目前实现成通过队列来实现通信：&lt;/p&gt;

&lt;p&gt;IO 线程，用户产生一个输入，进入事件队列。
游戏读取事件队列，开始计算游戏数据，把结果塞入渲染队列。
渲染线程，读取渲染队列数据进行渲染。&lt;/p&gt;
&lt;h2 id="后续讨论"&gt;后续讨论&lt;/h2&gt;
&lt;p&gt;我和同事交流了一下，就 2048 而言其实可以很多方式做：&lt;/p&gt;

&lt;p&gt;1) 如果是队列依赖式&lt;/p&gt;

&lt;p&gt;我们等于做出一个 pipline 的方式了&lt;/p&gt;

&lt;p&gt;2) 我们也可以解开队列阻塞&lt;/p&gt;

&lt;p&gt;真正的自由渲染。虽然 2048 看不出效果&lt;/p&gt;
&lt;h2 id="队列追赶问题"&gt;队列追赶问题&lt;/h2&gt;
&lt;p&gt;用户不断地敲击，产生时间，如果队列里一致产生数据，那不是渲染永远追不上？&lt;/p&gt;

&lt;p&gt;多线程队列需要思考 生产者、消费者模型，需要设计匹配的方式。&lt;/p&gt;

&lt;p&gt;解决方法&lt;/p&gt;

&lt;p&gt;1）控制生产频率，生产和消耗相抵消&lt;/p&gt;

&lt;p&gt;事件采样、渲染 可以保持一个频率&lt;/p&gt;

&lt;p&gt;2）不控制生产，但是跳过生产&lt;/p&gt;

&lt;p&gt;事件采样，可以携带时间戳。&lt;/p&gt;

&lt;p&gt;如果渲染的时候，每次时间超时，跳过关键帧。&lt;/p&gt;

&lt;p&gt;当然这些都是很细化的问题了。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;我倾向于研究一个东西，思考他的全部，寻找最佳的路径。
这些都是摸鱼结果，简单分享下。更深的感受还需要实践和交流。&lt;/p&gt;
&lt;h2 id="后续"&gt;后续&lt;/h2&gt;
&lt;p&gt;上文提到游戏里面最新流行 ECS 架构。ECS 抛弃了面向对象的思想，把同类数据摆放在一起，亲和 CPU 运行机制，方便大规模属性遍历。&lt;/p&gt;

&lt;p&gt;ECS 应该如何用 Ruby 实现呢？ &lt;/p&gt;

&lt;p&gt;&lt;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" rel="nofollow" target="_blank" title=""&gt;我的博客&lt;/a&gt;&lt;/p&gt;</description>
      <author>Mark24</author>
      <pubDate>Tue, 26 Jul 2022 13:41:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/42553</link>
      <guid>https://ruby-china.org/topics/42553</guid>
    </item>
  </channel>
</rss>
