<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>wadexing (邢星)</title>
    <link>https://ruby-china.org/wadexing</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>[北京|大望路] 住艺 (外资) 招聘 Web 前端开发工程师 (技术栈: React |&gt; Redux |&gt; GraphQL |&gt; Elixir |&gt; Phoenix |&gt; Postgres )</title>
      <description>&lt;h2 id="想要加入我们？"&gt;想要加入我们？&lt;/h2&gt;
&lt;p&gt;请发送简历至 
&lt;a href="mailto:%20wadexing@gmail.com" title=""&gt;wadexing@gmail.com&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="我们是谁？"&gt;我们是谁？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;假如你的工作变成人，你会不会爱上 TA？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="http://www.zhuyihome.com/" rel="nofollow" target="_blank" title=""&gt;住艺&lt;/a&gt;，是康泰纳仕集团旗下精选设计师平台，是与国际权威家居生活杂志《安邸 AD》关联的互联网新品牌。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ZhuyiHome" rel="nofollow" target="_blank" title=""&gt;住艺 Devs&lt;/a&gt; &lt;img src="https://avatars3.githubusercontent.com/u/24198367?v=3&amp;amp;s=200" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/elixir-cn/elixir-china-companies" rel="nofollow" target="_blank" title=""&gt;Elixir China Company&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://36kr.com/p/5054941.html" rel="nofollow" target="_blank" title=""&gt;36kr 对住艺的报道&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="我们能给？"&gt;我们能给？&lt;/h2&gt;
&lt;ol&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;Last but not least, 如果你爱设计，爱家居和生活，大概没有比这份工作更好的选择了~ &lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="你将和谁一起工作？"&gt;你将和谁一起工作？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;住艺团队&lt;/strong&gt;由来自清华大学、北京大学、耶鲁大学、哥伦比亚大学、INSEAD 商学院等著名学府的商业头脑，来自康泰纳仕集团，特别是《安邸 AD》的媒体精英，以及来自麦肯锡、亚马逊及领先互联网公司的一流产品技术骨干构成。团队成员推崇设计，尊崇设计师的价值，亦是家居美学和精彩空间的追求者。&lt;/p&gt;
&lt;h2 id="住艺在找"&gt;住艺在找&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我们很在意开发时工程师是否愉悦，因为我们坚信乐趣会带来更多创造性生产力。所以我们不在代码质量上妥协，这是对同袍的❤️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="前端开发工程师"&gt;前端开发工程师&lt;/h2&gt;&lt;h3 id="职责"&gt;职责&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;负责公司 web 项目前端的开发与维护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="薪资"&gt;薪资&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;18K ~ 30K * (13 + 3)&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="需要"&gt;需要&lt;/h3&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;精通 CSS3 和  HTML5&lt;/li&gt;
&lt;li&gt;掌握 React + Redux + GraphQL 技术栈 &lt;/li&gt;
&lt;li&gt;熟悉 SVG&lt;/li&gt;
&lt;li&gt;掌握 Git&lt;/li&gt;
&lt;li&gt;熟悉 Mac OS&lt;/li&gt;
&lt;li&gt;熟悉 Webpack &lt;/li&gt;
&lt;li&gt;有前后端分离经验&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="加分项"&gt;加分项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有前端单元测试经验&lt;/li&gt;
&lt;li&gt;熟悉 ES6/Typescript/Sass/Less/Bootstrap/Foundation&lt;/li&gt;
&lt;li&gt;参与过开源项目的开发，请附上 github 地址，我们很希望通过代码来了解你&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="简历请发邮件"&gt;简历请发邮件&lt;/h2&gt;
&lt;p&gt;&lt;a href="mailto:%20wadexing@gmail.com" title=""&gt;wadexing@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="http://mmbiz.qpic.cn/mmbiz/MKyoF2qNFAsaJFL6tlfs3eCy6S0czR61ZOmjkbeFBKUTdibzhYBciaZgLvpiazib8I9iayucR8tIOibt6WMwuIA46VVA/640?wx_fmt=jpeg&amp;amp;tp=webp&amp;amp;wxfrom=5&amp;amp;wx_lazy=1" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>wadexing</author>
      <pubDate>Tue, 14 Nov 2017 09:25:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/34570</link>
      <guid>https://ruby-china.org/topics/34570</guid>
    </item>
    <item>
      <title>[北京|大望路] 住艺 (外资) 招聘 Web 前端开发工程师 (技术栈: React |&gt; Redux |&gt; GraphQL |&gt; Elixir |&gt; Phoenix |&gt; Postgres )</title>
      <description>&lt;h2 id="想要加入我们？"&gt;想要加入我们？&lt;/h2&gt;
&lt;p&gt;请发送简历至 
&lt;a href="mailto:%20xing.xing@condenast.com.cn" title=""&gt;xing.xing@condenast.com.cn&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="我们是谁？"&gt;我们是谁？&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;假如你的工作变成人，你会不会爱上 TA？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="http://www.zhuyihome.com/" rel="nofollow" target="_blank" title=""&gt;住艺&lt;/a&gt;，是康泰纳仕集团旗下精选设计师平台，是与国际权威家居生活杂志《安邸 AD》关联的互联网新品牌。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ZhuyiHome" rel="nofollow" target="_blank" title=""&gt;住艺 Devs&lt;/a&gt; &lt;img src="https://avatars3.githubusercontent.com/u/24198367?v=3&amp;amp;s=200" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/elixir-cn/elixir-china-companies" rel="nofollow" target="_blank" title=""&gt;Elixir China Company&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://36kr.com/p/5054941.html" rel="nofollow" target="_blank" title=""&gt;36kr 对住艺的报道&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="我们能给？"&gt;我们能给？&lt;/h2&gt;
&lt;ol&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;Last but not least, 如果你爱设计，爱家居和生活，大概没有比这份工作更好的选择了~ &lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="你将和谁一起工作？"&gt;你将和谁一起工作？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;住艺团队&lt;/strong&gt;由来自清华大学、北京大学、耶鲁大学、哥伦比亚大学、INSEAD 商学院等著名学府的商业头脑，来自康泰纳仕集团，特别是《安邸 AD》的媒体精英，以及来自麦肯锡、亚马逊及领先互联网公司的一流产品技术骨干构成。团队成员推崇设计，尊崇设计师的价值，亦是家居美学和精彩空间的追求者。&lt;/p&gt;
&lt;h2 id="住艺在找"&gt;住艺在找&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;我们很在意开发时工程师是否愉悦，因为我们坚信乐趣会带来更多创造性生产力。所以我们不在代码质量上妥协，这是对同袍的❤️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="Elixir 开发工程师"&gt;Elixir 开发工程师&lt;/h2&gt;&lt;h3 id="职责"&gt;职责&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;负责公司 web 项目后端的开发与维护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="薪资"&gt;薪资&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;18K ~ 30K * (13 + 3)&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="需要"&gt;需要&lt;/h3&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;精通 Elixir / Ruby / Haskell / Clojure 其中之一&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;掌握 Git&lt;/li&gt;
&lt;li&gt;掌握关系型数据库的设计方法&lt;/li&gt;
&lt;li&gt;掌握 Http 协议&lt;/li&gt;
&lt;li&gt;了解持续集成&lt;/li&gt;
&lt;li&gt;了解函数式编程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="加分项"&gt;加分项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;熟悉 PostgresQL&lt;/li&gt;
&lt;li&gt;熟悉 Elasticsearch&lt;/li&gt;
&lt;li&gt;掌握 Vim / Emacs&lt;/li&gt;
&lt;li&gt;了解 GraphQL&lt;/li&gt;
&lt;li&gt;参与过开源项目的发开，请附上 github 地址，我们很希望通过代码来了解你&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="前端开发工程师"&gt;前端开发工程师&lt;/h2&gt;&lt;h3 id="职责"&gt;职责&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;负责公司 web 项目前端的开发与维护&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="薪资"&gt;薪资&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;18K ~ 30K * (13 + 3)&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="需要"&gt;需要&lt;/h3&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;精通 CSS3 和  HTML5&lt;/li&gt;
&lt;li&gt;掌握 React + Redux + GraphQL 技术栈 &lt;/li&gt;
&lt;li&gt;熟悉 SVG&lt;/li&gt;
&lt;li&gt;掌握 Git&lt;/li&gt;
&lt;li&gt;熟悉 Mac OS&lt;/li&gt;
&lt;li&gt;熟悉 Webpack &lt;/li&gt;
&lt;li&gt;有前后端分离经验&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="加分项"&gt;加分项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;有前端单元测试经验&lt;/li&gt;
&lt;li&gt;熟悉 ES6/Typescript/Sass/Less/Bootstrap/Foundation&lt;/li&gt;
&lt;li&gt;参与过开源项目的发开，请附上 github 地址，我们很希望通过代码来了解你&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="简历请发邮件"&gt;简历请发邮件&lt;/h2&gt;
&lt;p&gt;&lt;a href="mailto:%20xing.xing@condenast.com.cn" title=""&gt;xing.xing@condenast.com.cn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="http://mmbiz.qpic.cn/mmbiz/MKyoF2qNFAsaJFL6tlfs3eCy6S0czR61ZOmjkbeFBKUTdibzhYBciaZgLvpiazib8I9iayucR8tIOibt6WMwuIA46VVA/640?wx_fmt=jpeg&amp;amp;tp=webp&amp;amp;wxfrom=5&amp;amp;wx_lazy=1" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>wadexing</author>
      <pubDate>Sat, 24 Dec 2016 22:06:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/31996</link>
      <guid>https://ruby-china.org/topics/31996</guid>
    </item>
    <item>
      <title>[北京][八里庄] 星途无限招聘 Web 开发者 (并提供实习机会)[名额已满]</title>
      <description>&lt;h3 id="公司简介"&gt;公司简介&lt;/h3&gt;
&lt;p&gt;北京星途无限文化传媒有限公司成立于 2016 年元月，致力于打造以互联网产品为依托，致力于为演艺行业用户提供方便的一体化职业发展及资讯平台。
公司依托文化部等政府部门的支持，倡导“互联网+”，开拓一个全新的文化传媒商业模式：基于实名认证的注册体系，确保平台数据真实、严谨，构建值得信任的生态环境，并将完整、动态、清晰的呈现演艺行业人士职业近况。建立业内人员与普通大众新型互动通道，创造娱乐梦想新蓝图，打造中国梦想星工厂。网站汇聚行业内专属招聘信息，为业内人士提供优秀、公平的工作机会，打造健康、透明的职业发展平台。&lt;/p&gt;
&lt;h3 id="地址"&gt;地址&lt;/h3&gt;
&lt;p&gt;北京市朝阳区东四环中路 56 号远洋国际中心 A 座 B1 层 OK SPACE+
正式办公地点在 A 座 18 层，现正在装修。&lt;/p&gt;
&lt;h3 id="待遇"&gt;待遇&lt;/h3&gt;
&lt;p&gt;￥20K ~ 30K * 13 &lt;/p&gt;
&lt;h3 id="福利"&gt;福利&lt;/h3&gt;
&lt;p&gt;年假，五险一金，午餐补助，交通补助，话费补助，全勤奖，年度自然调薪，年终奖，免费下午茶，商业保险，旅游，部门活动补贴，健康体检，节日礼金……&lt;/p&gt;
&lt;h3 id="职位描述"&gt;职位描述&lt;/h3&gt;
&lt;p&gt;开发直播平台后端 web 服务。崇尚效率，而不是延长工时。&lt;/p&gt;
&lt;h4 id="任职需求"&gt;任职需求&lt;/h4&gt;
&lt;p&gt;1、较强的&lt;code&gt;自学能力&lt;/code&gt;;
2、熟练使用 &lt;code&gt;*nix&lt;/code&gt; 开发环境，能够编写 &lt;code&gt;Shell&lt;/code&gt; 脚本 ;
3、熟练使用 &lt;code&gt;Git&lt;/code&gt; ;
4、&lt;code&gt;MySQL&lt;/code&gt; / &lt;code&gt;PostgreSQL&lt;/code&gt; ;
5、可阅读英文技术文档 ;
6、清晰的&lt;code&gt;表达能力&lt;/code&gt; ;
7、理解 &lt;code&gt;RESTful&lt;/code&gt; ;
_、熟练使用以下语言中至少一种：&lt;code&gt;Ruby / Python / Go / Haskell / Node.js / Erlang / Elixir&lt;/code&gt;。(此项要求是针对自学能力的，故而不能算作 &lt;strong&gt;必要项&lt;/strong&gt;  ，我们需要会编程的人，而非只会用某编程语言的人)。
_、一年以上 web 开发经验 ;
_、代码洁癖 ;
_、有开源贡献; 
_、了解 Docker ; 
_、了解 JSON API 规格; 
_、有编写单元测试的习惯 ; 
_、Jenkins 使用经验; 
_、了解微服务架构; 
_、使用 &lt;code&gt;Emacs/Vim&lt;/code&gt;,代码洁癖 ;&lt;/p&gt;
&lt;h3 id="联系方式"&gt;联系方式&lt;/h3&gt;
&lt;p&gt;站内信我，发送简历至 xingxing (AT) xingtuwuxian.com&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/6daa8ff64bf917be7b86d87261ad6f91.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/a1bf5973d91aa98aa3aa029427c9d022.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/414f954015fae46fa908f46346f10c5e.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/a80415938be3c9931f98192c56ca3437.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/7e03ad853cc4f351afda8807dcc743e8.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2016/dd7b9f956ef0ce983732f3fce61a89fe.jpg!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>wadexing</author>
      <pubDate>Thu, 04 Aug 2016 09:51:10 +0800</pubDate>
      <link>https://ruby-china.org/topics/30724</link>
      <guid>https://ruby-china.org/topics/30724</guid>
    </item>
    <item>
      <title>无人知晓的 GIL</title>
      <description>&lt;h3 id="原文:"&gt;原文：&lt;/h3&gt;
&lt;p&gt;&lt;a href="http://www.jstorimer.com/blogs/workingwithcode/8085491-nobody-understands-the-gil" rel="nofollow" target="_blank" title=""&gt;http://www.jstorimer.com/blogs/workingwithcode/8085491-nobody-understands-the-gil&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="Part I"&gt;Part I&lt;/h2&gt;
&lt;p&gt;我的大半生都是在 Ruby 社区中度过的，然而 MRI 中臭名昭著的 GIL 对我而言却一直是个叵测的家伙。这是一个关于线程安全的故事，最终我们会真相大白，&lt;/p&gt;

&lt;p&gt;最初听人提及 GIL 时，我不知道它是如何工作的，它做了什么事，甚至也不知道它为什么会存在。我只知道这是个蠢主意，因为它限制了并行，换句话说就是"曾经辉煌"，因为它让我的代码线程安全。后来，我总算学会了如何去爱多线程，也意识到了现实远比我设想的复杂。&lt;/p&gt;

&lt;p&gt;我要知其然，更要知其所以然，GIL 到底是怎么工作的？但是，GIL 没有规程 (specification) 可循，亦没有文档可看。本质上说它就是一个未知行为；一个 MRI 的实现细节。Ruby 核心组没有对它将如何工作予以承诺或担保。&lt;/p&gt;

&lt;p&gt;也许我有点儿超前了。&lt;/p&gt;

&lt;p&gt;如果你对 GIL 一无所知，花 30 秒钟读读下面这个简介吧：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MRI 里有个东西叫全局解释器锁 (global interpreter lock)。这个锁环绕着 Ruby 代码的执行。即是说在一个多线程的上下文中，在任何时候只有一个线程可以执行 Ruby 代码。&lt;/p&gt;

&lt;p&gt;因此，假如一台 8 核机器上跑着 8 个线程，在特定的时间点上也只有一个线程和一个核心在忙碌。GIL 一直保护着 Ruby 内核，以免竞争条件造成数据混乱。把警告和优化放一边，这就是它的主旨了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="问题"&gt;问题&lt;/h3&gt;
&lt;p&gt;回到 2008，Ilya Grigorik 的 &lt;a href="http://www.igvita.com/2008/11/13/concurrency-is-a-myth-in-ruby/" rel="nofollow" target="_blank" title=""&gt;《Ruby 里的并行神话》&lt;/a&gt;给了我对 GIL 的高层次理解。即使我学确实学到了更多的 Ruby 多线程技术，但是这个高层次认识只是对我的单方灌输。真见鬼，我最近还写了一本关于&lt;a href="http://www.jstorimer.com/products/working-with-ruby-threads" rel="nofollow" target="_blank" title=""&gt;Ruby 里多线的书&lt;/a&gt;呢，但是对于 GIL 我就理解了这么点儿？&lt;/p&gt;

&lt;p&gt;问题是用些"微言大义"的认识，我没法回答有深度的技术问题。特别是，我想知道 GIL 是否提供了关于线程安全的任何保障。让我来示范一下。&lt;/p&gt;
&lt;h3 id="数组附加是非线程安全的"&gt;数组附加是非线程安全的&lt;/h3&gt;
&lt;p&gt;几乎没什么事在 Ruby 里是隐式线程安全的。以附加数组为例：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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="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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&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="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="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有 5 个线程共享一个数组对象。每个线程将 nil 放入数组 1000 次。因此，数组里应该有 5000 个元素，对吧？&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ruby pushing_nil.rb
5000

&lt;span class="nv"&gt;$ &lt;/span&gt;jruby pushing_nil.rb
4446

&lt;span class="nv"&gt;$ &lt;/span&gt;rbx pushing_nil.rb
3088
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:(&lt;/p&gt;

&lt;p&gt;即使这个微不足道的例子，也足以揭示 Ruby 里的一个操作并非隐式线程安全的。或许是？实际上发生什么了呢？&lt;/p&gt;

&lt;p&gt;请注意 MRI 的结果是正确的，5000。但是 JRuby 和 Rubinius 都错了。如果你再跑一遍，你很可能会看到 MRI 依然正确，但是 JRuby 和 Rubinius 给出了不同的错误结果。&lt;/p&gt;

&lt;p&gt;这些不同的结果是 GIL 造成的。因为 MRI 有 GIL，即使同时有 5 个线程在跑，在一个时间点上也只有一个线程是活动的。JRuby 和 Rubinius 没有 GIL，所以当你有 5 个线程在跑，你就真的有 5 个线程通过获取核心在并行地跑。&lt;/p&gt;

&lt;p&gt;在并行的 Ruby 实现中，这 5 个线程逐句通过代码，而这是非线程安全的。它们最终互相干扰，最终腐化底层数据。&lt;/p&gt;
&lt;h3 id="多线程如何腐化数据"&gt;多线程如何腐化数据&lt;/h3&gt;
&lt;p&gt;这怎么可能？我还以为 Ruby 会罩着我们呢，对吧？相对于通过高层次的解释来阐述技术细节，我更倾向于向你展示这在技术上的可能性。&lt;/p&gt;

&lt;p&gt;无论你是用 MRI,JRuby 或是 Rubinius，Ruby 语言是用其他语言实现的。MRI 是用 C 实现的，JRuby 用 Java,Rubinius 是 Ruby 和 C++ 的混合体。于是当你有这样一个 Ruby 操作时：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实际上在底层实现上会扩展为一大堆代码。例如，下面是 Array#&amp;lt;&amp;lt;在 MRI 中的实现：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;VALUE&lt;/span&gt;

&lt;span class="nf"&gt;rb_ary_push&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="n"&gt;VALUE&lt;/span&gt; &lt;span class="n"&gt;iterm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&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;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="n"&gt;ary_ensure_room_for_push&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;RARRAY_ASET&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="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;ARY_SET_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="n"&gt;idx&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;return&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意至少 4 个不同的底层操作。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;获取数组的当前长度&lt;/li&gt;
&lt;li&gt;检查数组里是否有空间容纳其他元素。&lt;/li&gt;
&lt;li&gt;将元素附件到数组&lt;/li&gt;
&lt;li&gt;将数组的长度属性置为原值 +1。&lt;/li&gt;
&lt;/ol&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;img src="https://l.ruby-china.com/photo/2015/53ede5b98679c41afe8d78296f4ab6ca.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;有两个活跃线程，同时进入这个函数 (C 语言中的)。将 1-4 步看做 MRI 中 Array#&amp;lt;&amp;lt;的伪代码实现，之前你见过的。一旦两个线程进入这个函数，就可能出现一系列事件，假设从线程 A 开始：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/766e09d66a4a4bf4ec6cf681acfd3b0e.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这看着更复杂了，但是只要跟着箭头的方向，你就可以穿过这个流程。我还加了在每个步骤上一些标签从每个线程的角度来显示各种状态。&lt;/p&gt;

&lt;p&gt;这只是其中一种可能性。&lt;/p&gt;

&lt;p&gt;于是线程 A 沿着函数的常规路径执行，但当执行到步骤 3 时，发生了上下文切换！线程 A 被暂停在当前位置。之后线程 B 接管了进程并运行整个函数，附加它自己的元素并增加&lt;strong&gt;length&lt;/strong&gt;属性。&lt;/p&gt;

&lt;p&gt;一旦线程 B 完事了，线程 A 就恢复执行。A 会在其中断的位置走起。记住，线程 A 是在增加&lt;strong&gt;length&lt;/strong&gt;属性前被暂停的，自然会从往下增加&lt;strong&gt;length&lt;/strong&gt;属性。只不过，A 并不知道线程 B 已经改变了事物的状态。&lt;/p&gt;

&lt;p&gt;于是线程 B 设置&lt;strong&gt;length&lt;/strong&gt;为 1，之后线程 A 又把&lt;strong&gt;length&lt;/strong&gt;设为 1，尽管它们格子的元素都已经被附加到了 Array 上。数据已经被玩坏了。看到图中的小闪电了吗，就这这个意思。&lt;/p&gt;
&lt;h3 id="但是我想Ruby会罩着我吧？"&gt;但是我想 Ruby 会罩着我吧？&lt;/h3&gt;
&lt;p&gt;如图中例子所示，JRuby 和 Rubinius 中的这一系列的事件会带来错误的结果。&lt;/p&gt;

&lt;p&gt;除此之外，在 JRuby 和 Rubinius 里，事情要更为复杂，因为线程实际可以平行跑。在该图中，一个线程被暂停，另一个在运行，而在真正并行的环境里，多个线程可以同时运行。&lt;/p&gt;

&lt;p&gt;要是你真的运行可前面的那个例子，可能会看到它总是能得到不同的错误结果。这里的上下文切换是不确定的，无法预知。它可能发生在函数运行前期，后期，或者就根本没发生。下一小节关这个我们会谈更多。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;所以，为什么 Ruby 不保护我们远离这些？&lt;/strong&gt; 出于同样的原因，其他一些编程语言内核也不提供线程安全保护：它成本太高。对所有的 Ruby 实现提供线程安全的数据结构不是不可能，但这需要额外的开销，拖了单线程代码的后腿。&lt;/p&gt;

&lt;p&gt;权衡之下，你，开发者就有责任在需要的时候提供线程安全的保证。&lt;/p&gt;

&lt;p&gt;对我而言，这提出了两个悬而未决的问题，并且我们并未潜入 GIL 的技术细节中。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;如果下上文切换是可能的，为什么 MRI 还能给出正确答案呢？&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;上下文切换到底是什么鬼？&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;问题 1 是我写这篇文正的动机。对 GIL 高层次的认识无法回答这个问题。高层次的认识只说清了只有一个时间点上只有一个线程可以被执行。但是 Ruby 之下，上下文切换是不是还能在函数的中间发生呢？&lt;/p&gt;

&lt;p&gt;但是首先.....&lt;/p&gt;
&lt;h3 id="都是调度程序的错！"&gt;都是调度程序的错！&lt;/h3&gt;
&lt;p&gt;上下文切换源于操作系统的线程调度程序。在所有我展示过的 Ruby 语言实现中，一个 Ruby 线程依托于一个原生的操作系统线程。操作系统必须保证没有一个线程可以独霸所有可用资源，如 CPU 时间，于是它实现了调度算法，使得雨露均沾。&lt;/p&gt;

&lt;p&gt;这表现为一系列的暂停会恢复。每个线程都有机会消耗资源，之后它暂停在其轨道上，以便其他线程可以有机可乘。随着时间推移，这个线程经会被不断被恢复。&lt;/p&gt;

&lt;p&gt;这一做法提高了操作系统的效率，但也引入和一定程度的不确定性和程序正确性的难度。例如，Array#&amp;lt;&amp;lt;操作现在需要考虑到它可以随时暂停，另一个线程可以并行地执行相同的操作，改变脚下"世界"的状态。&lt;/p&gt;
&lt;h4 id="则何如？让关键操作具有原子性"&gt;则何如？让关键操作具有原子性&lt;/h4&gt;
&lt;p&gt;如果想确保这样的线程间中断不发生，就应该使操作具有原子性。通过原子性操作，可以保证线程在完成动作前不会被打断，这就防止了我们例子中的，在步骤 3 被打断，并最终在步骤 4 时恢复导致的数据误。&lt;/p&gt;

&lt;p&gt;是操作具有原子性的最简方案是使用锁。下面的代码会确保结果的正确，不论是在 MRI,JRuby 还是 Rubinius 里。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;mutex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mutex&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;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="n"&gt;mutex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt; &lt;span class="k"&gt;do&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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&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;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="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它确保正确是因为使用了一个共享的互斥或者说锁。一旦一个线程进入&lt;strong&gt;mutex.synchronize&lt;/strong&gt;内的代码块时，所有其他线程必须在进入同一代码前等待，直到这个线程执行完毕。如果你回想前面，我说过这个操作下是多行 C 代码，并且线程调度上下文切换可以发生在任意两行代码间。&lt;/p&gt;

&lt;p&gt;通过原子性操作，你可以保证如果一个上下文切换在这个代码块里发生了，其他线程将无法执行相同的代码。线程调度器会观察这一点，并再切换另一个线程。这同样也保证了没有线程可以一同进入代码块并各自改变"世界"的状态。这个例子现在就是线程安全的。&lt;/p&gt;
&lt;h3 id="GIL也是个锁"&gt;GIL 也是个锁&lt;/h3&gt;
&lt;p&gt;我刚才已经展示乐怎样可以使用锁得到原子性并提供好线程安全保证。GIL 也是一个锁，所以它也能保证你代码的线程安全吗？&lt;/p&gt;

&lt;p&gt;GIL 会使 &lt;strong&gt;array &amp;lt;&amp;lt; nil&lt;/strong&gt; 变成原子性操作吗？&lt;/p&gt;

&lt;p&gt;这篇文章已经够长的了。就让我们在下一部门深入 MRI 的 GIL 来回答这些问题吧&lt;/p&gt;
&lt;h2 id="Part II 实现"&gt;Part II 实现&lt;/h2&gt;
&lt;p&gt;上文书说到，我想带你深潜到 MRI 里去看看 GIL 是怎么实现的。但是首先，我想确认一下我提出了正确的问题。Part I 中的疑问，但是今天我们将在 MRI 内找寻答案。我们将会追寻这条见首不见尾的神龙，他们管它叫 GIL。&lt;/p&gt;

&lt;p&gt;在本文的初稿中，我真的很强调 GIL 底层的 C 代码，尽可能展示它们。但过了一段时间，一些重要的信息被细节淹没了。我重头来过了，你现在看到的这一版去掉了一些 C 代码，多了一些解释和图示，但是对于代码水鬼们，我至少会提到 C 的函数名，所以你可以自己去一探究竟。&lt;/p&gt;
&lt;h3 id="书接上文..."&gt;书接上文...&lt;/h3&gt;
&lt;p&gt;Part I 留下了两个问题：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GIL 会使 &lt;strong&gt;array &amp;lt;&amp;lt; nil&lt;/strong&gt; 变成原子性操作吗？&lt;/li&gt;
&lt;li&gt;GIL 能保证你的 Ruby 代码线程安全吗？&lt;/li&gt;
&lt;/ol&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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;mutex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Mutex&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;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="n"&gt;mutex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synchronize&lt;/span&gt; &lt;span class="k"&gt;do&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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&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;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="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你假设 Array 是线程安全的，预计的结果是数组会有 5000 个元素。因为数组不是线程安全的，在 JRUby 和 Rubinius 的实现中产生了不期的结果；比 5000 少。这是多线程间交互切换造成的底层数据错误。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MRI 产生了预期结果，这是侥幸还是必然呢？&lt;/strong&gt; 让我们用这个 Ruby 代码片段来进行技术深潜。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="自顶而下"&gt;自顶而下&lt;/h3&gt;
&lt;p&gt;为了学习这个片段中到底发生了什么，我们需要 MRI 内部是如何衍生线程的。我们主要看&lt;strong&gt;thread*.c&lt;/strong&gt;文件中的那些函数。这些文件中有不少迂回之处，来同时支持 Windows 和 Posix 的线程 APIs，但是这个些函数都是从这些源码文件中看来的。&lt;/p&gt;

&lt;p&gt;第一个&lt;strong&gt;Thread.now&lt;/strong&gt;底层操作是衍生一个新的原生线程来支持 Ruby 线程。成为新线程主体的 C 函数称为&lt;a href="https://github.com/ruby/ruby/blob/trunk/thread.c#L480" rel="nofollow" target="_blank" title=""&gt;thread_start_func_2&lt;/a&gt;。让我们从高层次一看这个函数。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/b87c50095eceb2b99920ede9d1a465bd.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这里有很多样板代码，不值得一看。我标出了值得我们关注的部分。在接近顶部的地方，新线程获取 GIL。注意，这个线程会保持空闲，直到它确实获得了 GIL。在函数中部，它调用你穿给&lt;strong&gt;Thread.new&lt;/strong&gt;的那个代码块。包装事物后，它释放乐 GIL 并退出原生线程。&lt;/p&gt;

&lt;p&gt;在我们的片段中，这个新线程衍生于主线程。有鉴于此，我们可以假设主线程当前正持有 GIL。新线程将必须等待，直到主线程释放 GIL，它才能继续。&lt;/p&gt;

&lt;p&gt;让我们看一下当新线程尝试获取 GIL 时发生了什么吧。&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;void&lt;/span&gt;
&lt;span class="nf"&gt;gvl_acquire_common&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gvl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;vm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gvl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiting&lt;/span&gt;&lt;span class="o"&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;vm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gvl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiting&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_thread_wakeup_timer_thread_low&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&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;vm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gvl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;native_cond_wait&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;vm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gvl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cond&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;vm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;gvl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lock&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;a href="https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L68" rel="nofollow" target="_blank" title=""&gt;gvl_acquire_common&lt;/a&gt;函数。此函数在我们的新线程尝试获取 GIL 时被调用。&lt;/p&gt;

&lt;p&gt;首先，它会检查 GIL 当前是否被占有了，之后它增加 GIL 的&lt;strong&gt;waiting&lt;/strong&gt;属性。同我们的片段，这个值应该现在为 1。紧接着的一行检查看&lt;strong&gt;wating&lt;/strong&gt;是否是 1。它正是 1，于是下一行触发唤醒了个计时器线程。&lt;/p&gt;

&lt;p&gt;计时器线程是 MRI 中线程系统能一路高歌的秘密武器，并避免任意线程独霸 GIL。但在我们跳得太远之前，先让我们阐述一个 GIL 相关事物的状态，然后再来介绍计时器线程。&lt;/p&gt;

&lt;p&gt;我前面说了几次，MRI 线程依靠的是原生的操作系统线程。这是真的，但是如图中所示，每个 MRI 线程并行运行在各自的原生线程中。GIL 阻止这样。我们需要画出 GIL 来让其更为接近事实。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/71e0971109e3facdfe441a4711f66c81.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;当一个 Ruby 线程希望在它自己的原生线程中执行代码时，必须先获得 GIL。&lt;strong&gt;GIL 在 Ruby 线程和它们各自的原生进程之间周旋，极力消减并发！&lt;/strong&gt; 上张图里，Ruby 线程在其原生线程里可以并行执行。而第二张更接近 MRI 事实真相的图里，在特定时间点上只有一个线程可以获取 GIL，于是代码的执行是完全不能并行的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/3dbced195ad01d1d6cb8b223362c3e31.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;对 MRI 核心组而言，&lt;strong&gt;GIL 保卫着系统的内部状态&lt;/strong&gt;。使用 GIL，他们不需要在数据结构周围使用任何锁或者同步机制。如果两个线程不能够同时改变内部状态，也就不会有竞争条件发生了。&lt;/p&gt;

&lt;p&gt;对你，开发者而言，这会大大限制你从 MRI Ruby 代码中获得的并发能力。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/730dc87f009f5b8ba232045a0d8739b0.jpg" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="计时器线程"&gt;计时器线程&lt;/h3&gt;
&lt;p&gt;我前面提到计时器线程是用来避免一个线程独霸 GIL 的。计时器线程只是一个存在于 MRI 内部的原生线程；它没有相应的 Ruby 线程。计时器线程在 MRI 启动时以&lt;a href="https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1399" rel="nofollow" target="_blank" title=""&gt;rb_thread_create_timer_thread&lt;/a&gt;函数启动。&lt;/p&gt;

&lt;p&gt;当 MRI 启动并只有主线程运行时，计时器线程沉睡。但请记住，一旦有一个线程在等待 GIL，它即会唤醒计时器线程。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/c00cf71b1ec7797c12c5c8bef8446bd0.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这张图更近乎于 MRI 中 GIL 的实现。回想之前的片段，我刚刚衍生出最右边的线程。因为它是唯一在等待 GIL 的，就是它唤醒计时器线程的。&lt;/p&gt;

&lt;p&gt;计时器线程是用来避免一个线程独霸 GIL 的。每 100 毫秒，计时器线程在当前持有 GIL 的线程上设置一个中断标志，使用 &lt;strong&gt;RUBY_VM_SET_TIMER_INTERRUPT&lt;/strong&gt; 宏。这里的细节需要注意，因为这会给&lt;strong&gt;array &amp;lt;&amp;lt; nil&lt;/strong&gt; 是否是原子性操作这个问题提供线索。&lt;/p&gt;

&lt;p&gt;如果你熟悉&lt;a href="https://en.wikipedia.org/wiki/Preemption_(computing)#Time_slice" rel="nofollow" target="_blank" title=""&gt;时间片&lt;/a&gt;的概念，与此很相似。&lt;/p&gt;

&lt;p&gt;每 100 毫秒计时器线程会在当前持有 GIL 的线程上设置中断标记。设置中断标记并不实际中断线程的执行。如果是这样的话，我们可以肯定&lt;strong&gt;array &amp;lt;&amp;lt; nil&lt;/strong&gt;不是一个原子性操作。&lt;/p&gt;
&lt;h3 id="控制中断标志"&gt;控制中断标志&lt;/h3&gt;
&lt;p&gt;深入名为&lt;strong&gt;vm_eval.c&lt;/strong&gt;的文件，包含了控制 Ruby 方法调用的代码。它有负责创建方法调用的上下文，并调用正确的方法。在方法结束时调用&lt;a href="https://github.com/ruby/ruby/blob/trunk/vm_eval.c#L238" rel="nofollow" target="_blank" title=""&gt;vm_call0_body&lt;/a&gt;，正当它返回当前方法的返回值之前，这些中断会被检查。&lt;/p&gt;

&lt;p&gt;如果这个线程已经被设置了中断标志，则在返回其值前当场停止执行。在执行更多 Ruby 代码之前，当前线程会释放 GIL 并调用&lt;strong&gt;sched_yield&lt;/strong&gt;。&lt;strong&gt;sched_yield&lt;/strong&gt;是一个系统方法提示线程调度器安排另一个线程。一旦完成这个工作，该中断线程尝试重新获取 GIL，现在不得不等待另一个线程释放 GIL 了。&lt;/p&gt;

&lt;p&gt;嘿，这就是我们问题的答案。&lt;strong&gt;array &amp;lt;&amp;lt; nil&lt;/strong&gt;是原子性的。多亏了 GIL，所有用 C 实现的 Ruby 方法都是原子性的。&lt;/p&gt;

&lt;p&gt;所以这个例子：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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="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="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&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="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="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行在 MRI 上每次都保证产生预期的结果。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;但请记住，这个保证并不针对 Ruby 写成的那些代码。&lt;/strong&gt;如果你把这段代码放到没有 GIL 的其他实现里，它将会产生叵测的结果。很有必要了解一个 GIL 保证，但依赖它来写代码就不是个好主意了。在此过程中，你基本就把自己和 MRI 捆绑在一块儿了。&lt;/p&gt;

&lt;p&gt;相似的，GIL 不是公开的 API。没有文档和规程说明。虽说 Ruby 代码是隐式依赖 GIL 的，但之前的 MRI 团队曾谈及想摆脱 GIL 或改变其语义。出于这些原因，你当然不希望，写出来的代码只能依赖于现下 GIL 的行为吧。&lt;/p&gt;
&lt;h3 id="非原生方法"&gt;非原生方法&lt;/h3&gt;
&lt;p&gt;目前为止，我说到&lt;strong&gt;array &amp;lt;&amp;lt; nil&lt;/strong&gt;是原子性的。这很简单，因为&lt;strong&gt;Array#&amp;lt;&amp;lt;&lt;/strong&gt;方法只带一个参数。这个表达式里只有一个方法调用，并且它是用 C 实现的。如果它在过程中被中断了，只会继续直到完成，之后释放 GIL。&lt;/p&gt;

&lt;p&gt;那类似这样的呢？&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;在&lt;strong&gt;Array#&amp;lt;&amp;lt;&lt;/strong&gt;方法执行前，它先要对右侧的表达式进行求值，然后才能把表达式的值作为参数。所以&lt;strong&gt;User.find(1)&lt;/strong&gt;必须先被调用。如你所知，&lt;strong&gt;User.find(1)&lt;/strong&gt;会调用一大堆其他 Ruby 代码。&lt;/p&gt;

&lt;p&gt;所以，在上面的例子中 &lt;strong&gt;Array#&amp;lt;&amp;lt;&lt;/strong&gt; 依然是原子性的吗？是的，但是一旦右手边被求值。换句话说，没有原子性保证&lt;strong&gt;User.find(1)&lt;/strong&gt;方法将被调用。之后返回值会传给 有原子性保证的&lt;strong&gt;Array#&amp;lt;&amp;lt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;更新：&lt;a href="https://twitter.com/headius" rel="nofollow" target="_blank" title=""&gt; &lt;/a&gt;&lt;a href="/headius" class="user-mention" title="@headius"&gt;&lt;i&gt;@&lt;/i&gt;headius&lt;/a&gt; 发了一个&lt;a href="http://www.jstorimer.com/blogs/workingwithcode/8100871-nobody-understands-the-gil-part-2-implementation#comment-930773796" rel="nofollow" target="_blank" title=""&gt;极好的评论&lt;/a&gt;，扩展了 GIL 提供的保证。如果你读到这个，考虑必读一下。&lt;/p&gt;
&lt;h3 id="这一切意味着什么？"&gt;这一切意味着什么？&lt;/h3&gt;
&lt;p&gt;GIL 使得方法调用原子性。这个对你意味着什么呢？&lt;/p&gt;

&lt;p&gt;在 Part I 中，我举例展示了在 C 函数中发生上下文切换时会发生什么。使用 GIL，这种情况不会再发生了。相反，如果上下文切换发生了，其他线程会保持空闲以待 GIL，给当前线程机会继续不中断。此行为只适用于 MRI 用 C 实现的 Ruby 方法。&lt;/p&gt;

&lt;p&gt;这种行为消除了竞争条件的源头，不然 MRI 的内部竞争会防不胜防。从这个角度，GIL 是一个严格的 MRI 内部实现细节。它保持 MRI 的安全。&lt;/p&gt;

&lt;p&gt;但是还有一个挥之不去的问题尚无答案。GIL 能提供给你的 Ruby 代码线程安全保证吗？&lt;/p&gt;

&lt;p&gt;这是一个 MRI 使用中的重要问题，要是你熟悉其他环境的多线程编程，你可能已经知道了，答案是一个大写的&lt;strong&gt;不行&lt;/strong&gt;。但是这篇文章已经足够长了，我将会在&lt;a href="http://www.rubyinside.com/does-the-gil-make-your-ruby-code-thread-safe-6051.html" rel="nofollow" target="_blank" title=""&gt;Part III&lt;/a&gt;更彻底地解决这个问题。&lt;/p&gt;
&lt;h2 id="Part III: GIL能让你的Ruby代码线程安全吗？"&gt;Part III: GIL 能让你的 Ruby 代码线程安全吗？&lt;/h2&gt;
&lt;p&gt;围绕着 MRI 的 GIL，ruby 社区中有一些错误观念。要是你今天只想从这篇文章获取一个观点，那就是：GIL 不会使你的 Ruby 代码线程安全。&lt;/p&gt;

&lt;p&gt;但请别这么相信我。&lt;/p&gt;

&lt;p&gt;这个系列一开始只是为了从技术层面上了解 GIL。Part I 解释了竞争条件是如何在实现 MRI 的 C 源码中发生的。还有，GIL 貌似排除了风险，至少我们看到 &lt;strong&gt;Array#&amp;lt;&amp;lt;&lt;/strong&gt;方法是这样。&lt;/p&gt;

&lt;p&gt;Part II 证实了 GIL 的作为，实际上，它使得 MRI 的原生 C 方法实现原子化了。换而言之，这些原生方法是对竞争条件免疫的。这个保证只针对 MRI 的 C 原生方法，你自己写的那些 Ruby 可不行。
于是我得到一个遗留问题：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GIL 能否保证我们的 Ruby 代码是线程安全的？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我已经回答过这个问题了。现在我想确保谣言止于智者。&lt;/p&gt;
&lt;h3 id="归来的竞争条件"&gt;归来的竞争条件&lt;/h3&gt;
&lt;p&gt;竞争条件发生在一些数据块在多个线程之间共享，并且这些线程企图同时在数据上进行操作的时候。当发生时没有一种同步机制，比如锁，你的程序会开始做一些意料之外的事，并且数据也会遗失。&lt;/p&gt;

&lt;p&gt;让我们回过头来回顾一下这种竞争状态是如何发生的。我们将使用如下 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;Sheep&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;@shorn&lt;/span&gt; &lt;span class="o"&gt;=&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;def&lt;/span&gt; &lt;span class="nf"&gt;shorn?&lt;/span&gt;
    &lt;span class="vi"&gt;@shorn&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;shear!&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"shearing..."&lt;/span&gt;
    &lt;span class="vi"&gt;@shorn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&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;strong&gt;羊&lt;/strong&gt;在初始化的时候是没被薅过的。&lt;strong&gt;shear!&lt;/strong&gt;方法执行薅羊毛并标记这头羊为薅过。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/fbdc08265b90e42609c113f455832f14.jpg" title="" alt=""&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;sheep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sheep&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;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="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;sheep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shorn?&lt;/span&gt;
      &lt;span class="n"&gt;sheep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shear!&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="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;这一小段代码创建了头羊并且衍生出 5 个线程。每个线程竞相检查羊是不是被薅过？要是没有，就调用 &lt;strong&gt;shear!&lt;/strong&gt; 方法。&lt;/p&gt;

&lt;p&gt;以下结果是我在 MRI2.0 里多次执行得到的。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ruby check_then_set.rb
shearing...
&lt;span class="nv"&gt;$ &lt;/span&gt;ruby check_then_set.rb
shearing...
shearing...
&lt;span class="nv"&gt;$ &lt;/span&gt;ruby check_then_set.rb
shearing...
shearing...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有的时候一只羊被薅了两回。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;如果你有 GIL 是你的代码在多线程面前一马平川，赶快忘了吧。&lt;/em&gt; GIL 不能做出这样的担保。需要注意第一次运行时产生了预期结果。随后的几次运行，意外的结果才出现。如果继续试几次，会看到不同的变化。&lt;/p&gt;

&lt;p&gt;这些意外的结果归咎于 Ruby 代码中的竞争条件。这实际上是一个足够典型的竞争条件，这一模式被称为：&lt;strong&gt;检查 - 后-设置&lt;/strong&gt; 竞争条件。在检查 - 后-设置竞争条件中，两个以上线程检查某值，之后设置基于这个值的一些状态。在没有提供原子性的情况下，很有可能两个线程竞争通过"检查"阶段，之后一同执行"设置"阶段。&lt;/p&gt;
&lt;h3 id="认出竞争条件"&gt;认出竞争条件&lt;/h3&gt;
&lt;p&gt;在我们解决这个问题之前，首先我想让大家理解怎么认出竞争条件。向我介绍&lt;em&gt;交错&lt;/em&gt;这个术语的&lt;a href="https://twitter.com/brixen" rel="nofollow" target="_blank" title=""&gt;&lt;/a&gt;&lt;a href="/brixen" class="user-mention" title="@brixen"&gt;&lt;i&gt;@&lt;/i&gt;brixen&lt;/a&gt;,我欠你个人情。这真的很有帮助。&lt;/p&gt;

&lt;p&gt;记得吗，上下文切换可以发生在代码的任何一行上。当一个线程切换到另一个线程时，想象你的程序被切分了一组互不关联的块。有序的一组块就是一组交错。&lt;/p&gt;

&lt;p&gt;一种极端情况是，每行代码后面都可能都发生了上下文切换！这组交错会将每行代码穿插起来。另一种极端是，线程体中可能并没有发生上下文切换，这组交错会为每个线程保持各自原来代码的顺序。&lt;/p&gt;

&lt;p&gt;一些交错是无害的。不是没行代码都会进入竞争条件。但是把你的程序想象成一组可能的交错可以帮助你辨识到什么时候竞争竞争条件确实发生了。我会用一系列图示来展现：这段代码可能被两个 Ruby 线程交错的情况。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/bd508bd6ce29c6b887d680587ed197da.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;为了使图示简单明了，我将&lt;strong&gt;shear!&lt;/strong&gt;方法调用替换成了其方法体。&lt;/p&gt;

&lt;p&gt;考虑这个图示：红色标注的代码是线程 A 中的一组交错，蓝色标出的是线程 B 的一组交错。&lt;/p&gt;

&lt;p&gt;现在让我们模拟上下文切换来看一下代码是怎么被穿插起来的。最简单的情况是在运行中的线程没有被中断过。这样就没有竞争条件并会产生我们预期的输出。看起来就像是这样。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/5b01af3edebb83e675f4796de24192d8.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;如图中所示这是一系列的有序事件组成的。&lt;em&gt;注意 GIL 锁环绕着 Ruby 代码，所以两个线程不能真的并行跑。&lt;/em&gt;事件是有序的，从上到下依次发生。&lt;/p&gt;

&lt;p&gt;在这样的交错中，线程 A 做完了它所有的工作，之后线程调度器触发了一个上下文切换到线程 B。由于线程 A 已经薅完了羊毛并更新了&lt;strong&gt;shorn&lt;/strong&gt;变量，线程 B 其实什么也没做。&lt;/p&gt;

&lt;p&gt;但事情不总是这样简单。注意线程调度器可以在这块代码的任意一点触发上下文切换。这次只是我们运气好而已。&lt;/p&gt;

&lt;p&gt;来看看更凶残一些的例子，这回会产生意外的输出。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/c973201c69ecb3c85d5e4355f5b939b1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在这样的交错时，上下文切换真发生在会产生问题的地方。线程 A 检查了条件并且开始薅羊毛了。之后线程调度器调度了一个上下文切换，线程 B 上位了。尽管线程 A 已经执行了薅羊毛的工作，但尚未有机会更新&lt;strong&gt;shorn&lt;/strong&gt;属性，于是线程 B 对此一无所知。&lt;/p&gt;

&lt;p&gt;线程 B 自己也检查了条件，发现是&lt;strong&gt;false&lt;/strong&gt;，又薅了一回这只羊。一旦其完成了，线程 A 又被调度回来，完成执行。即使线程 B 在执行期间已经通过代码设置了&lt;strong&gt;shorn = true&lt;/strong&gt;，线程 A 也需要在做一遍，因为它就是在这退出又恢复的。&lt;/p&gt;

&lt;p&gt;一只羊被薅两次也没什么大不了的，但是试将羊替换成发票，薅羊毛替换成集款，那一些客户就该不 happy 了。&lt;/p&gt;

&lt;p&gt;我会分享更多例子来阐述事物不确定性的本质。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2015/a531400504c151d03284fe62ce8f5ffd.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这里加入了更多的上下文切换，于是每个线程占小步前进，而且来回切换。请注意这里展示的是理论上的情况，上下文切换可以发生在程序的任何一行。每次代码执行交错的发生也不尽相同，所以这一次它可能会得到预期结果，下回就可能得到意外结果。&lt;/p&gt;

&lt;p&gt;这真的是思考竞争条件的一种好方法。当你执笔多线程代码时，你需要考虑到程序可能背怎样切开和穿插，并产生多种多样的交错。如果一些交错貌似会带来错误的结果，你也许就要重新考虑解决问题的方法或者是用&lt;strong&gt;Mutex&lt;/strong&gt;引入同步机制。&lt;/p&gt;
&lt;h3 id="真糟糕！"&gt;真糟糕！&lt;/h3&gt;
&lt;p&gt;此刻正应告诉你用&lt;strong&gt;Mutex&lt;/strong&gt;引入同步机制可以使示例代码线程安全。这是真的，你&lt;a href="https://gist.github.com/jstorimer/5802725" rel="nofollow" target="_blank" title=""&gt;可以试一下&lt;/a&gt;。但我有意举例证明这一观点，这些槽糕的代码可不要用在多线程环境中啊。&lt;/p&gt;

&lt;p&gt;无论何时你有多个线程共享一个对象引用时，并对其做了修改，你就要有麻烦了，除非有锁来阻止修改中的上下文切换。&lt;/p&gt;

&lt;p&gt;然而，不在代码中使用显示锁，也能简单解决这种特定的竞争条件。这里有个&lt;strong&gt;Queue&lt;/strong&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;'thread'&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sheep&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;sheep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sheep&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;sheep_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="n"&gt;sheep_queue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;sheep&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="k"&gt;begin&lt;/span&gt;
        &lt;span class="n"&gt;sheep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sheep_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;sheep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shear!&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ThreadError&lt;/span&gt;
        &lt;span class="c1"&gt;# raised by Queue#pop in the threads&lt;/span&gt;
        &lt;span class="c1"&gt;# that don't pop the sheep&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="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;因为没有变化，我忽略了&lt;strong&gt;Sheep&lt;/strong&gt;的实现。现在，不在是每个线程共享&lt;strong&gt;sheep&lt;/strong&gt;对象并竞争去薅它，队列提供了同步机制。&lt;/p&gt;

&lt;p&gt;用 MRI 或其他真正并行的 Ruby 实现来运行，这段程序总会返回预期的结果。&lt;em&gt;我们已经消除了这段代码的竞争条件。&lt;/em&gt;即使所有的线程可能多多少少会在同一时间掉用 &lt;strong&gt;Queue#pop&lt;/strong&gt;，但是其内部使用了&lt;strong&gt;Mutex&lt;/strong&gt;来保证只有一个线程可以得到羊。&lt;/p&gt;

&lt;p&gt;一旦这个线程得到了羊，竞争条件就消失了。这个线程也没有竞争对手了！&lt;/p&gt;

&lt;p&gt;我建议使用&lt;strong&gt;Queue&lt;/strong&gt;作为锁的替代品，是因为它只简单地正确利用了队列。众所周知锁是很容易出错的。一旦使用不当，它们就会带来像死锁和性能下降这样的担忧。利用依赖抽象的数据结构。它严格包装了复杂的问题，提供简便的 API。&lt;/p&gt;
&lt;h3 id="惰性初始化"&gt;惰性初始化&lt;/h3&gt;
&lt;p&gt;我会一笔带过：惰性初始化是&lt;strong&gt;检查 - 后-设置&lt;/strong&gt;另一种形式的竞争条件。&lt;strong&gt;||=&lt;/strong&gt;操作符实际上扩展为：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vi"&gt;@logger&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;Logger&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;# expands to&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@logger&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;@logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Logger&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;end&lt;/span&gt;

&lt;span class="vi"&gt;@logger&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看看扩展版本，然后想象一下交错在哪会发生。在多线程和无同步的情况下，&lt;strong&gt;@logger&lt;/strong&gt;确实可能被初始化两次。再此强调，两次创建&lt;strong&gt;Logger&lt;/strong&gt;可能没什么的，但是我见过一些 Bug 像是&lt;a href="https://github.com/krisleech/wisper/commit/38c7783887a5dabdb7d7c93910dcedbf6d94f308" rel="nofollow" target="_blank" title=""&gt;in the wild&lt;/a&gt;就是这个原因造成的。&lt;/p&gt;
&lt;h3 id="反思"&gt;反思&lt;/h3&gt;
&lt;p&gt;在最后我想给大家一些忠告。&lt;/p&gt;

&lt;p&gt;5 个牙医里有 4 个同意多线程编程很难做到正确。&lt;/p&gt;

&lt;p&gt;最终，GIL 保证了 MRI 中 C 实现的原生 Ruby 方法执行的原子性（即便有些&lt;a href="http://www.jstorimer.com/blogs/workingwithcode/8100871-nobody-understands-the-gil-part-2-implementation#comment-930773796" rel="nofollow" target="_blank" title=""&gt;警告&lt;/a&gt;）。这一行为有时可以帮助作为 Ruby 开发者的我们，但是 GIL 其实是为了保护 MRI 内部而设计的，对 Ruby 开发者没有可靠的 API。&lt;/p&gt;

&lt;p&gt;所以 GIL 不能解决线程安全的问题。就像我说的，使多线程编程正确很难，但是我们每天都在解决棘手的问题。我们面对棘手问题的方法之一是良好的抽象。&lt;/p&gt;

&lt;p&gt;举例来说，当我的代码需要发一个 HTTP 请求时，我需要用一个套接字。但我并不直接使用套接字，这样既笨重又容易出错。相反，我使用一个抽象。一个 HTTP 客户端来提供更具体，简便的 API，把与套接字的交互和相关边界问题隐藏起来。&lt;/p&gt;

&lt;p&gt;如果多线程编程很难保持正确，也许你不应该直接干。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果程序里增加一个线程，可能同时增加 5 个新 Bug。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.rubyinside.com/twitter.com/mperham" rel="nofollow" target="_blank" title=""&gt; Mike Perham&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;我看到越来越多围绕线程的抽象出现了。在 Ruby 社区里流行起了并发的角色模型，最受欢迎的实现是&lt;a href="http://celluloid.io/" rel="nofollow" target="_blank" title=""&gt;Celluloid&lt;/a&gt;。Celluloid 结合 Ruby 对象模型和并发原语提供了良好的抽象。Celluloid 无法保证你的代码线程安全或对竞争条件免疫，但它包装了最佳实践。我希望你&lt;a href="https://github.com/celluloid/celluloid/wiki/Basic-usage" rel="nofollow" target="_blank" title=""&gt;试一试&lt;/a&gt;&lt;a href="http://railscasts.com/episodes/367-celluloid" rel="nofollow" target="_blank" title=""&gt;Celluloid&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;我们谈论这些不特定于 Ruby 或者 MRI。这是一个多核编程的真实世界。我们设备上的核数只会越来越多，MRI 仍然在寻找解决方案。尽管它的保证，GIL 限制并行执行的方向似乎是错误的。这也是 MRI 的成长的烦恼吧。其他实现，如 JRuby 和 Rubinius 中已经没有了 GIL，现实了真正的并行。&lt;/p&gt;

&lt;p&gt;我们看到许多新的语言，具有内置在语言核心内的并发抽象。Ruby 没有，至少目前还没有。依赖于抽象的另一个好处是，抽象可以改进它们的实现，同时保持（业务逻辑）代码不变。例如，如果队列的现实从依靠锁切换到无锁同步，而得益于抽象，你的代码无需任何修改。&lt;/p&gt;

&lt;p&gt;目前，Ruby 开发者应该在这些问题上自我提高！了解并发。警惕竞争条件。以交错方式思考代码可以帮助你研究竞争条件。&lt;/p&gt;

&lt;p&gt;我会引用这句话，它对今天并发领域工作的影响是巨大的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;别用状态共享通信，用通信共享状态。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用数据结构来提供同步机制；角色模型即使如此。这一理念也存在于 Go,Erlang 和其他一些语言的并发模型内核里。&lt;/p&gt;

&lt;p&gt;Ruby 需要看看别的语言里做了什么并且拥抱变化。作为一个 Ruby 开发人员，你现在就可以尝试和支持这些可替代方案（指其他语言中的）。随着越来越多人参与进来，这些方案有可能会成为 Ruby 的新标准。&lt;/p&gt;

&lt;p&gt;感谢  Brian Shirai 为我校队本文草稿。&lt;/p&gt;</description>
      <author>wadexing</author>
      <pubDate>Wed, 16 Dec 2015 18:20:31 +0800</pubDate>
      <link>https://ruby-china.org/topics/28415</link>
      <guid>https://ruby-china.org/topics/28415</guid>
    </item>
    <item>
      <title>画说 Ruby 与 Python 垃圾回收</title>
      <description>&lt;p&gt;&lt;a href="http://patshaughnessy.net/2013/10/24/visualizing-garbage-collection-in-ruby-and-python" rel="nofollow" target="_blank" title=""&gt;原文&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;本文基于我在刚刚过去的在布达佩斯举行的&lt;a href="http://13.rupy.eu/" rel="nofollow" target="_blank" title=""&gt;RuPy&lt;/a&gt;上的演讲。我觉得趁热打铁写成帖子应该会比只留在幻灯片上更有意义。你也可以看看&lt;a href="https://www.youtube.com/watch?v=qzEekAnAS_g" rel="nofollow" target="_blank" title=""&gt;演讲录像&lt;/a&gt;。再跟你说件事，我在 Ruby 大会也会做一个&lt;a href="https://www.youtube.com/watch?v=yl_zYzPiDto" rel="nofollow" target="_blank" title=""&gt;相似的演讲&lt;/a&gt;，但是我不会去说 Python 的事儿，相反我会对比一下 MRI,JRuby 和 Rubinius 的垃圾回收机制。&lt;/p&gt;

&lt;p&gt;想了解 Ruby 垃圾回收机制和 Ruby 内部实现更详尽的阐述，请关注即将问世的拙作&lt;a href="http://nostarch.com/rum" rel="nofollow" target="_blank" title=""&gt;《Ruby Under a Microscope》&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/circuitory-system-polish.png" title="" alt="如果将算法和业务逻辑比作应用程序的大脑，垃圾回收对应哪个器官呢？"&gt;&lt;/p&gt;

&lt;p&gt;既然是"Ruby Python"大会，我觉得对比一下 Ruby 和 Python 的垃圾回收机制应该会很有趣。在此之前，到底为什么要计较垃圾回收呢？毕竟，这不是什么光鲜亮丽激动人心的主题，对吧。你们大家有多少人对垃圾回收感冒？(竟然有不少 RuPyde 与会者举手了!)&lt;/p&gt;

&lt;p&gt;最近 Ruby 社区发表了一篇&lt;a href="http://fredwu.me/post/60441991350/protip-ruby-devs-please-tweak-your-gc-settings-for" rel="nofollow" target="_blank" title=""&gt;博文&lt;/a&gt;,是关于如何通过更改 Ruby GC 设置来为单元测试提速的。我认为这篇文章是极好的。对于想让单元测试跑得更快和让程序 GC 暂停更少的人来说很有裨益，但是 GC 并没能引起我的兴趣。第一瞥 GC 就像是一个让人昏昏欲睡的、干巴巴的技术主题。&lt;/p&gt;

&lt;p&gt;但是实际上垃圾回收是一个迷人的主题：GC 算法不仅是计算机科学史的重要组成部分，也是一个前沿课题。举例来说，MRI Ruby 使用的标记 - 清除算法已经年逾五旬了，而 Ruby 的替代语言 Rubinius 使用的 GC 算法在不久前的 2008 年才被发明出来。&lt;/p&gt;

&lt;p&gt;然而，"垃圾回收"这个词其实有些用词不当。&lt;/p&gt;
&lt;h2 id="应用程序那颗跃动的心"&gt;应用程序那颗跃动的心&lt;/h2&gt;
&lt;p&gt;GC 系统所承担的工作远比"垃圾回收"多得多。实际上，它们负责三个重要任务。它们&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;如果将应用程序比作人的身体：所有你所写的那些优雅的代码，业务逻辑，算法，应该就是大脑。以此类推，垃圾回收机制应该是那个身体器官呢？（我从 RuPy 听众那听到了不少有趣的答案：腰子、白血球 :) ）&lt;/p&gt;

&lt;p&gt;我认为垃圾回收就是应用程序那颗跃动的心。像心脏为身体其他器官提供血液和营养物那样，垃圾回收器为你的应该程序提供内存和对象。如果心脏停跳，过不了几秒钟人就完了。如果垃圾回收器停止工作或运行迟缓，像动脉阻塞，你的应用程序效率也会下降，直至最终死掉。&lt;/p&gt;
&lt;h2 id="一个简单的例子"&gt;一个简单的例子&lt;/h2&gt;
&lt;p&gt;运用实例一贯有助于理论的理解。下面是一个简单类，分别用 Python 和 Ruby 写成，我们今天就以此为例：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/code.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;顺便提一句，两种语言的代码竟能如此相像：Ruby 和 Python 在表达同一事物上真的只是略有不同。但是在这两种语言的内部实现上是否也如此相似呢？&lt;/p&gt;
&lt;h3 id="可用列表"&gt;可用列表&lt;/h3&gt;
&lt;p&gt;当我们执行上面的&lt;em&gt;Node.new(1)&lt;/em&gt;时，Ruby 到底做了什么？Ruby 是如何为我们创建新的对象的呢？&lt;/p&gt;

&lt;p&gt;出乎意料的是它做的非常少。实际上，早在代码开始执行前，Ruby 就提前创建了成百上千个对象，并把它们串在链表上，名曰：可用列表。下图所示为可用列表的概念图：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/free-list1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;想象一下每个白色方格上都标着一个"未使用预创建对象"。当我们调用 &lt;em&gt;Node.new&lt;/em&gt; ,Ruby 只需取一个预创建对象给我们使用即可：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/free-list2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图中左侧灰格表示我们代码中使用的当前对象，同时其他白格是未使用对象。(请注意：无疑我的示意图是对实际的简化。实际上，Ruby 会用另一个对象来装载字符串"ABC",另一个对象装载 Node 类定义，还有一个对象装载了代码中分析出的抽象语法树，等等)&lt;/p&gt;

&lt;p&gt;如果我们再次调用 &lt;em&gt;Node.new&lt;/em&gt;，Ruby 将递给我们另一个对象：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/free-list3.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这个简单的用链表来预分配对象的算法已经发明了超过 50 年，而发明人这是赫赫有名的计算机科学家 John McCarthy，一开始是用 Lisp 实现的。Lisp 不仅是最早的函数式编程语言，在计算机科学领域也有许多创举。其一就是利用垃圾回收机制自动化进行程序内存管理的概念。&lt;/p&gt;

&lt;p&gt;标准版的 Ruby，也就是众所周知的"Matz's Ruby Interpreter"(MRI),所使用的 GC 算法与 McCarthy 在 1960 年的实现方式很类似。无论好坏，Ruby 的垃圾回收机制已经 53 岁高龄了。像 Lisp 一样，Ruby 预先创建一些对象，然后在你分配新对象或者变量的时候供你使用。&lt;/p&gt;
&lt;h3 id="Python 的对象分配"&gt;Python 的对象分配&lt;/h3&gt;
&lt;p&gt;我们已经了解了 Ruby 预先创建对象并将它们存放在可用列表中。那 Python 又怎么样呢？&lt;/p&gt;

&lt;p&gt;尽管由于许多原因 Python 也使用可用列表 (用来回收一些特定对象比如 list)，但在为新对象和变量分配内存的方面 Python 和 Ruby 是不同的。&lt;/p&gt;

&lt;p&gt;例如我们用 Pyhon 来创建一个 Node 对象：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;与 Ruby 不同，当创建对象时 Python 立即向操作系统请求内存。(Python 实际上实现了一套自己的内存分配系统，在操作系统堆之上提供了一个抽象层。但是我今天不展开说了。)&lt;/p&gt;

&lt;p&gt;当我们创建第二个对象的时候，再次像 OS 请求内存：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;看起来够简单吧，在我们创建对象的时候，Python 会花些时间为我们找到并分配内存。&lt;/p&gt;
&lt;h3 id="Ruby 开发者住在凌乱的房间里"&gt;Ruby 开发者住在凌乱的房间里&lt;/h3&gt;
&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/mess.jpg" title="" alt="Ruby把无用的对象留在内存里，直到下一次GC执行"&gt;&lt;/p&gt;

&lt;p&gt;回过来看 Ruby。随着我们创建越来越多的对象，Ruby 会持续寻可用列表里取预创建对象给我们。因此，可用列表会逐渐变短：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/free-list4.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;...然后更短：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/free-list5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;请注意我一直在为变量 n1 赋新值，Ruby 把旧值留在原处。"ABC","JKL"和"MNO"三个 Node 实例还滞留在内存中。Ruby 不会立即清除代码中不再使用的旧对象！Ruby 开发者们就像是住在一间凌乱的房间，地板上摞着衣服，要么洗碗池里都是脏盘子。作为一个 Ruby 程序员，无用的垃圾对象会一直环绕着你。&lt;/p&gt;
&lt;h3 id="Python 开发者住在卫生之家庭"&gt;Python 开发者住在卫生之家庭&lt;/h3&gt;
&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/clean.jpg" title="" alt="用完的垃圾对象会立即被Python打扫干净"&gt;&lt;/p&gt;

&lt;p&gt;Python 与 Ruby 的垃圾回收机制颇为不同。让我们回到前面提到的三个 Python Node 对象：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python3b.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在内部，创建一个对象时，Python 总是在对象的 C 结构体里保存一个整数，称为 &lt;em&gt;引用数&lt;/em&gt;。期初，Python 将这个值设置为 1：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python4.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;值为 1 说明分别有个一个指针指向或是引用这三个对象。假如我们现在创建一个新的 Node 实例，JKL：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;与之前一样，Python 设置 JKL 的引用数为 1。然而，请注意由于我们改变了 n1 指向了 JKL，不再指向 ABC，Python 就把 ABC 的引用数置为 0 了。&lt;/p&gt;

&lt;p&gt;此刻，Python 垃圾回收器立刻挺身而出！每当对象的引用数减为 0，Python 立即将其释放，把内存还给操作系统：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python6.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上面 Python 回收了 ABC Node 实例使用的内存。记住，Ruby 弃旧对象原地于不顾，也不释放它们的内存。&lt;/p&gt;

&lt;p&gt;Python 的这种垃圾回收算法被称为&lt;em&gt;引用计数&lt;/em&gt;。是 George Collins 在 1960 年发明的，恰巧与 John McCarthy 发明的&lt;em&gt;可用列表算法&lt;/em&gt;在同一年出现。就像 Mike Bernstein 在 6 月份&lt;a href="http://goruco.com/" rel="nofollow" target="_blank" title=""&gt;哥谭市 Ruby 大会&lt;/a&gt;杰出的&lt;a href="http://www.confreaks.com/videos/2545-goruco2013-to-know-a-garbage-collector" rel="nofollow" target="_blank" title=""&gt;垃圾回收机制演讲&lt;/a&gt;中说的:"1960 年是垃圾收集器的黄金年代..."&lt;/p&gt;

&lt;p&gt;Python 开发者工作在卫生之家，你可以想象，有个患有轻度 [OCD][] 的室友一刻不停地跟在你身后打扫，你一放下脏碟子或杯子，有个家伙已经准备好把它放进洗碗机了！&lt;/p&gt;

&lt;p&gt;现在来看第二例子。加入我们让 n2 引用 n1：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/python8.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图中左边的 DEF 的引用数已经被 Python 减少了，垃圾回收器会立即回收 DEF 实例。同时 JKL 的引用数已经变为了 2，因为 n1 和 n2 都指向它。&lt;/p&gt;
&lt;h2 id="标记-清除"&gt;标记 - 清除&lt;/h2&gt;
&lt;p&gt;最终那间凌乱的房间充斥着垃圾，再不能岁月静好了。在 Ruby 程序运行了一阵子以后，可用列表最终被用光光了：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/mark-and-sweep1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;此刻所有 Ruby 预创建对象都被程序用过了 (它们都变灰了)，可用列表里空空如也（没有白格子了）。&lt;/p&gt;

&lt;p&gt;此刻 Ruby 祭出另一 McCarthy 发明的算法，名曰：标记 - 清除。首先 Ruby 把程序停下来，Ruby 用"地球停转垃圾回收大法"。之后 Ruby 轮询所有指针，变量和代码产生别的引用对象和其他值。同时 Ruby 通过自身的虚拟机便利内部指针。标记出这些指针引用的每个对象。
我在图中使用 M 表示。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/mark-and-sweep2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图中那三个被标 M 的对象是程序还在使用的。在内部，Ruby 实际上使用一串位值，被称为：可用位图 (译注：还记得《编程珠玑》里的为突发排序吗，这对离散度不高的有限整数集合具有很强的压缩效果，用以节约机器的资源。)，来跟踪对象是否被标记了。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/mark-and-sweep3.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Ruby 将这个可用位图存放在独立的内存区域中，以便充分利用 Unix 的写时拷贝化。有关此事的更多内容请关注我另一博文&lt;a href="http://patshaughnessy.net/2012/3/23/why-you-should-be-excited-about-garbage-collection-in-ruby-2-0" rel="nofollow" target="_blank" title=""&gt;《Why You Should Be Excited About Garbage Collection in Ruby 2.0》&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果说被标记的对象是存活的，剩下的未被标记的对象只能是垃圾，这意味着我们的代码不再会使用它了。我会在下图中用白格子表示垃圾对象：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/mark-and-sweep4.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;接下来 Ruby 清除这些无用的垃圾对象，把它们送回到可用列表中：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2013/10/24/mark-and-sweep5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;在内部这一切发生得迅雷不及掩耳，因为 Ruby 实际上不会吧对象从这拷贝到那。而是通过调整内部指针，将其指向一个新链表的方式，来将垃圾对象归位到可用列表中的。&lt;/p&gt;

&lt;p&gt;现在等到下回再创建对象的时候 Ruby 又可以把这些垃圾对象分给我们使用了。在 Ruby 里，对象们六道轮回，转世投胎，享受多次人生。&lt;/p&gt;
&lt;h2 id="标记-删除 vs. 引用计数"&gt;标记 - 删除 vs. 引用计数&lt;/h2&gt;
&lt;p&gt;乍一看，Python 的 GC 算法貌似远胜于 Ruby 的：宁舍洁宇而居秽室乎？为什么 Ruby 宁愿定期强制程序停止运行，也不使用 Python 的算法呢？&lt;/p&gt;

&lt;p&gt;然而，引用计数并不像第一眼看上去那样简单。有许多原因使得不许多语言不像 Python 这样使用引用计数 GC 算法：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;首先，它不好实现。Python 不得不在每个对象内部留一些空间来处理引用数。这样付出了一小点儿空间上的代价。但更糟糕的是，每个简单的操作（像修改变量或引用）都会变成一个更复杂的操作，因为 Python 需要增加一个计数，减少另一个，还可能释放对象。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;第二点，它相对较慢。虽然 Python 随着程序执行 GC 很稳健（一把脏碟子放在洗碗盆里就开始洗啦），但这并不一定更快。Python 不停地更新着众多引用数值。特别是当你不再使用一个大数据结构的时候，比如一个包含很多元素的列表，Python 可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;最后，它不是总奏效的。在我的下一篇包含了我这个演讲剩余部分笔记的文章中，我们会看到，引用计数不能处理环形数据结构--也就是含有循环引用的数据结构。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="下回分解"&gt;下回分解&lt;/h2&gt;
&lt;p&gt;下周我会分解&lt;a href="http://patshaughnessy.net/2013/10/30/generational-gc-in-python-and-ruby" rel="nofollow" target="_blank" title=""&gt;演讲的剩余部分&lt;/a&gt;。我会讨论一下 Python 如何摆平环形数据类型及 GC 在即将出炉的 Ruby2.1 发行版中是如何工作的。&lt;/p&gt;

&lt;p&gt;[OCD]: Obsessive compulsive disorder  强迫症即强迫性神经症，亦译沉溺，是一种神经官能症，为焦虑症的一种。患有此病的患者总是被一种入侵式的思维所困扰，在生活中反复出现强迫观念及强迫行为，使到患者感到不安、恐慌或者担忧等等，从而进行某种重复行为，至使舒缓其此种压迫感受。患者自知力完好，对于症状了解，然而无法摆脱强迫行为。强迫症是世界上最常见精神问题中的第四位，其病发率跟哮喘及糖尿病同样普遍。在美国，每 50 个人就有一人可能是强迫症患者。&lt;/p&gt;</description>
      <author>wadexing</author>
      <pubDate>Fri, 20 Nov 2015 14:14:07 +0800</pubDate>
      <link>https://ruby-china.org/topics/28127</link>
      <guid>https://ruby-china.org/topics/28127</guid>
    </item>
    <item>
      <title>千万别构建超过 23 个字符的 Ruby 字符串</title>
      <description>&lt;p&gt;&lt;a href="http://patshaughnessy.net/2012/1/4/never-create-ruby-strings-longer-than-23-characters" rel="nofollow" target="_blank" title=""&gt;原文&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;显而易见这完全是句胡话：这样的编码规定不仅难以置信的荒诞，而且还多少有点故弄玄虚。
我甚至可以想象出设计师和资方的一系列可笑对白："不...&amp;lt;输入&amp;gt;字段的大小应该是 23...24！太长了！" 或者 "我们需要对用户解释他们的标题行应当小于 23 个字母..." 或者 "Twitter 完全搞错了...140 字的限制应该是 23 个字！"&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2012/1/4/microscope.jpg" title="Optional title" alt="在显微镜下观察事物有时会有意想不到的发现"&gt;&lt;/p&gt;

&lt;p&gt;为啥我会提出这样一个在现实中显得傻兮兮的编码规则呢？这确实有背后的缘由：构造短一些的 Ruby 字符串确实比构建长字符串更快。来看这一行 Ruby 代码：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1234567890123456789012"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"x"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 MRI 1.9.3 Ruby 解释器中它的执行速度几乎是下面这一行的两倍：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"12345678901234567890123"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"x"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;啊？有啥不同啊？这两行看起来一模一样啊！好吧，不同之处在于第一行代码构建了一个包含 23 个字符的新字符串，而第二个行代码包含了 24 个字符。如此可见，MRI Ruby 1.9 解释器对创建 23 个字符的字符串进行了优化，至少说比构建长字符串要快。Ruby 1.8 里可不是这么回事。&lt;/p&gt;

&lt;p&gt;我会细究一下 MRI Ruby 1.9 解释器，来了解它是怎么处理字符串的，并搞清楚这到底是怎么回事。&lt;/p&gt;
&lt;h2 id="不是所有字符串创建都是一样的"&gt;不是所有字符串创建都是一样的&lt;/h2&gt;
&lt;p&gt;节前 (译注：根据原文的发布时间推测是 2011 的圣诞) 我决定通读一下&lt;a href="http://rhg.rubyforge.org/" rel="nofollow" target="_blank" title=""&gt;Ruby Hacking Guide&lt;/a&gt;。要是你还没听过这篇文章的话，它是一篇对 Ruby 解释器内部工作进行阐述的佳作。不幸的时这实际是日语写就的，但是一部分章节已经被翻译成为英语了。比如&lt;a href="http://rhg.rubyforge.org/chapter02.html" rel="nofollow" target="_blank" title=""&gt;第二章&lt;/a&gt;，从这一章开始阐述了所有基本 Ruby 数据类型，包括字符串。&lt;/p&gt;

&lt;p&gt;通读之后，我决定潜入 MRI 1.9.3 C 源码中，更深入地学习 Ruby 是如何操纵字符串的。由于我使用 RVM，对我而言 Ruby 源代码在~/.rvm/src/ruby-1.9.3-preview1 目录下。我从 include/ruby/ruby.h 文件开始了源码漫游，这个头文件中定义了额所有的 Ruby 基本数据类型，对于 Ruby 字符串对象的实现在 string.c 中。&lt;/p&gt;

&lt;p&gt;通过阅读 C 代码我发现其实 Ruby 实际上用到了三种不同的字符串值，我把它们称为：&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;我被迷住了！多年来我早已假定所有的 Ruby 字符串对象都是一样的。但结果这并不正确！我们凑近了看一下吧....&lt;/p&gt;
&lt;h2 id="堆字符串"&gt;堆字符串&lt;/h2&gt;
&lt;p&gt;对 Ruby 而言标准做法也是最常见的方式是：字符串数据被存储到"堆"中。在 C 语言中堆是个核心概念：堆是一个大内存池，C 程序可以通过 &lt;em&gt;malloc&lt;/em&gt; 方法分配和使用它。举例来说，下面的 C 代码从堆中分配了 100 字节的内存块 (chunk)，并将其内存地址保存在一个指针中：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;malloc&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;之后，当 C 程序用完那段内存，她可以通过&lt;em&gt;free&lt;/em&gt;方法将其释放，将它还给系统使用：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用像Ruby、Java、C#这些高级语言,最大的好处之一就是避免了显示的手动内存管理。当你在Ruby代码中构造一个字符串值，就像这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Lorem ipsum dolor sit amet, consectetur adipisicing elit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ruby 解释器构造了一个称为"RString"的结构，类似下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2012/1/4/heap-string.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;如你所见 RString 结构包含了两个值：&lt;em&gt;ptr&lt;/em&gt; 和 &lt;em&gt;len&lt;/em&gt;，但并不包含字符串本身的数据。Ruby 实际上将字符串的字符值本身存在从堆中分配的内存空间中，之后将 ptr 设置为那段堆内存的地址，len 为字符串的长度。&lt;/p&gt;

&lt;p&gt;这里有一个 RString 结构体的简易版：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&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 结构体中实际上包含了不少其他值。我会迟点讨论一些值，另一些则跳过不说。如果你对 C 不是很熟，你可以把结构（简称结构）看做一个包含一组实例变量的对象，C 语言中其实根本没有对象的概念，结构就是一段含有一些值的内存块。&lt;/p&gt;

&lt;p&gt;我把这样的 Ruby 字符串称为"堆字符串"，因为这种字符串数据实际保存在堆中。&lt;/p&gt;
&lt;h2 id="共享型字符串"&gt;共享型字符串&lt;/h2&gt;
&lt;p&gt;另一类 Ruby 解释器用到的字符串值在 C 源码中称为"共享型字符串"。每当写下将一个字符串复制到另一个变量上时就会创建共享型字符串，类似这样：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Lorem ipsum dolor sit amet, consectetur adipisicing elit"&lt;/span&gt;
&lt;span class="n"&gt;str2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时 Ruby 解释器意识到你正将同一个字符串值赋值给两个变量：str 和 str2。于是事实上没有必要对字符串数据创建两个副本，相反 Ruby 创建了两个 RString 值，共享一个字符串数据副本。方法是使两个 RString 结构包含相同的 &lt;em&gt;ptr&lt;/em&gt; 值 ... 这意味着两个字符串包含相同的值。在第二个 RString 结构中还有一个值 &lt;em&gt;shared&lt;/em&gt; 指向第一个 RString 结构。有些细节我在这里没有展示，像一些位掩码标识 (bit mask flags) 用来跟踪 RString 是否是共享的。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2012/1/4/shared-string.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;除了可以节省内存，这一做法也显著提高了 Ruby 程序的执行速度，避免再次调用 malloc 从堆分配内存。&lt;em&gt;malloc&lt;/em&gt; 确实是一项代价不菲的操作：它得花时间在堆中追查适当大小的可用内存，还得持续跟踪它之后的释放情况。&lt;/p&gt;

&lt;p&gt;下面是一个更接近事实版本的 C RString 结构，包含了 shared 值：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&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;shared&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;h2 id="嵌入式字符串"&gt;嵌入式字符串&lt;/h2&gt;
&lt;p&gt;第三种也是最后一种 MRI Ruby 1.9 保存字符串数据的方式是：将字符数据嵌入 RString 结构体自身，就像：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;str3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Lorem ipsum dolor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2012/1/4/embedded-string.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;这个 RString 结构体包含了一个字符数组 &lt;em&gt;ary&lt;/em&gt; 但没有 &lt;em&gt;ptr&lt;/em&gt;，&lt;em&gt;len&lt;/em&gt; 和 &lt;em&gt;shared&lt;/em&gt; 值，就像上图中我们看到的。下面同样是一个简化了的 RString 结构体定义，只是这次包含了 &lt;em&gt;ary&lt;/em&gt; 字符数组：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;RString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;char&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;RSTRING_EMBED_LEN_MAX&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;要是你是对 C 代码不熟，语法 char ary[100] 创建了一个长度为 100 字符的字节 的数组。不像 Ruby，C 语言里的数组不是对象，而只是一个字节集合。在 C 语言里必须在数组创建之初就指定其长度。&lt;/p&gt;

&lt;p&gt;内嵌式字符串是如何起效的？好吧，关键在于 &lt;em&gt;ary&lt;/em&gt; 数组的大小，它被设为 &lt;em&gt;RSTRING_EMBED_LEN_MAX+1&lt;/em&gt;。如果运行在 64 位机器上，Ruby 的&lt;em&gt;RSTRING_EMBED_LEN_MAX&lt;/em&gt;被设为 24。所以像下面这样的短字符串可以被放到 RString 的&lt;em&gt;ary&lt;/em&gt;数组中：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Lorem ipsum dolor"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相对的长字符串就不行了：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Lorem ipsum dolor sit amet, consectetur adipisicing elit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Ruby是如何构建一个新字符串值的"&gt;Ruby 是如何构建一个新字符串值的&lt;/h2&gt;
&lt;p&gt;无论何时 Ruby 1.9 解释器通过类似这样的算法来构建一个字符串：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;是新的字符串值？还一个已存在字符串的副本？如果是副本的话，Ruby 就创建一个共享型字符串。这是最快的选项，因为 Ruby 只需要新建一个 RString 结构体，不需要复制一个已经存在的字符串数据。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;是长字符串吗？还是短的？如果新字符串的值小于等于 23 个字符，Ruby 会创建一个嵌入式字符串。尽管这不会像共享型字符串那么快，但还是蛮快的，因为 23 个字符被简单地复制到了 RString 结构体内，并且不需要调用 &lt;em&gt;malloc&lt;/em&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;最后，来解决长字符串，24 个字符以上的那些，Ruby 创建堆字符串：调用 &lt;em&gt;malloc&lt;/em&gt; 从堆中获取新的内存空间，将字符串值复制进去。这是最慢的一种。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="真实的RSting结构体"&gt;真实的 RSting 结构体&lt;/h2&gt;
&lt;p&gt;Ruby 1.9 中 RString 的真实定义如下：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RString&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RBasic&lt;/span&gt; &lt;span class="n"&gt;basic&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;union&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;union&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;capa&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;shared&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;aux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;char&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;RSTRING_EMBED_LEN_MAX&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;as&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;ul&gt;
&lt;li&gt;&lt;p&gt;RBasic 结构体跟踪关于这个字符串的许多重要信息位，像区分是共享型还是嵌入式的标识，还有一个指针指向 Ruby 中 String 对象的结构体。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;capa 值维护每个堆字符串的容量 (capacity)...原来 Ruby 通常为每个堆字符串分配比其需求 (字符串数据长度) 更大的内存空间，以避免在字符串大小发生变化时额外的 &lt;em&gt;malloc&lt;/em&gt; 调用。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;使用联合体 Ruby 既可以使用 len ptr 和 capa/shared，也可以使用数组来保存字符串。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RSTRING_EMBED_LEN_MAX 被用来与 len/ptr/capa 的比较。这也是题目中 23 个字符串的由来。下面的代码摘自 ruby.h 定义了这个值。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define RSTRING_EMBED_LEN_MAX ((int)((sizeof(VALUE)*3)/sizeof(char)-1))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 64 位的机器上 sizeof(VALUE) 是 8，于是 23 就成为了限制。32 位机器上会更小一点。&lt;/p&gt;
&lt;h2 id="Ruby 分配字符串基准测试"&gt;Ruby 分配字符串基准测试&lt;/h2&gt;
&lt;p&gt;来度量一下在 Ruby 1.9.3 里短字符串比长字符串快多少吧，下面一行简单的代码通过向字符串尾部追加的方式构建了一个新字符串：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;new_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'x'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;new_string 的值将是堆字符串或是嵌入式字符串。这取决于 str 有多长。我使用字符串连接操作符（+ 'x'）是为了迫使 Ruby 动态分配一个新的字符串。如果只是用 new_string = str，我们会得到一个共享式字符串。&lt;/p&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="s1"&gt;'benchmark'&lt;/span&gt;

&lt;span class="no"&gt;ITERATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; chars"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;ITERATIONS&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="n"&gt;new_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'x'&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;这里我使用 benchmark 库来度量此方法一百万次的执行时间。现在，用不同长度的字符串来跑一下。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bm&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;bench&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"12345678901234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"123456789012345678901"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1234567890123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"12345678901234567890123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"123456789012345678901234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"1234567890123456789012345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"12345678901234567890123456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bench&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 plaintext"&gt;&lt;code&gt;    user     system      total        real
21 chars  0.250000   0.000000   0.250000 (  0.247459)
22 chars  0.250000   0.000000   0.250000 (  0.246954)
23 chars  0.250000   0.000000   0.250000 (  0.248440)
24 chars  0.480000   0.000000   0.480000 (  0.478391)
25 chars  0.480000   0.000000   0.480000 (  0.479662)
26 chars  0.480000   0.000000   0.480000 (  0.481211)
27 chars  0.490000   0.000000   0.490000 (  0.490404)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意当字符串小于等于 23 个字符的时候，建一百万字符串差不多花了 250 毫秒。但是当我们的字符串长度大于等于 24 时，花了大约 480 毫秒，几乎两倍长。&lt;/p&gt;

&lt;p&gt;下面的柱形图来展示了更多数据，柱形展示课给定长度的一百万个字符串的创建用时。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://patshaughnessy.net/assets/2012/1/4/string-allocations.png" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;别担心！我不认为你应该重构所有的代码确保字符串长度小于 24。这显然很荒谬。速度看起来是提升了，但是实际上即使我构建了一百个字符串，时间差距也微乎其微。试问有多少 Ruby 应用需要创建如此之多的字符串值？即使需要创建很多字符串对象，因为只能使用短字符串带来的痛苦和疑惑会抵消所有性能提升所带来的福利。&lt;/p&gt;

&lt;p&gt;于我而言，真正理解 Ruby 解释器的工作方式充满乐趣！我乐于透过显微镜来观察这类细枝末节。我确实也怀疑：通过对 Matz（及其同事）实现语言方法的理解来提升自己的 Ruby 水平，是不是一条明智和通达的。拭目以待吧，会有更多关于 Ruby 内部实现的帖子的！&lt;/p&gt;</description>
      <author>wadexing</author>
      <pubDate>Wed, 18 Nov 2015 17:14:44 +0800</pubDate>
      <link>https://ruby-china.org/topics/28104</link>
      <guid>https://ruby-china.org/topics/28104</guid>
    </item>
  </channel>
</rss>
