<?xml version="1.0" encoding="gb2312"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>云风的 BLOG</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/" />
    <link rel="self" type="application/atom+xml" href="http://blog.codingnow.com/atom.xml" />
   <id>tag:blog.codingnow.com,2012://1</id>
    <link rel="service.post" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1" title="云风的 BLOG" />
    <updated>2012-02-03T02:36:49Z</updated>
    <subtitle>思绪来得快去得也快，偶尔会在这里停留</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 3.2b5</generator>
 
<entry>
    <title>Ring Buffer 的应用</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/02/ring_buffer.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=745" title="Ring Buffer 的应用" />
    <id>tag:blog.codingnow.com,2012://1.745</id>
    
    <published>2012-02-02T14:20:19Z</published>
    <updated>2012-02-03T02:36:49Z</updated>
    
    <summary>这是一篇命题作文，源于今天在微薄上的一系列讨（好吧，也可以说是吵架）。其实方案没有太多好坏，就看你信不信这样做能好一些或坏一些。那么，整理成 blog 写出，也就是供大家开拓思路了。 我理解的需求来源于网络服务提供程序的一个普遍场景：一个服务器程序可能会收到多个客户端的网络数据流，在每个数据流上实际上有多个独立的数据包，只有一个数据包接收完整了才能做进一步的处理。如果在一个网络连接上数据包并不完整，就需要暂时缓存住尚未接收完的数据包。 问题是：如何管理这些缓冲区比较简洁明了，且性能高效。 其实这个有许多解决方案，比如为每个网络连接开一个单独的固定长度的 buffer 。或是用 memory pool 等改善内存使用率以及动态内存分配释放，等等。今天在微薄上吵架也正是在于这些方案细节上，到底好与不好，性能到底如何。既然单开一篇 blog 了，我不像再谈任何有争议的细节，仅仅说说，用 Ring Buffer 如何解决这个问题。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="网络与安全" />
            <category term="优化与技巧" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>这是一篇命题作文，源于今天在微薄上的一系列讨（好吧，也可以说是吵架）。其实方案没有太多好坏，就看你信不信这样做能好一些或坏一些。那么，整理成 blog 写出，也就是供大家开拓思路了。</p>

<p>我理解的需求来源于网络服务提供程序的一个普遍场景：一个服务器程序可能会收到多个客户端的网络数据流，在每个数据流上实际上有多个独立的数据包，只有一个数据包接收完整了才能做进一步的处理。如果在一个网络连接上数据包并不完整，就需要暂时缓存住尚未接收完的数据包。</p>

<p>问题是：如何管理这些缓冲区比较简洁明了，且性能高效。</p>

<p>其实这个有许多解决方案，比如为每个网络连接开一个单独的固定长度的 buffer 。或是用 memory pool 等改善内存使用率以及动态内存分配释放，等等。今天在微薄上吵架也正是在于这些方案细节上，到底好与不好，性能到底如何。既然单开一篇 blog 了，我不像再谈任何有争议的细节，仅仅说说，用 Ring Buffer 如何解决这个问题。</p>
]]>
        <![CDATA[<hr />

<p>具体点说，我倾向于用 C 语言来做这种偏底层、业务简单的模块。为了减低工作量，可以使用一些成熟的库，比如 libev 等。</p>

<p>类似的库多半提供的是一种回调机制的框架，设置好对应的 IO 异步请求的 callback 函数，然后启动框架的主循环，每个 socket （或别的句柄）可读写时，回调注册好的函数。</p>

<p>把这件事情做的干净漂亮的关键点之一在于数据缓冲区的管理。</p>

<p>拿到需求后，我们应该适当估算我们的程序需要解决多大的数据吞吐量。比如，我们可以假设，一个逻辑完整的数据包在 TCP 连接上，可能最长大约会经过一两秒时间，通过 1 到 10 个包发送过来。整个系统每秒会处理 100M 字节（千兆网）的数据流，那么大约在 10 秒内，处理的数据量大约就在 1G 。</p>

<p>根据对实际业务的估算，这个值可能不到 1G ，128M 就够，也可能多达几个 G 。没关系。我们只是估算出，大致在这个范围内，一个独立的逻辑包一定存在于整个数据流的截段中。我指的数据流是服务程序从网卡上读到的所有数据。</p>

<p>就以 1G 为例，那么这个服务程序只需要开单个这么大的 buffer 就足够了，不必再有任何的动态内存管理。</p>

<p>我们把所有的数据，不论它来至哪个 TCP 连接，都以循环队列的方式，无差别的循序置入这个 buffer。放置的时候，以每次 IO 可读时可以读入的最大字节长度为限。一旦放不下，就折返到 buffer 头部。</p>

<p>buffer 里大概的数据结构是这样的：</p>

<p>[数据长度 连接号 下一块的位置 数据] [数据长度 连接号 下一块的位置 数据] [数据长度 连接号 下一块的位置 数据] ...</p>

<p>另外，内存里开一张 hash 表，记下连接号到数据块的映射关系。如果不想用 hash 表的话，也可以在 buffer 中直接记下连接对象在内存中的地址。</p>

<p>每当一个连接可读的时候，无论读到多少字节，都向这个 buffer 后面追加。并且用链表将其和历史上曾经读过的数据连起来。</p>

<p>同时，可以分析一个逻辑包是否完成。如果没有完成，则继续下面的工作。完成了的话，则利用已有的链表，将分离的数据块拼合在一块连续的内存上。之后，如何处理这个逻辑包，就不在是这个层次上的工作了。</p>

<p>对于处理掉的数据块，可以做一个记号表示废弃即可。我的做法是对数据长度段取反，这样 buffer 在循环使用时，可以判断出下面的内存空间是否可以安全使用。</p>

<p>处理完一个逻辑包后，有可能最后一块数据被切分出去。我的做法时调整这个块，前半块标记为废弃数据块，后半块为待处理数据块。</p>

<p>理论上，如果你的估算没有错且留有余量的话，每次新到来的数据包都能在 buffer 中找到储存它们的空间。因为根据估算，消费速度是要大于生产速度的。不然整个系统都跑不下去。</p>

<p>但如果碰到例外怎么办? 比如有个客户端半个逻辑包发来以后，迟迟不发下半个包。最简单的做法是，碰到 ring buffer 回卷后，碰到那些未废弃的数据块（尚未处理掉），索引到对应的连接，直接 close 掉连接，把没有处理的数据扔掉即可。因为在互联网上，连接本来就是不稳定的。你的协议原本就要处理主动断开连接的情况。无非是根据 ring buffer 的大小和当时的负载情况，设置了一个超时而已。</p>

<p>有兴趣的同学，可以用这个思路实现一下几年前我提到的<a href="http://blog.codingnow.com/2006/04/iocp_kqueue_epoll.html">连接服务器</a> 。代码量应该不大。</p>

<hr />

<p>另，在更高层的应用上，同样可以使用类似的策略。即循环使用一个 ring buffer 。当 buffer 回转时碰到有对象占用 buffer 拦路时，杀掉对象。对于一些对象比较复杂占用的数据段不固定，对象生命期很短的应用，ring buffer 都有参考价值。例如 3d engine 中的粒子系统。对于要个别需要长期生存的对象，还可以定期复制自己，重新压入 ring buffer 的方式来延长生命期。</p>

<hr />

<p>使用 ring buffer 的优势是内存使用率很高，不会造成内存碎片，几乎没有浪费（比如传统动态内存分配需要的 cookie）。业务处理的同一时间，访问的内存数据段集中。可以更好的适应不同系统，取得较高的性能。内存的物理布局简单单一，不太容易发生内存越界、悬空指针等 bug ，出了问题也容易在内存级别分析调试。做出来的系统容易保持健壮。</p>

<hr />

<p>2 月 3 日 补充:</p>

<p>读了几位同学的反馈，发现在几个地方没讲清楚，造成许多疑惑。</p>

<p>一、从 ring buffer 里切分出去的数据块是可以任意合并或再切分的。数据块头上的数据块长度正是为了找到下一块的位置，可以把邻接的两个被标记（废弃）的块合成一个；为了可以任意切分，这里数值上制定的长度和实际占用的长度可能略微不等。实际占用的长度是 4 字节对齐的，而记录的长度则是真实值。比如，记录长度为 6 ，那么在推算下一块位置的时候，需要选原整到 8 。这样做的话，可以让数据块长度至少为 4 。我们用 4 字节记录长度信息的话，就足够了。更细节的位置还有，我们需要利用连接号域做一个标记，比如空出一个不存在的连接号，表示这个块废弃且无关联连接。实际上，ring buffer 初始化时，就是把整个空间初始化为一个块，并打上可用（废弃）标记。使用的时候就从这个块中一分为二，取其一而用。</p>

<p>二、ring buffer 中切分出去的块的分配过程是顺序依次进行的，回收的次序则是随机的。初看起来这会导致使用一段时间后，buffer 里有很多空洞。但是， ring buffer 不是一个 memory pool ，并不做复杂的内存块管理工作。如果你想那样做的话，不如直接使用 malloc/free 。也不用为预测的数据量留大一个数量级的空间。ring buffer 在使用时，留足了空间和时间，当 buffer 回转，头指针继续从低地址位移动时，那些先前分配出去的数据块应该都被打上废弃标记了。我们要做的只是根据需要，把这些连续废弃块合成我们需要的程度。万一碰到中间有个别继续被占用的数据块怎么办？那就是上文中提到的，根据连接号强行回收的工作了。</p>
]]>
    </content>
</entry>
<entry>
    <title>libuv 初窥</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/libuv.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=744" title="libuv 初窥" />
    <id>tag:blog.codingnow.com,2012://1.744</id>
    
    <published>2012-01-20T15:18:48Z</published>
    <updated>2012-01-20T18:06:23Z</updated>
    
    <summary>过年了，人都走光了，结果一个人活也干不了。所以我便想找点东西玩玩。 今天想试一下 libev 写点代码。原本在我那台 ubuntu 机器上一点问题都没有，可在 windows 机上用 mingw 编译出来的库一个 backend 都没有，基本不可用。然后网上就有同学推荐我试一下 libuv 。 libuv 是 node.js 作者做的一个封装库，在 unix 环境整合的 libev ，而在 windows 下用 IOCP 另实现了一套。看起来挺满足我的玩儿的需求的。所以就试了一下。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="网络与安全" />
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>过年了，人都走光了，结果一个人活也干不了。所以我便想找点东西玩玩。</p>

<p>今天想试一下 libev 写点代码。原本在我那台 ubuntu 机器上一点问题都没有，可在 windows 机上用 mingw 编译出来的库一个 backend 都没有，基本不可用。然后网上就有同学推荐我试一下 libuv 。</p>

<p>libuv 是 node.js 作者做的一个封装库，在 unix 环境整合的 libev ，而在 windows 下用 IOCP 另实现了一套。看起来挺满足我的玩儿的需求的。所以就试了一下。</p>
]]>
        <![CDATA[<p>这东西没有文档，暂时没看出来作者有写文档的打算，恐怕他是自己用为主。我 google 了一下，就是 github 上有<a href="https://github.com/joyent/libuv">源代码</a>，.h 文件里有还算比较详细的注释。当然最主要是有些 test 程序，可以大概浏览其设计思路。</p>

<p>编译倒挺顺利，照着 test 写点小东西也不复杂。所以我也就逐步开始了解这个东东了。</p>

<p>老实说，对于一个别人写的库，我爱用不爱用主要是考察其 API 设计。也就是该怎么用，设计的好不好，有没有冗余设计。文档什么的其实不太所谓，反正有代码可以看嘛。</p>

<p>libuv 大体上我还算满意，用 C 实现可以加很多分。不过有些小细节我觉得还是有点小遗憾的。(这个遗憾是指，如果我自己来设计，绝对不会像这个样子）</p>

<p>最关键就是接口对 C 结构体布局的依赖性。这点我曾经因为 hiredis windows 版的缘故<a href="http://blog.codingnow.com/2011/11/dev_note_2.html">吐槽过一次</a>了。以我自己的经验，似乎大多数 Windows 出身的程序员都有一点这种坏习惯。好吧，我也不知道怎么把这点和 windows 联系起来的，纯粹感觉而已。因为我自己以前做设计也有这个习惯。</p>

<p>为什么我觉得这样不好？</p>

<p>因为我觉得一个库，若想被人当成黑盒子去使用，以后也作用黑盒子来维护，甚至可以用别的盒子去替代它。关键的一点就是接口简单。这个简单包括了使用最少的概念、需要最少的知识去理解它。</p>

<p>文档通常是对接口无法自描述所需知识的一种补充。对一些例外的说明。而这些当然是越少越好。</p>

<p>我倾向于不在对外接口（对于 C 库来说，就是 .h 文件）中定义所用数据结构的具体布局。通常只需要一个名字即可。这个名字是用来做强类型约束的。</p>

<p>过多的结构定义导致了过多的概念，增加了接口复杂度。</p>

<p>接口的最小知识表达就是用一致的 C 函数调用约定。有明确的输入参数、输出参数。对于接口函数，应该是无全局相关状态的。这不仅仅是为了线程安全，而是可以保证没有隐式约定（额外的知识）。</p>

<p>如果某些行为需要用户设置或读取某个结构体的一个特定域，我觉得就是在 C 函数调用这一方式外，增加了一种改变模块行为的接口形式。或许这样做的成本比 C 函数调用要来的低，但我以为得不偿失。</p>

<p>尤其是、你的模块如果相当依赖这种形式：直接对结构体的特定域赋值的形式来交换信息。这种依赖可能来至于你对性能的追求。那我觉得一般都是整个模块的需求定义出了什么问题。一个独立模块需要解决的问题，通常对外界的信息交换应该是低频的，它应该是可以独立工作解决更复杂的问题的。而不应该是不断的要求外部告知它新的状态变换。</p>

<p>ps. 对于接口中的结构体定义问题。有另一种情况需要区分开。就是有大量的输入参数或输出参数需要一次性交换时，可以考虑定义一个结构体来做。这样比在 C 函数调用前压一大堆的数据去堆栈里要干净的多。</p>

<hr />

<p>写了这么多，我是想说说我初步阅读 libuv 代码的感受。我碰到的第一个问题就是：libuv 用了大量 callback 机制来完成异步 IO 的问题。而这些 callback 函数通常都带有一个参数 <code>uv_stream_t</code> 或 <code>uv_req_t</code> 等。这个数据表示这次 callback 绑定的数据 。</p>

<p>我们知道， C 语言是没有原生 closure 支持的。若有的话，closure 应是 callback 机制最价解决方案。而 C 语言模拟 closure 的方法是用一个 C Function 并携带一个 void * ud 。此 ud 即原本应该在 closure 中绑定的数据块。</p>

<p>这里，libuv 用的  <code>uv_stream_t</code> 大致上等同于这个 ud 。</p>

<p>问题出来了。用户在用这类异步 IO 库的时候，每次 IO 事件都需要绑定的行为需要的数据不仅仅是一个 stream 。还需要一些围绕这个 stream 做的动作所需要的一些其它数据。</p>

<p>我在阅读 <code>test/echo-server.c</code> 时看到这么一段：</p>

<pre class="mtc_block"><span class="c_KeywordANSI def_Keyword">static</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_FuncOutline def_Outlined def_Special">after_write</span><span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>uv_write_t<span class="c_Symbol def_Symbol">*</span> req<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">int</span> status<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span> <span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
  write_req_t<span class="c_Symbol def_Symbol">*</span> wr<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

  <span class="c_KeywordANSI def_Keyword">if</span> <span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>status<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span> <span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
    uv_err_t err <span class="c_Symbol def_Symbol">=</span> uv_last_error<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>loop<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    fprintf<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordStdio c_KeywordStructure def_StructKeyword def_Keyword">stderr</span><span class="c_Symbol def_Symbol">,</span> <span class="c_String def_String">&quot;uv_write error: <span class="c_StringEscape def_StringContent def_String">%s</span><span class="c_StringEscape def_StringContent def_String">\n</span>&quot;</span><span class="c_Symbol def_Symbol">,</span> uv_strerror<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>err<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    ASSERT<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="def_NumberDec def_Number">0</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  <span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span>

  wr <span class="c_Symbol def_Symbol">=</span> <span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>write_req_t<span class="c_Symbol def_Symbol">*</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span> req<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> Free the read/write buffer and the request <span class="def_PairEnd def_Special">*/</span></span>
  free<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>wr<span class="c_Symbol def_Symbol">-</span><span class="c_Symbol def_Symbol">&gt;</span>buf<span class="c_Symbol def_Symbol">.</span>base<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  free<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>wr<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span></pre>

<p>这里用了一次强制转换，把 <code>uv_write_t</code> 转换为 <code>write_req_t</code> 。为什么可以这样干，是因为 <code>write_req_t</code> 被定义成：</p>

<pre class="mtc_block"><span class="c_KeywordANSI def_Keyword">typedef</span> <span class="c_KeywordANSI def_Keyword">struct</span> <span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
  uv_write_t req<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  uv_buf_t buf<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span> write_req_t<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span></pre>

<p>这里根据 C 结构布局，req 是第一个域，所以排在最前面。</p>

<p>这样做有点晦涩，我只能说感觉不太好。因为如果约定了 <code>uv_write</code> 接口传递的是一个 <code>uv_write_t</code> 类型的数据，这就明显是利用 C 语言特性来夹带私货了。</p>

<p>如果这是作者推荐的惯用法的话，我则这样理解：</p>

<p>libuv 其实在 API 上有个隐含约定。即回调函数的参数指向的地址偏移量为某个数值以后的数据是用户数据。这个数值为类型的尺寸。这类似 c++ 的继承。数据类型尺寸数值是编译时通过编译器来约定的。</p>

<p>而且，单就现在的用法，我认为更严谨的做法应该是类似 socket API ，显式的把传递的结构尺寸在函数接口表达出来（参考 socket connect 的接口定义中的第三个参数 addrlen）。 这样对库的接口稳定有好处。库可以知道用户有可能扩展数据，长度信息提示了库，传入数据体的真实大小。</p>

<p>btw, C++ 在用继承来完成类似设计时，则依靠了语言对 cast 的约定。C++ 语言的知识概念太多，很难完成简洁的模块接口约定。在我看来，这直接导致了 C++ 很难设计通用库，而只能设计专有框架。</p>

<hr />

<p>我着一些疑惑阅读了不少 libuv 里的实现代码，尤其是 uv.h 的细节。我发现这样一个结构定义。</p>

<pre class="mtc_block"><span class="c_Preproc def_Directive">#<span class="c_PreprocWord def_DirectiveContent def_Directive">define</span> <span class="c_DefineOutline def_Outlined def_Special">UV_HANDLE_FIELDS</span> \
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> read-only <span class="def_PairEnd def_Special">*/</span></span> \
  uv_loop_t<span class="c_Symbol def_Symbol">*</span> loop<span class="c_Symbol def_Symbol">;</span> \
  uv_handle_type type<span class="c_Symbol def_Symbol">;</span> \
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> public <span class="def_PairEnd def_Special">*/</span></span> \
  uv_close_cb close_cb<span class="c_Symbol def_Symbol">;</span> \
  void<span class="c_Symbol def_Symbol">*</span> data<span class="c_Symbol def_Symbol">;</span> \
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> private <span class="def_PairEnd def_Special">*/</span></span> \
  UV_HANDLE_PRIVATE_FIELDS</span>

<span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> The abstract base class of all handles.  <span class="def_PairEnd def_Special">*/</span></span>
<span class="c_KeywordANSI def_Keyword">struct</span> <span class="c_StructOutline def_Outlined def_Special">uv_handle_s </span><span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
  UV_HANDLE_FIELDS
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span></pre>

<p>注意这里有一个 data 域。从我的经验判断，这个域应该就是用来在一个 handle 上夹带用户数据的。由于没有文档确认，我只能从有限的代码阅读中来确认我的判断。我很奇怪没有定义一个明确的 api 出来绑定用户数据。因为在库的实现代码中也确实库自己用到过这个域，所以估计也不是库的使用者可以自由使用的。</p>

<p>当然对应的还有几处类似设计：</p>

<pre class="mtc_block"><span class="c_Preproc def_Directive">#<span class="c_PreprocWord def_DirectiveContent def_Directive">define</span> <span class="c_DefineOutline def_Outlined def_Special">UV_REQ_FIELDS</span> \
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> read-only <span class="def_PairEnd def_Special">*/</span></span> \
  uv_req_type type<span class="c_Symbol def_Symbol">;</span> \
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> public <span class="def_PairEnd def_Special">*/</span></span> \
  void<span class="c_Symbol def_Symbol">*</span> data<span class="c_Symbol def_Symbol">;</span> \
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> private <span class="def_PairEnd def_Special">*/</span></span> \
  UV_REQ_PRIVATE_FIELDS</span>

<span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> Abstract base class of all requests. <span class="def_PairEnd def_Special">*/</span></span>
<span class="c_KeywordANSI def_Keyword">struct</span> <span class="c_StructOutline def_Outlined def_Special">uv_req_s </span><span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
  UV_REQ_FIELDS
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span></pre>

<p>还有</p>

<pre class="mtc_block"><span class="c_KeywordANSI def_Keyword">struct</span> <span class="c_StructOutline def_Outlined def_Special">uv_loop_s </span><span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
  UV_LOOP_PRIVATE_FIELDS
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> list used for ares task handles <span class="def_PairEnd def_Special">*/</span></span>
  uv_ares_task_t<span class="c_Symbol def_Symbol">*</span> uv_ares_handles_<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> Various thing for libeio. <span class="def_PairEnd def_Special">*/</span></span>
  uv_async_t uv_eio_want_poll_notifier<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  uv_async_t uv_eio_done_poll_notifier<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  uv_idle_t uv_eio_poller<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> Diagnostic counters <span class="def_PairEnd def_Special">*/</span></span>
  uv_counters_t counters<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> The last error <span class="def_PairEnd def_Special">*/</span></span>
  uv_err_t last_err<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  <span class="c_Comment def_Comment def_Syntax"><span class="def_PairStart def_Special">/*</span> User data - use this for whatever. <span class="def_PairEnd def_Special">*/</span></span>
  <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span><span class="c_Symbol def_Symbol">*</span> data<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span></pre>

<p>这个 <code>struct uv_loop_s</code> 的 data 域倒是明确的注释可以随便使用了。</p>

<hr />

<p>话说回来，这个绑定用户数据的需求我在早年阅读 Windows 的 MFC 实现时倒是见过另外一种解决方案。</p>

<p>Windows 的窗体有一个 SetWindowLong 的 API 可以让用户去设置一个用户数据。这样可以方便用户在用 C++ 封装的时候把一个 C++ 对象指针绑定在窗体 Handle 上。这样在窗口消息回调函数中就可以取回这个对象指针。</p>

<p>MFC 封装这些系统 API 时，可能是为了更通用，没有占用这个内置域，而是自己建立了一个全局的映射表。每次窗体消息回调时，查表来找到对应的窗体对象。这种非侵入式的方案，也凑合用吧。就是对于用 C/C++ 编写代码的追求性能的同学来说，或许有些小小不爽。</p>

<hr />

<p>这就是我初步阅读 libuv 代码的一些简单看法。当然，我觉得 libuv 是个很不错的东西，不然我也不会饶有兴致的玩了一晚上。只是由于在这块投入时间精力不多，错误难免。有行家看到，一笑了之吧。</p>
]]>
    </content>
</entry>
<entry>
    <title>一个链接 lua 引起的 bug , 事不过三</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/lua_link_bug.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=743" title="一个链接 lua 引起的 bug , 事不过三" />
    <id>tag:blog.codingnow.com,2012://1.743</id>
    
    <published>2012-01-19T10:42:09Z</published>
    <updated>2012-01-19T11:17:21Z</updated>
    
    <summary>今天花了将近 3 个小时帮同事看一个崩在 lua VM 中的 bug 结果打乱了进度，没有在年前把预想的东西做完。其实说起来这不是个大问题，以前也碰到过。我检讨自己没有在看到出错时的调用栈时去看一眼 lua 相关的代码。如果是那样，因为以前遇到过同样的问题，所以就可以条件反射出问题原因，而不用荒废宝贵了数小时时间了。 唉，这下整合的进度没接上，过年不能自己一个人接着做下面的活了。 下面记录一下这个 bug ，提醒自己第三次遇到时不用再花时间找问题：...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天花了将近 3 个小时帮同事看一个崩在 lua VM 中的 bug 结果打乱了进度，没有在年前把预想的东西做完。其实说起来这不是个大问题，以前也碰到过。我检讨自己没有在看到出错时的调用栈时去看一眼 lua 相关的代码。如果是那样，因为以前遇到过同样的问题，所以就可以条件反射出问题原因，而不用荒废宝贵了数小时时间了。</p>

<p>唉，这下整合的进度没接上，过年不能自己一个人接着做下面的活了。</p>

<p>下面记录一下这个 bug ，提醒自己第三次遇到时不用再花时间找问题：</p>
]]>
        <![CDATA[<p>问题的缘由是因为的不同的 lua 扩展库链接的时候多链接了一份 lua 库，导致进程内有超过一份 lua 库。这是在写 Makefile 时不小心造成的。知道原因后自然很容易解决。要么把 lua 编译成 .so ；要么只有嵌入的主框架使用 -E 参数静态链接 lua 的静态库。这里就不展开说了。</p>

<p>用了 lua 十年，绝对不是第一次帮其他用 lua 的同学指出这个问题。下面要讨论的是错误产生后，程序是怎么崩溃的。</p>

<p>lua 的代码中几乎没有使用全局变量。就是说，lua 的 api 的相关状态仅存在于参数 L 中，而和库无关。这点 lua 实现的是很漂亮的。对于一个无外部状态的 api ，理论上无论代码段在进程内被链接再多次，都不会有副作用的。但副作用的确又发生了，为何？</p>

<p>问题出在 ltable.c 中引用了一个静态全局变量 <code>dummynode_</code> 。这个东西是做什么用的呢？</p>

<p>在 lua table 实现中，为了不让 hash part 为空的时候不引用 NULL 指针（一种优化），而引用了这个 dummynode ，这样就可以减少操作表时的判断操作。</p>

<p>由于这个 dummynode 是静态分配出来的特殊节点，所以是不能调用内存管理函数去释放它的。lua 另外用一个宏来判断一个节点是否为 dummynode 。</p>

<pre>
#define isdummy(n)      ((n) == dummynode)
</pre>

<p>当进程中链接了多份 lua 库时，就出现了多份 dummynode 对象，自然 isdummy 的行为就可能不正确了。那么接下来的释放工作则会崩掉，看起来段错误会发生在 <code>lua_Alloc</code> 中。</p>

<p>问题多出现在运行过程中，向空 table 插入第一个 hash 值时，也就是 <code>luaH_resize</code> 函数的最后一行：</p>

<pre>
 if (!isdummy(nold))
    luaM_freearray(L, nold, cast(size_t, twoto(oldhsize))); /* free old array */
</pre>

<p>或是关闭 lua state 时，释放 table 时，的 <code>luaH_free</code> 函数最前面：</p>

<pre>
  if (!isdummy(t->node))
    luaM_freearray(L, t->node, cast(size_t, sizenode(t)));
</pre>

<p>之所以我把代码和出错位置列出来，是因为我今天不是第一次专门分析这个点了。如果我下午在刚出问题时，能够在看到 stack traceback 信息的函数名)<code>luaH_resize</code> 打开 lua 的源代码看一眼的话，我相信我能立刻回想起上次分析这个问题的情景，从而节省掉后来浪费在查别的地方上的无尽时间。</p>

<p>希望经过这次，下次有人向我反应类似问题时，我能直接对应到问题。</p>
]]>
    </content>
</entry>
<entry>
    <title>开发笔记 (9) ：近期工作小结</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/dev_note_9.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=742" title="开发笔记 (9) ：近期工作小结" />
    <id>tag:blog.codingnow.com,2012://1.742</id>
    
    <published>2012-01-18T16:49:09Z</published>
    <updated>2012-01-18T16:51:50Z</updated>
    
    <summary>最近主要就是一些琐碎的事儿，基本上正事没啥进展。 那个模拟网络环境框架的 skynet 早在一周半前的周末就完成了。一直在等蜗牛的真框架架好。看起来那个昨天也弄好了，但是需要细节磨合需要两天，希望在年前能整合好客户端和服务器框架，过个好年。 我觉得理想情况下，这部分进度能快上几天。但我也觉得目前的进度可以接受。集成这些个不同人写的东西本来就是个麻烦事儿。怪物公司的客户端的网络部分也因为方案调整花了好几天事情修改。 我的想法是尽量隔绝客户端和服务器的具体设计方案。在知识点上不要紧密耦合。就是说只交代很少的一点通讯基本协议，然后由不同的人分别设计代码的架子，看最后再怎么合起来。这样可以保证各自的独立性。适应将来的分工和扩展。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="杂记" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近主要就是一些琐碎的事儿，基本上正事没啥进展。</p>

<p>那个模拟网络环境框架的 skynet 早在一周半前的周末就完成了。一直在等蜗牛的真框架架好。看起来那个昨天也弄好了，但是需要细节磨合需要两天，希望在年前能整合好客户端和服务器框架，过个好年。</p>

<p>我觉得理想情况下，这部分进度能快上几天。但我也觉得目前的进度可以接受。集成这些个不同人写的东西本来就是个麻烦事儿。怪物公司的客户端的网络部分也因为方案调整花了好几天事情修改。</p>

<p>我的想法是尽量隔绝客户端和服务器的具体设计方案。在知识点上不要紧密耦合。就是说只交代很少的一点通讯基本协议，然后由不同的人分别设计代码的架子，看最后再怎么合起来。这样可以保证各自的独立性。适应将来的分工和扩展。</p>
]]>
        <![CDATA[<p>毕竟，自己设计自己实现是最保险不太出问题的做法。尤其对于已经有多年开发经验的程序员来说更是如此。我给自己的定位就是尽量让每个人写的东西可以不出太多问题的粘在一起。</p>

<p>客户端那边基本可以操控一个 3d 角色在场景中走跑跳了，以 wow 为标杆的话，可以说操作手感几乎和 wow 无二。这算是完成了我们第一阶段的目标。我知道做到这点其实不算容易，小 V 同学花了不少精力。每天我都看到他是 22 点以后才离开办公室的。晚上都在调动画控制、镜头控制方面的程序。</p>

<p>前几天为策划设计的 DSL 经过这周的应用加了诸多需求，我的代码改了又改，发觉我终于成功的把函数式编程的理念部分灌输给了没写过程序的策划。并且他表示写的很爽，可以在我做的沙盒里测试战斗结果了。我觉得这也算是把战斗逻辑独立开的一次小成功吧。性能方面的问题等后期或许还要再考虑一下。当然，设计 DSL 的目的之一也是为了日后性能优化留下空间。毕竟如果是一个通用语言，恐怕我只能求助于类似 luajit 这样的东西了。</p>

<p>服务器逻辑方面，我希望年后把前面想的诸多部分顺利整合起来。还是自己先做原型，探一下水的深浅。大约就是采用一开始想采用的 agent 模式。把对单个 client 服务的代码集中写在 agent 服务中。由 agent 再和内部其它服务沟通。数据加载使用共享内存的方案，由 agent 向持久化模块发出信号，做加载或纯盘处理，通过共享内存得到结构化数据块。</p>

<p>逻辑处理流程中的共享状态问题，采用简单的生产消费模式。修改每个玩家数据的操作全部放在 agent 中，而其它服务，尤其是场景服务通过只读的方式消费这些（共享内存中的）数据。年后，（或者有空的话，在过年期间）花大约 10 个小时写出一个粗略的游戏场景服务器看看可能会碰到哪些当初没想到的问题。并把交给策划去写的战斗公式模块整合进来。当然，这部分的设计还需要理出一份详细的文档再开始干活。</p>

<hr />

<p>今天去岩馆攀岩累了个半死，手都爬软了。就剩下脑子还算比较清醒。精神亢奋，正好花点时间码点字做个工作记录。</p>
]]>
    </content>
</entry>
<entry>
    <title>今天终于爬先锋了</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/niioeoouaieeaee.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=741" title="今天终于爬先锋了" />
    <id>tag:blog.codingnow.com,2012://1.741</id>
    
    <published>2012-01-18T15:31:29Z</published>
    <updated>2012-01-18T15:52:53Z</updated>
    
    <summary>说来很惭愧，接触攀岩运动有 7,8 年了。一直没好好玩。话说线也开过了，结组爬长线路也试过了。就是一直不敢爬先锋。 估计是被人教育的，说先锋这要注意，那要小心等等。说着说着，想着想着，心里就越来越畏惧。 ps. 写给不懂攀岩的同学：...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="攀岩" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>说来很惭愧，接触攀岩运动有 7,8 年了。一直没好好玩。话说<a href="http://blog.codingnow.com/2011/08/kexiao1.html">线也开过</a>了，<a href="http://blog.codingnow.com/2011/07/tianzhuyan-1.html">结组爬长线路</a>也试过了。就是一直不敢爬先锋。</p>

<p>估计是被人教育的，说先锋这要注意，那要小心等等。说着说着，想着想着，心里就越来越畏惧。</p>

<p>ps. 写给不懂攀岩的同学：</p>
]]>
        <![CDATA[<p>一般运动攀岩，初学的时候，都会用一跟绳子吊在顶上，你攀爬的时候，有一个同伴在下面拉着绳子不断收紧。一旦你攀爬脱手，人会有绳子吊着，所以非常安全。一般说来，攀岩运动比骑自行车都要安全很多。可以算是挂着极限运动名号的最安全的游戏了。</p>

<p>但一旦你出去野外攀爬，就不大可能山顶有绳子垂下来了。许多不懂的同学总是问我，你们是不是每次要先绕到山背后去把绳子挂好的？</p>

<p>我说，其实是边爬边把绳子挂上去的。许多同学表示不理解。他们老把一些 free solo （不做保护的自由攀爬）的视频和先锋攀登在想像中混为一谈。其实那些玩法，玩的人极少，而且也是看似危险，其实也不然。当然我这辈子都不会有 free solo 的打算的。</p>

<p>先锋攀登简单说就是在爬到 2 米以上后，就用快挂（一种锁具）挂在事先用膨胀螺栓在岩壁上打好的挂片中。然后再把绑在腰间的绳子挂在快挂的环里。然后每向上爬几米，就不断的挂快挂、挂绳子到快挂中。</p>

<p>下面做保护的同学一点点放松绳子，让攀爬的同学一点点把绳子拖上去，直到顶端。</p>

<p>中途任何一个位置脱手的话，人有可能做几米的自由落体，但一系列的快挂会挂住绳子，不会让你直接落到地面。总的来说，还是非常安全的。当然前提是，攀爬者和保护者要有长期默契，保护人也有许多技术要掌握，需要一些经验。在发生冲坠时，危险的不只是攀爬的人。下坠的冲力有可能把下面的人带起来撞到石头上。</p>

<p>细节就不多解释了。不懂问 google 。</p>

<p>今天是我第一次爬先锋，心里压力有点大。阿文让我在下面练了一个小时的挂快挂的手法。果然是个熟能生巧的活。我在下面练的好好的，结果到上面没气力的时候脑子就懵了，不知道该用什么动作把绳子挂进去。</p>

<p>这个对心理的考验挺大。到最后感觉再上一步就可以挂下一把快挂时，对自己的手就没信心。觉得手心都冒汗，担心自己冲上去了也没力气把绳子挂进去。</p>

<p>今天在屋檐下爬了两小时，最后一趟刚摸石头就下来了。担心自己手软挂不到第 2 把有危险。可是下来后挂了顶绳又哧溜一下上去把快挂都收下来了。看来这心里障碍还需要慢慢克服。</p>

<p>不过今天享受了几把自由落体，居然跳的时候一点不害怕的。比预想的好多了。反而觉得小刺激的。</p>

<p>再练上两个月，应该能自己背绳子上白云山了吧。</p>
]]>
    </content>
</entry>
<entry>
    <title>12306 可望成为中国最大的 SNS 网站</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/12306_sns.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=740" title="12306 可望成为中国最大的 SNS 网站" />
    <id>tag:blog.codingnow.com,2012://1.740</id>
    
    <published>2012-01-12T03:55:07Z</published>
    <updated>2012-01-12T04:06:43Z</updated>
    
    <summary>现在有哪家 SNS 网站可以帮用户制造几小时甚至几十小时的共处时间? 还能有共同话题方便搭讪? 没错, 12306 有这个潜质. 它可以帮助适龄男女(通过身份证) 共搭一趟列车， 并肩回家过年。 过于矜持，没留电话？没关系。12306 帮您联系上偶遇的 TA 。 深入挖掘一下，很多潜力（收费项目）可以有。单这一点，就值得铁道部把 12306 做的更好一些，直接通过电子商务创收。单独 IPO ，去骗广大股民的钱。这个概念大家一听就明白，果断上钩啊。 昨天说的排队系统，很多同学提出若干质疑。最多的问题是：黄牛来了肿么办？ 本来这么简单的问题，可以有无数方案来解决。我都不忍心说出来几个剥夺您思考的乐趣。不过就着今天 SNS 这话题我提一个。 领排队 ticket 前，需要先提交身份证。排到了必须用这个身份证买一张，或多张同一车次的票。如果排队时间过长，我们可以把排队的人放在一个（多个）大聊天室里。可按年龄段啊，IP 段啊区分一下。 这不就是一同城交友么？ 如今还有什么地方好凑这么多人一起聊天的？还不用太担心人妖、装嫩啊等等。谈的投机再想一个方法买一趟车回家，多好。 各位同学可以自由发挥，随便 YY 。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="杂记" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>现在有哪家 SNS 网站可以帮用户制造几小时甚至几十小时的共处时间? 还能有共同话题方便搭讪?</p>

<p>没错, 12306 有这个潜质. 它可以帮助适龄男女(通过身份证) 共搭一趟列车， 并肩回家过年。</p>

<p>过于矜持，没留电话？没关系。12306 帮您联系上偶遇的 TA 。</p>

<p>深入挖掘一下，很多潜力（收费项目）可以有。单这一点，就值得铁道部把 12306 做的更好一些，直接通过电子商务创收。单独 IPO ，去骗广大股民的钱。这个概念大家一听就明白，果断上钩啊。</p>

<hr />

<p><a href="http://blog.codingnow.com/2012/01/ticket_queue.html">昨天说的排队系统</a>，很多同学提出若干质疑。最多的问题是：黄牛来了肿么办？</p>

<p>本来这么简单的问题，可以有无数方案来解决。我都不忍心说出来几个剥夺您思考的乐趣。不过就着今天 SNS 这话题我提一个。</p>

<p>领排队 ticket 前，需要先提交身份证。排到了必须用这个身份证买一张，或多张同一车次的票。如果排队时间过长，我们可以把排队的人放在一个（多个）大聊天室里。可按年龄段啊，IP 段啊区分一下。</p>

<p>这不就是一同城交友么？</p>

<p>如今还有什么地方好凑这么多人一起聊天的？还不用太担心人妖、装嫩啊等等。谈的投机再想一个方法买一趟车回家，多好。</p>

<p>各位同学可以自由发挥，随便 YY 。</p>
]]>
        

    </content>
</entry>
<entry>
    <title>铁路订票系统的简单设计</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/ticket_queue.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=739" title="铁路订票系统的简单设计" />
    <id>tag:blog.codingnow.com,2012://1.739</id>
    
    <published>2012-01-11T15:25:58Z</published>
    <updated>2012-01-11T16:51:47Z</updated>
    
    <summary>其实铁路订票系统面临的技术难点无非就是春运期间可能发生的海量并发业务请求。这个加上一个排队系统就可以轻易解决的。 本来我在 weibo 上闲扯两句，这么简单的方案，本以为大家一看就明白的。没想到还是许多人有疑问。好吧，写篇 blog 来解释一下。 简单说，我们设置几个网关服务器，用动态 DNS 的方式，把并发的订票请求分摊开。类比现实的话，就是把人分流到不同的购票大厅去。每个购票大厅都可以买到所有车次的票。OK ，这一步的负载均衡怎么做我就不详细说了。 每个网关其实最重要的作用就是让订票的用户排队。其实整个系统也只用做排队，关于实际订票怎么操作，就算每个网关后坐一排售票员，在屏幕上看到有人来买票，输入到内部订票系统中出票，然后再把票号敲回去，这个系统都能无压力的正常工作。否则，以前春运是怎么把票卖出去的？ 我们来说说排队系统是怎么做的：...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="网络与安全" />
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>其实铁路订票系统面临的技术难点无非就是春运期间可能发生的海量并发业务请求。这个加上一个排队系统就可以轻易解决的。</p>

<p>本来我在 weibo 上闲扯两句，这么简单的方案，本以为大家一看就明白的。没想到还是许多人有疑问。好吧，写篇 blog 来解释一下。</p>

<p>简单说，我们设置几个网关服务器，用动态 DNS 的方式，把并发的订票请求分摊开。类比现实的话，就是把人分流到不同的购票大厅去。每个购票大厅都可以买到所有车次的票。OK ，这一步的负载均衡怎么做我就不详细说了。</p>

<p>每个网关其实最重要的作用就是让订票的用户排队。其实整个系统也只用做排队，关于实际订票怎么操作，就算每个网关后坐一排售票员，在屏幕上看到有人来买票，输入到内部订票系统中出票，然后再把票号敲回去，这个系统都能无压力的正常工作。否则，以前春运是怎么把票卖出去的？</p>

<p>我们来说说排队系统是怎么做的：</p>
]]>
        <![CDATA[<p>其实就类似我们去热门馆子吃饭拿号。只不过要防止别人伪造号插队而已。</p>

<p>如果你来一个人（一次 HTTP 请求），我就随机产生一个我做过一些签名处理的号码返回给你。暂时称为 ticket id 。这个 ticked id 是很难伪造的。</p>

<p>系统在内存里开一个大数组（32G 内存够排上亿人了吧），就是一循环队列。把这个 ticket id 放在队列尾。</p>

<p>用户现在拿着 ticket id 向网关发起请求。网关利用一次 hash 查询，在内存中的数组队列里查到它的位置，立刻返回给用户。用户的前端就可以看到，他在这个网关（售票大厅）前面还有多少人等着。</p>

<p>这里的关键是，整个队列都在本机的内存中，查询返回队列中的位置，可以实现的比一个处理静态文件的 web server 还要高效。静态文件至少还要去调用文件 IO 呢。静态文件 web server 可以处理多少并发量，不用我介绍了。</p>

<p>同时，前端会控制用户拿着 ticket id 查询队列位置的频率。高负载时可以 1s 一次，甚至更长时间。为了防止用户自己写脚本刷这个请求（虽然没有太大意义，因为刷的多也不会排到前面去），如果见到同一个 ticket id 过于频繁的查询。比如 10s 内查询了 20 次以上。就直接把这个 ticket id 作废。持有这个 ticket 的人就需要重新排队了。</p>

<p>对于最后排到的人，系统会生成一个唯一的不可伪造的 session id ，用户下面就可以通过这个 session id 去做实际的购票流程了。可以连去真正的购票服务器，也可以通过网关中转。非法的 session id 会立刻断掉，用户一旦知道伪造 session id 几乎不可能，只有通过 ticket id 排队拿到，除非是恶意攻击系统，不然不会有人乱拿 session id 去试。</p>

<p>我们再给每个 session id 设置一个最长有效时间，比如半小时。如果超过半小时还没有完整购票流程，那么就重新去排队。</p>

<p>至于同时开放多少个 session id ，也就是相当于开放多少个购票窗口，就取决于购票系统能承受的负载了。不过简单计算一下，就知道有排队系统保证了良好的次序，再以计算机的吞吐能力，解决不过几亿人的购票请求，即使这些人都同来排队，也就是一组机器几小时的处理量而已。</p>

<p>这票 blog 也就是随便写写，可能不太严谨，但意思达到了。中间有很多数据需要估算，也不是太难的事情。</p>

<p>为什么现在的购票系统这么滥？关键在于大量的网络带宽，计算力浪费在了“维持次序”上。系统不稳定时，大量的只做了一半的无效的购票流程浪费掉了这些。要响应高并发的 HTTP 请求，关键就在于迅速反应，不要什么都想着从数据库绕一圈。排队的队伍维持就完全不需要使用数据库。如果所有 HTTP 请求都立刻返回，在短时间内可以处理的 HTTP 请求量也会非常大。而如果你一下处理不了这个请求，又把 TCP 连接保持在那里，就莫怪系统支持不住了。</p>

<p>另外，用户看到了不断在减少的队列前面的人数，他们也会安心等待。只要网站页面刷新流畅（只处理队列信息很容易保证），用户体验会很好。</p>

<hr />

<p>最后补充几句废话：因为铁路购票系统很多年前就实现了内部网络化，有成熟系统支撑，运作多年。这次做互联网版本，一定不能放弃原有系统新来一套。不然实体购票点也在网页上刷不出票就崩溃了。</p>

<p>所以要做的仅仅是怎么做一个系统和原有系统对接。这样风险最小。两套系统可以分别优化处理能力。基于这个设计起点，所以我才不看好所有企图取代原有系统的方案。</p>
]]>
    </content>
</entry>
<entry>
    <title>开发笔记 (8) : 策划公式的 DSL 设计</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/dev_note_8.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=738" title="开发笔记 (8) : 策划公式的 DSL 设计" />
    <id>tag:blog.codingnow.com,2012://1.738</id>
    
    <published>2012-01-09T15:05:54Z</published>
    <updated>2012-01-09T15:09:18Z</updated>
    
    <summary>今天很早就起床了，以至于到了办公室还不到 11 点。中饭前有一个多小时可以做各种杂事。 我把周末做的工作和蜗牛同步了一下信息，然后得到了几个新需求。主要就是还是需要在协议定义中加入 protobuf 中等价于 service 的东西。思索了一下，觉得有必要，就花了一个小时把特性加上。 C Binding API 方面还有一点疏漏的地方。大概是源于基于 Erlang 框架下的一些小困难。略微修改了下 C 接口协议就 OK 了。然后很 happy 的去食堂吃饭。 然后我暂时就可以转向 Client 方面的一些需求分析以及解决了。 在生成动画树的数据方面，我们的交换格式使用了某中文本中间格式，最终利用 protobuf 来做二进制持久化。在解析文本格式方面，我操起了多年前耍过的 LPEG 。（我曾经用 lpeg 写过 protobuf 文本的解析工具）这个绝对是解析文本的利器啊。午饭时在食堂大家围在桌子边吐槽 Java ，说道 Java 社区最二的莫过于抓着个 XML 当救命稻草不放，所以便有了各种淡疼的基于 XML 的框架。如果早期...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="游戏开发" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天很早就起床了，以至于到了办公室还不到 11 点。中饭前有一个多小时可以做各种杂事。</p>

<p>我把周末做的工作和蜗牛同步了一下信息，然后得到了几个新需求。主要就是还是需要在协议定义中加入 protobuf 中等价于 service 的东西。思索了一下，觉得有必要，就花了一个小时把特性加上。 C Binding API 方面还有一点疏漏的地方。大概是源于基于 Erlang 框架下的一些小困难。略微修改了下 C 接口协议就 OK 了。然后很 happy 的去食堂吃饭。</p>

<p>然后我暂时就可以转向 Client 方面的一些需求分析以及解决了。</p>

<p>在生成动画树的数据方面，我们的交换格式使用了某中文本中间格式，最终利用 protobuf 来做二进制持久化。在解析文本格式方面，我操起了多年前耍过的 <a href="http://www.inf.puc-rio.br/~roberto/lpeg/">LPEG</a> 。（我曾经用 lpeg 写过 protobuf 文本的解析工具）这个绝对是解析文本的利器啊。午饭时在食堂大家围在桌子边吐槽 Java ，说道 Java 社区最二的莫过于抓着个 XML 当救命稻草不放，所以便有了各种淡疼的基于 XML 的框架。如果早期 Java 社区能多那么几个受过 Unix/C 传统熏陶过的程序员，就能知道设计 DSL 来解决问题是多么爽快的事情。也不至于在 XML 的树上吊死了。唉，搞得现在积重难返了。</p>

<p>下午正式和策划进行沟通，观看他们这段时间写的各种 excel 表格。我说，你们放开了想怎么把问题表达清楚吧，只要逻辑清晰有条例，信息不要漏掉，怎么表达那些公式都行。我慢慢看，然后规范写法，最终方便程序解析。</p>

<p>以前见过许多项目，有的设计出繁杂的 excel 表格式，然后 export 给程序用；有的干脆让策划写程序代码；甚至有的做一堆漂亮 UI 的公式编辑器。我想最快也最方便达到效果的，莫过于设计一个最小需求集合的 DSL ，让策划认同其语法，然后使用 DSL 来编辑了。</p>
]]>
        <![CDATA[<p>为什么是 DSL 而不是特定语言代码？因为通用语言往往是为了解决更繁杂的需求，有很多多余的语法会干扰策划的思维。他们会将大量时间浪费在学习语言、检查语法错误上。</p>

<p>另外，限制于特定语言也会局限项目的发展。很有可能以后你会换一种方式去解析公式。比如在性能无所谓的时候，你想用 lua 代码就好。但性能敏感了，你又想用 C 实现，等等。有个中立的 DSL ，为以后留下更多的优化空间。</p>

<p>我们把公式定义和计算这个模块的知识依赖尽量的做小了，只依赖一种简单的 DSL 实现。</p>

<p>当然 DSL 的定义也是在不断发展的，这个需要语言设计的经验以及对问题域的理解。这两点我都不太够。只能试试。我相信对于这样一只小团队，是可以承受某种程度的变化的。更何况 DSL 是我自己实现的，当一些重大修改发生后，我可以自己写工具来批量修正历史代码。</p>

<p>我几个小时来了解需求，并定义 DSL 草稿。</p>

<p>策划需要的大概是列出一些可以做基本四则运算的公式，依靠一些变量（通常是人物属性），计算出新的值，赋给新变量。当公式比较复杂的时候，他们希望可以自定义一些函数，这些函数几乎都是 n:1 的。输入 n 个参数，得到一个值。</p>

<p>最常用的两个外部函数（策划往往不像程序那般思维，他们不把数学运算以外的数据处理称为函数，但程序员却这么看，我们也容易灌输这个观念），一是查表。就是在 excel 里做一张单独的二纬表，查询第几行第几列的值。我想了一下，其实最终这个是一个三纬向量：表名本身是一个维度、行列是另两个；其二是随机数。</p>

<p>有了这些，几乎就可以满足策划的所有计算要求了。</p>

<p>但是有些计算还需要有一点流程控制。以某策划同学爱玩的魔兽世界为例，就需要先产生一个随机数，判定攻击是否被躲闪；一旦被躲闪，后面的计算就不需要了。如果不被躲闪，则算下去。当然在 DSL 中设计流程控制也不无不可。但我觉得仅仅是这种需求还没有必要增加它。我想，如果顺序执行每条表达式的功能足够的话，那么最好不要加新的知识。所以我决定向策划推销 bool 运算规则。</p>

<p>毕竟大家都是理科出生，很快就明白了。lua 风格的 and or not 的短路规则很简单，写几个范例大家就懂了。最后我设计了这么一个粗陋的东西。</p>

<pre>
DODGE := 50
PARRY := 30
CRITICAL := 20
RACE := "战士"
LEVEL := 23
DPS := 100

dodge = roll(DODGE)
hit = not (dodge or roll(PARRY))
critical = hit and roll(CRITICAL)
_critical (race , level) -> table("cri" , race , level) * 0.1 + 1
damage = hit and (DPS * (critical and _critical(RACE , LEVEL) or 1))
</pre>

<p>里面有一张 cri 表，我先用文本格式表达，以后再花半小时去支持 excel 格式。</p>

<pre>
     10-19  20-29   30
战士 1      2       3
牧师 2      4       6
</pre>

<p>今天就不在 blog 上解释了，反正日后总要花时间写文档的。等明天先口头教一下我们的设置策划去用。</p>

<p>实现这么一套 DSL 解析大约花掉了我大半个下午。应该感谢 lua 和 lpeg 的便利，100 多行代码就把整个模块和应用工具写完了。主要是要方便策划测试使用。</p>

<p>今天的流水账就先记在这里了。</p>
]]>
    </content>
</entry>
<entry>
    <title>开发笔记 (7) : 服务器底层框架及 RPC</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/dev_note_7.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=737" title="开发笔记 (7) : 服务器底层框架及 RPC" />
    <id>tag:blog.codingnow.com,2012://1.737</id>
    
    <published>2012-01-08T16:29:18Z</published>
    <updated>2012-01-08T17:03:17Z</updated>
    
    <summary>很久没有写工作笔记了，如果不在这里写，我连写周报的习惯都没有。所以太长时间不写就会忘记到底做了些啥了。 这半个多月其实做了不少工作，回想起来又因为太琐碎记不太清。干脆最近这几天完成的这部分工作来写写吧。 我在 开发笔记 第四篇谈到了 agent 的处理流程。但实际操作下来还是觉得概念显得复杂。推而广之，对于不是 agent 的服务，我需要一个通用的消息处理框架。 对于每个服务器，可以看成是对一组约定的服务协议进行处理。对于协议分组，之前我有许多想法，可做下来又发现了若干问题。本来我希望定义出一个完整的 session 概念，同一个 session 下，可以分不同的步骤，每个步骤都有一个激活的协议组。协议组之间可以共享状态，同时限制并发。做下来发现，很难定义出完整的事务处理流程并描述清楚。可能需要设计一个 DSL 来解决这个问题更好一些。一开始我也是计划设置这个小语言的。可一是时间紧迫，二是经验不足，很难把 DSL 设计好。 而之前的若干项目证明，其实没有良好的事务描述机制，并不是不可用。实现一个简单的 RPC 机制，一问一答的服务提供方式也能解决问题。程序员只要用足够多经验，是可以用各种土法模拟长流程的事务处理流。只是没有严格约束，容易写出问题罢了。那么这个问题的最小化需求定义就是：可以响应发起请求人的请求，解析协议，匹配到对应的处理函数。所有请求都应该可以并发，这样就可以了。至于并发引起的问题，可以不放在这个层次解决。 我谨慎的选择了 RPC 这种工作方式。实现了一个简单的 RPC 调用。因为大多数服务用 Lua 来实现，利用 coroutine 可以工作的很好。不需要利用 callback 机制。在每条请求/回应的数据流上，都创建了独立的环境让工作串行进行。相比之前，我设计的方案是允许并发的 RPC 调用的。这个修改简化了需求定义，也简化的实现。 举例来说，如果 Client 发起登陆验证请求，那么由给这个 Client 服务的 Agent...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>很久没有写工作笔记了，如果不在这里写，我连写周报的习惯都没有。所以太长时间不写就会忘记到底做了些啥了。</p>

<p>这半个多月其实做了不少工作，回想起来又因为太琐碎记不太清。干脆最近这几天完成的这部分工作来写写吧。</p>

<p>我在 <a href="http://blog.codingnow.com/cloud/DevNotes">开发笔记</a> 第四篇谈到了 agent 的处理流程。但实际操作下来还是觉得概念显得复杂。推而广之，对于不是 agent 的服务，我需要一个通用的消息处理框架。</p>

<p>对于每个服务器，可以看成是对一组约定的服务协议进行处理。对于协议分组，之前我有许多想法，可做下来又发现了若干问题。本来我希望定义出一个完整的 session 概念，同一个 session 下，可以分不同的步骤，每个步骤都有一个激活的协议组。协议组之间可以共享状态，同时限制并发。做下来发现，很难定义出完整的事务处理流程并描述清楚。可能需要设计一个 DSL 来解决这个问题更好一些。一开始我也是计划设置这个小语言的。可一是时间紧迫，二是经验不足，很难把 DSL 设计好。</p>

<p>而之前的若干项目证明，其实没有良好的事务描述机制，并不是不可用。实现一个简单的 RPC 机制，一问一答的服务提供方式也能解决问题。程序员只要用足够多经验，是可以用各种土法模拟长流程的事务处理流。只是没有严格约束，容易写出问题罢了。那么这个问题的最小化需求定义就是：可以响应发起请求人的请求，解析协议，匹配到对应的处理函数。所有请求都应该可以并发，这样就可以了。至于并发引起的问题，可以不放在这个层次解决。</p>

<p>我谨慎的选择了 RPC 这种工作方式。实现了一个简单的 RPC 调用。因为大多数服务用 Lua 来实现，利用 coroutine 可以工作的很好。不需要利用 callback 机制。在每条请求/回应的数据流上，都创建了独立的环境让工作串行进行。相比之前，我设计的方案是允许并发的 RPC 调用的。这个修改简化了需求定义，也简化的实现。</p>

<p>举例来说，如果 Client 发起登陆验证请求，那么由给这个 Client 服务的 Agent 首先获知 Client 的需求。然后它把这个请求经过加工，发送到认证服务器等待回应（代码上看起来就是一次函数调用），一直等到认证服务器响应发回结果，才继续跑下面的逻辑。所以处理 Client 登陆请求这单条处理流程上，所有的一切都仅限于串行工作。当然，Agent 同时还可以相应 Client 别的一些请求。</p>

<p>如果用 callback 机制来表达这种处理逻辑，那就是在发起一个 RPC 调用后，不能做任何其它事情，后续流程严格在 callback 函数中写。</p>

<p>每个 RPC 调用看起来是这样的：</p>
]]>
        <![CDATA[<pre>
  local salt = service.call(addr ,
     "login.salt" , { username = username }).salt
  local response = md5(username .. ":" .. password .. ":" .. salt)
  local ok = service.call(addr, "login.challenge" ,
     { username = username , response = response }).ok
  print(ok)
</pre>

<p>而 service 编写是这样的：</p>

<pre>
-- login.lua
function salt(username)
  local salt = gen_salt(username)
  return { salt = salt }
end

function challenge(username , response)
    local salt = gen_salt(username)
    local password = service.call("authdb",
       "query" , { username = username }).password
    local result = md5(username .. ":" .. password .. ":" .. salt
    return { ok = (result == response) }
end
</pre>

<p>大家需要约定中间的协议，采用 protobuf 格式描述：</p>

<pre>
package login;

message salt {
  optional string username = 1;
  message response {
    optional bool salt = 1;
  }
}

message challenge {
  optional string username = 1;
  optional string response = 2;
  message response {
    optional bool ok = 1;
  }
}
</pre>

<p>btw, 我没有用 protobuf 的 serivce 特性。觉得不太方便，而且也没有太多必要引入过多特性。</p>

<p>当然这只是 RPC 范例代码，不设计认证协议的具体设计。实际应用中，校验过程会比这个更完备一些。</p>

<p>在 login 服务中，收到用户来的请求，会去向 authdb 服务索取密码信息。这个 RPC 调用是阻塞的，直到 authdb 返回结果。但是，login 服务并不被阻塞，可以接受其它请求。每个不同请求的回应对应关系，是在低一个层次上的 session id 来区分。这些隐藏在框架实现中了。</p>

<p>设计细节今天先不列了，也没太多难度。</p>

<p>最后，我对 RPC 的使用是谨慎的。必须留意 RPC 会带来的问题。这些在《<a href="http://blog.codingnow.com/2006/10/taoup.html">Unix 编程艺术</a>》7.3.2 有论述，我再说多少次也超不出这个范畴。</p>

<hr />

<p>做这套东西，我和蜗牛是有一些分工的。服务器数据流框架并不是我来实现。蜗牛用 erlang 做的。</p>

<p>这些东西我们折腾了好久，这里面有我的很多工作失误。最终，我觉得应该把工作严格划分开，需要有一个清晰的模块需求定义。</p>

<p>本来是初步定好了，每个服务工作在一个独立的 Lua State 中（并不一定在独立进程/线程中），包括为每个 Client 服务的 Agent 。一开始是在 Lua 的层次来划分工作的。蜗牛写了一部分的 Lua 驱动代码。我觉得是我在分工问题上的错误。这个需求很难稳定下来。后来，我整理了一下思路，决定定义明确的 C 接口，然后我来做 Lua 封装。</p>

<p>服务器的大框架主要解决玩家的接入问题，各个服务间的数据流向问题，服务的启动和停止等等。我设想，每个服务都有单一的输入输出流，只面对框架。实现上可能框架并不是在做复制转发，有可能直接对接两个服务的输入输出。尤其是两个服务可能是在同一个线程中，以 coroutine 的形式工作。这个优化空间可以留下来。</p>

<p>我在做后面的工作之前，先写了一份文档，描述需求：</p>

<ul>
<li>向 skynet 发送一条指令, 并立刻获得一个结果. 目前提供以下三条指令:
<ul>
<li>注册自己到 skynet , 可以给出一个 well-known 的名字 (name), 也可以不给出. skynet 返回一个 unique 名字 (uid)</li>
<li>向 skynet 查询系统时间</li>
<li>设置 timer , skynet 会在 timeout 时回调</li>
</ul></li>
<li>向 skynet 发送数据, 由 skynet 决定把数据送达何处.</li>
<li>注册一个 callback , 当 skynet 有消息送达的时候触发.</li>
</ul>

<p>注：skynet 是框架的开发代号。</p>

<p>然后是接口的 C 定义</p>

<pre class="mtc_block"><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span> skynet_context<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> skynet_exit<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span> context<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> skynet_step<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span> ud<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

<span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> skynet_command<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span> context<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> cmd <span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> parm<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> skynet_send<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span> context<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> addr <span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> msg<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordStdio c_KeywordStructure def_StructKeyword def_Keyword">size_t</span> sz<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

<span class="c_KeywordANSI def_Keyword">typedef</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol">*</span>skynet_cb<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span>context<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span>ud<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> uid <span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">const</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span> <span class="c_Symbol def_Symbol">*</span> msg<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordStdio c_KeywordStructure def_StructKeyword def_Keyword">size_t</span> sz<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> skynet_callback<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">void</span> <span class="c_Symbol def_Symbol">*</span> context<span class="c_Symbol def_Symbol">,</span> skynet_cb cb<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span></pre>

<p>注: command 我选取了最简单的字符串风格。是因为考虑到字符串的知识依赖最小，很容易 binding 到任何语言中。也最容易实现网络传输。不同 command 的协议定义也写了文档，包括文本协议的定义。这里就不一一列出了。</p>

<p>有了文档之后，我开始编写 skynet 的黑盒。基本上就是一个本地单一进程，用来调度同一进程下的不同服务。这个很好写，大约两三小时，不到 300 行 C 代码就搞定了。虽然限制很多，性能也很低下，但可以实现以上的接口。</p>

<p>之后的代码就不再是假盒子了。是把这几个 C 接口 binding 到 lua 里，然后在 lua 层面写真实的应用。工作量比较大，但也好做。一旦蜗牛那边真正的 skynet 工作正确，整合工作只是替换前面几个接口的实现库而已。</p>

<hr />

<p>Blog 写这种系列， 不太方便整理。 这点还是 wiki 比较好。<a href="http://blog.codingnow.com/cloud/DevNotes">我把这个系列整理出一个列表了</a>。</p>

<p>ps. 这段时间还做了许多琐碎的工作，包括帮客户端解决一些问题。中间闲了两天，想玩一下 LaTeX ，结果就写了这个：<a href="http://www.codingnow.com/download/readinglua.pdf">Lua 源码欣赏</a> 。结果工作一忙就没时间写下去了。估计这个半拉子工程会这么太监掉的。</p>
]]>
    </content>
</entry>
<entry>
    <title> 关于分工合作</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2012/01/_oeouoeie.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=736" title=" 关于分工合作" />
    <id>tag:blog.codingnow.com,2012://1.736</id>
    
    <published>2012-01-07T11:12:59Z</published>
    <updated>2012-01-07T11:14:20Z</updated>
    
    <summary>最近工作有点感触， 关于如何分工的。 我觉得所谓设计和实现是无论如何都很难分拆出去的。就是说你不实现你设想的结构，永远都很难知道哪里有问题；即使没有问题，换一个人来实现你想的东西，也无法把设计意图全部传达过去。如果可以做到，那么耗费的时间和精力足够你自己来实现了。 这也是为什么我之前说，软件项目需要很多人一起完成可能是一个骗局 。但毕竟，一个人精力有限，项目时间也有限。分工是无奈之举。可这件事情怎样做才对呢？ 我最近有所体会的还是那些被嚼过很多年的老道理。就是模块划分清晰，强内聚，低耦合之类。想强调的是，模块的层次一定要适中，同一层次上规模不能太大，有严格输入、输出接口。 这些并不是为了方便测试，检验工作正确性，而是为了拆分工作。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>最近工作有点感触， 关于如何分工的。</p>

<p>我觉得所谓设计和实现是无论如何都很难分拆出去的。就是说你不实现你设想的结构，永远都很难知道哪里有问题；即使没有问题，换一个人来实现你想的东西，也无法把设计意图全部传达过去。如果可以做到，那么耗费的时间和精力足够你自己来实现了。</p>

<p>这也是为什么我之前说，<a href="http://blog.codingnow.com/2011/05/solo.html">软件项目需要很多人一起完成可能是一个骗局</a> 。但毕竟，一个人精力有限，项目时间也有限。分工是无奈之举。可这件事情怎样做才对呢？</p>

<p>我最近有所体会的还是那些被嚼过很多年的老道理。就是模块划分清晰，强内聚，低耦合之类。想强调的是，模块的层次一定要适中，同一层次上规模不能太大，有严格输入、输出接口。</p>

<p>这些并不是为了方便测试，检验工作正确性，而是为了拆分工作。</p>
]]>
        <![CDATA[<p>软件可以有若干层次，总体设计一个人来做没有问题，但在每个层次上应该有足够独立的接口。接口数据要严格控制，并有最小化的知识依赖。包括接口引用的数据类型，接口的参数数量。最好是单一语言的，如有可能，只使用 C 语言的函数和 C 语言的基础类型最为通用。即使各个组件的实现不是用 C 来编写的。</p>

<p>每个层次对上或对下都应该是黑盒子，有比较单一的输入输出点交互。同一层次接口数量过多，应该想办法切分成多个模块。判断模块是否应该切分的标准，不在于实现的代码行数，而在于模块的接口的数量。</p>

<p>这样，我们可以清晰的文档化模块的需求定义。方便把工作分拆后，其他人可以利用文档自行编写假盒子，来让自己实现的部分可以工作起来。</p>

<p>编写一些其他人正在做的模块的假盒子很重要。即使他人的工作已经完成，可以用真盒子来整合也不要轻易去用真的那份。因为编写假盒子的过程，这样可以增进对自己的模块的理解，也可以检验接口设计是否合理。如果假盒子太难编写，很可能是设计有问题，把交互特别繁杂的模块强行分开了。</p>

<p>基于这些点，就能发现，其实单一模块的规模最终控制在一个人可以完成的规模最好，然后设计和实现同一个人来完成就好了。对于不同的人合作时，只是在最后做一些接口粘合和小修正工作。</p>
]]>
    </content>
</entry>
<entry>
    <title>lua 5.2 的 _ENV</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2011/12/lua_52_env.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=735" title="lua 5.2 的 _ENV" />
    <id>tag:blog.codingnow.com,2011://1.735</id>
    
    <published>2011-12-29T16:34:21Z</published>
    <updated>2011-12-30T12:29:05Z</updated>
    
    <summary>lua 5.2 正式发布了，对于 lua 语言本身的修改，重中之重就是对 environment 这个概念的修改。 可以说， 5.1 以前的 environment 已经没有了。environment 对于制造一个安全的沙盒（或是实现 DSL）是一个很重要的语言特性，我以前很喜欢使用，但也很容易用错。这次的修改我认为是一个谨慎的决定，并使得 lua 语言更为精简和严谨了。 我这样理解 5.2 中的 environment 。本质上，lua 取消了原有意义上的 environment 。所以我们可以看到 C Function 不再有环境了。function 、在 lua 中称为 closure ，仅仅只是函数体和 upvalue 的联合体。这简化了 lua 语言本身。全局变量实际上只是一个语法糖，编译时再前面加上了 _ENV. 的前缀。这样，从 load 开始，第一个...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>lua 5.2 正式发布了，对于 lua 语言本身的修改，重中之重就是对 environment 这个概念的修改。</p>

<p>可以说， 5.1 以前的 environment 已经没有了。environment 对于制造一个安全的沙盒（或是实现 DSL）是一个很重要的语言特性，我以前很喜欢使用，但也很容易用错。这次的修改我认为是一个谨慎的决定，并使得 lua 语言更为精简和严谨了。</p>

<p>我这样理解 5.2 中的 environment 。本质上，lua 取消了原有意义上的 environment 。所以我们可以看到 C Function 不再有环境了。function 、在 lua 中称为 closure ，仅仅只是函数体和 upvalue 的联合体。这简化了 lua 语言本身。全局变量实际上只是一个语法糖，编译时再前面加上了 <code>_ENV.</code> 的前缀。这样，从 load 开始，第一个 chunk 就被加上了 <code>_ENV</code> 这个 upvalue ，然后依次传递下去。</p>

<p>这个设计基本可以取代以前使用 getfenv/setfenv 改变函数环境的方法。但是又不完全等价。总体来说，增加了一些限制，但不太容易写出 bug 的代码了。</p>

<p>比如说，现在想给返回一个独立环境的函数，可以这样写：</p>
]]>
        <![CDATA[<pre>
function foobar(env)
    local _ENV = env
    return function() ... end
end
</pre>

<p>而以前大约是这样：</p>

<pre>
function foobar(env)
    return setfenv(function() ...  end, env)
end
</pre>

<p>这不太看得出好坏，但是如果是一组函数，就有区别了。5.2 中是这样:</p>

<pre>
function foobar(env)
    local _ENV = env
    local ret = {}
    function ret.foo()
        ...     
    end
    function ret.bar()
        ...
    end
    return ret
end
</pre>

<p>5.1 的等价代码大约是这样：</p>

<pre>
function foobar(env)
    local old = getfenv()
    setfenv(1,env)
    local ret = {}
    function ret.foo()
        ...     
    end
    function ret.bar()
        ...
    end
    setfenv(1,old)
    return ret
end
</pre>

<p>或者这样更函数式一点：</p>

<pre>
function foobar(env)
    return setfenv(
    function()
        local ret = {}
        function ret.foo()
            ...     
        end
        function ret.bar()
            ...
        end
        return ret
    end , env) ()
end
</pre>

<p>getfenv/setfenv 更灵活，却更容易出错。</p>

<p>对于制作沙盒来说，我感觉 lua 5.2 会更为鼓励使用 load 这种运行时的编译行为。即一定程度上的鼓励元编程。（因为取消了 setfenv ，所以给了 load 显式的参数来制定给 chunk 一个新的环境）</p>

<p>btw, 这个语言设计变更的同时也增强了函数式编程的性能。因为 lua 现在可以更方便的合并那些有相同 upvalue 的 closure 了。（从前除了 upvalue 还有 environment ，合并行为更为复杂）</p>

<hr />

<p>12 月 30 日补充:</p>

<p>如果非要类似 setfenv 的功能, 修改一组函数的 <code>_ENV</code> 大概需要这样做了:</p>

<pre>
function getfuncs()
  local _ENV = _ENV
  local ret = {}
  function ret.foo()
    ...
  end
  function ret.setfenv(env)
    _ENV = env
  end
  return ret 
end
</pre>
]]>
    </content>
</entry>
<entry>
    <title>Buddy memory allocation (伙伴内存分配器)</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2011/12/buddy_memory_allocation.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=734" title="Buddy memory allocation (伙伴内存分配器)" />
    <id>tag:blog.codingnow.com,2011://1.734</id>
    
    <published>2011-12-20T13:38:05Z</published>
    <updated>2011-12-25T14:16:08Z</updated>
    
    <summary>今天吃晚饭的时候想到，我需要一个定制的内存分配器。主要是为了解决 共享内存 中的字符串池的管理。 这个内存分配器需要是非入侵式的，即不在要分配的内存块中写 cookie 。 而我的需求中，需要被管理的内存块都是很规则的，成 2 的整数次幂的长度。buddy memory allocation 刚好适用。 算法很简单，就是每次把一个正内存块对半切分，一直切到需要的大小分配出去。回收的时候，如果跟它配对的块也是未被使用的，就合并成一个大的块。标准算法下，分配和释放的时间复杂度都是 O(log N) ，N 不会特别大。算法的优点是碎片率很小。而且很容易做成非入侵式的，不用在被管理的内存上保存 cookie 。只需要额外开辟一个二叉树记录内存使用状态即可。 我吃完饭简单 google 了一下，没有立刻找到满足我要求的现成代码。心里估算了一下，C 代码量应该在 200 行以下，我大概可以在 1 小时内写完。所以就毫不犹豫的实现了一份。 然后，自然是开源了。有兴趣的同学可以去 github 拿一份。这样就省得到再需要时再造轮子了。嘿嘿。...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="算法" />
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>今天吃晚饭的时候想到，我需要一个定制的内存分配器。主要是为了解决 <a href="http://blog.codingnow.com/2011/12/dev_note_6.html">共享内存</a> 中的字符串池的管理。</p>

<p>这个内存分配器需要是非入侵式的，即不在要分配的内存块中写 cookie 。</p>

<p>而我的需求中，需要被管理的内存块都是很规则的，成 2 的整数次幂的长度。<a href="http://en.wikipedia.org/wiki/Buddy_memory_allocation">buddy memory allocation</a>  刚好适用。</p>

<p>算法很简单，就是每次把一个正内存块对半切分，一直切到需要的大小分配出去。回收的时候，如果跟它配对的块也是未被使用的，就合并成一个大的块。标准算法下，分配和释放的时间复杂度都是 O(log N) ，N 不会特别大。算法的优点是碎片率很小。而且很容易做成非入侵式的，不用在被管理的内存上保存 cookie 。只需要额外开辟一个二叉树记录内存使用状态即可。</p>

<p>我吃完饭简单 google 了一下，没有立刻找到满足我要求的现成代码。心里估算了一下，C 代码量应该在 200 行以下，我大概可以在 1 小时内写完。所以就毫不犹豫的实现了一份。</p>

<p>然后，自然是开源了。有兴趣的同学可以<a href="https://github.com/cloudwu/buddy">去 github 拿一份</a>。这样就省得到再需要时再造轮子了。嘿嘿。</p>
]]>
        <![CDATA[<p>btw, 当然这块代码有许多值得优化的地方，比如可以把里面的递归优化成循环回溯。这个算法我读初中时经常写。因为初一那个时候参加信息学奥赛时用的 basic 不支持局部变量，全部变量都是全局的，很难实现递归。所以早期我都不用递归遍历二叉树的，感觉写起来好麻烦。</p>

<p>不过循环回溯遍历树应该是比递归快不少的，因为减少了许多不必要的环境变量压栈，对不支持 closure 的 C 语言尤其是。</p>

<p>这个库用起来很简单。它并不实际管理内存（它不侵入被管理的内存）。你可以设想你另外有一大块内存是由许多最小单位块合起来的。你可以假设最小单位是 1K 。那么用 <code>buddy_new(10)</code> 就可以帮你管理 1024K 内存。</p>

<p><code>buddy_alloc</code> 可以请求若干个最小单位块，返回一个序号。然后用户可以自己去大内存上索引出来用。用完调用 <code>buddy_free</code> 归还即可。</p>

<p>为了调试方便，我还提供了 <code>buddy_dump</code> 打印二叉树的细节，可以直观的看出那些内存区域未被使用，哪些已经被占用。</p>

<p>ps. 果然，写这篇 blog 花掉的时间比完成这些代码时间更长。代码也如我所料的没有超过 200 行。看看，把东西描述清楚就是比实现一个东西要花更长的时间，这就是项目人多反而做的慢的原因之一吧。</p>

<hr />

<p>12 月 21 日:</p>

<p>感觉用了递归很不爽, 所以重写了实现, 改用完全二叉树储存, 去掉了递归. 并且(也是主要原因) 减少了内存占用.</p>

<p>如果需要优化的话, 应该加一组 free list , 这个再说。我自己的需求中，时间性能并不太敏感。</p>

<hr />

<p>12 月 25 日:
楼下有 wuwenbin 提到可以在节点中保存最大连续空闲区域来优化分配过程. 这样代码更短小, 速度也可以快一点. 唯一的缺憾是内存略微消耗的多一点点. 实现在 <a href="https://github.com/wuwenbin/buddy2">https://github.com/wuwenbin/buddy2</a></p>
]]>
    </content>
</entry>
<entry>
    <title>开发笔记 (6) : 结构化数据的共享存储</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2011/12/dev_note_6.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=733" title="开发笔记 (6) : 结构化数据的共享存储" />
    <id>tag:blog.codingnow.com,2011://1.733</id>
    
    <published>2011-12-15T11:59:22Z</published>
    <updated>2011-12-15T12:03:48Z</updated>
    
    <summary>开始这个话题前，离上篇开发笔记已经有一周多了。我是打算一直把开发笔记写下去的，而开发过程中一定不会一帆风顺，各种技术的抉择，放弃，都可能有反复。公开记录这个历程，即是对思路的持久化，又是一种自我督促。不轻易陷入到技术细节中而丢失了产品开发进度。而且有一天，当我们的项目完成了后，我可以对所有人说，看，我们的东西就是这样一步步做出来的。每个点滴都凝聚了叫得上名字的开发人员这么多个月的心血。 技术方案的争议在我们几个人内部是很激烈的。让自己的想法说服每个人是很困难的。有下面这个话题，是源于我们未来的服务器的数据流到底是怎样的。 我希望数据和逻辑可以分离，有物理上独立的点可以存取数据。并且有单独的 agent 实体为每个外部连接服务。这使得进程间通讯的代价变得很频繁。对于一个及时战斗的游戏，我们又希望对象实体之间的交互速度足够快。所以对于这个看似挺漂亮的方案，可能面临实现出来性能不达要求的结果。这也是争议的焦点之一。 我个人比较有信心解决高性能的进程间数据共享问题。上一篇 谈的其实也是这个问题，只是这次更进一步。 核心问题在于，每个 PC (玩家) 以及有可能的话也包括 NPC 相互在不同的实体中（我没有有进程，因为不想被理解成 OS 的进程），他们在互动时，逻辑代码会读写别的对象的数据。最终有一个实体来保有和维护一个对象的所有数据，它提供一个 RPC 接口来操控数据固然是必须的。因为整个虚拟世界会搭建在多台物理机上，所以 RPC 是唯一的途径。这里可以理解成，每个实体是一个数据库，保存了实体的所有数据，开放一个 RPC 接口让外部来读写内部的这些数据。 但是，在高频的热点数据交互时，无论怎么优化协议和实现，可能都很难把性能提升到需要的水平。至少很难达到让这些数据都在一个进程中处理的性能。 这样，除了 RPC 接口，我希望再提供一个更直接的 api 采用共享状态的方式来操控数据。如果我们认为两个实体的数据交互很频繁，就可以想办法把这两个实体的运行流程迁移到同一台物理机上，让同时处理这两个对象的进程可以同时用共享内存的方式读写两者的数据，性能可以做到理论上的上限。 ok, 这就涉及到了，如何让一块带结构的数据被多个进程共享访问的问题。结构化是其中的难点。 方案如下：...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="游戏开发" />
            <category term="语言与设计" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>开始这个话题前，离上篇开发笔记已经有一周多了。我是打算一直把开发笔记写下去的，而开发过程中一定不会一帆风顺，各种技术的抉择，放弃，都可能有反复。公开记录这个历程，即是对思路的持久化，又是一种自我督促。不轻易陷入到技术细节中而丢失了产品开发进度。而且有一天，当我们的项目完成了后，我可以对所有人说，看，我们的东西就是这样一步步做出来的。每个点滴都凝聚了叫得上名字的开发人员这么多个月的心血。</p>

<p>技术方案的争议在我们几个人内部是很激烈的。让自己的想法说服每个人是很困难的。有下面这个话题，是源于我们未来的服务器的数据流到底是怎样的。</p>

<p>我希望数据和逻辑可以分离，有物理上独立的点可以存取数据。并且有单独的 agent 实体为每个外部连接服务。这使得进程间通讯的代价变得很频繁。对于一个及时战斗的游戏，我们又希望对象实体之间的交互速度足够快。所以对于这个看似挺漂亮的方案，可能面临实现出来性能不达要求的结果。这也是争议的焦点之一。</p>

<p>我个人比较有信心解决高性能的进程间数据共享问题。<a href="http://blog.codingnow.com/2011/12/dev_note_5.html">上一篇</a> 谈的其实也是这个问题，只是这次更进一步。</p>

<p>核心问题在于，每个 PC (玩家) 以及有可能的话也包括 NPC 相互在不同的实体中（我没有有进程，因为不想被理解成 OS 的进程），他们在互动时，逻辑代码会读写别的对象的数据。最终有一个实体来保有和维护一个对象的所有数据，它提供一个 RPC 接口来操控数据固然是必须的。因为整个虚拟世界会搭建在多台物理机上，所以 RPC 是唯一的途径。这里可以理解成，每个实体是一个数据库，保存了实体的所有数据，开放一个 RPC 接口让外部来读写内部的这些数据。</p>

<p>但是，在高频的热点数据交互时，无论怎么优化协议和实现，可能都很难把性能提升到需要的水平。至少很难达到让这些数据都在一个进程中处理的性能。</p>

<p>这样，除了 RPC 接口，我希望再提供一个更直接的 api 采用共享状态的方式来操控数据。如果我们认为两个实体的数据交互很频繁，就可以想办法把这两个实体的运行流程迁移到同一台物理机上，让同时处理这两个对象的进程可以同时用共享内存的方式读写两者的数据，性能可以做到理论上的上限。</p>

<p>ok, 这就涉及到了，如何让一块带结构的数据被多个进程共享访问的问题。结构化是其中的难点。</p>

<p>方案如下：</p>
]]>
        <![CDATA[<hr />

<p>我们认为，需要交互和共享的数据，就是最终需要持久化到外存中的数据。整体上看，它好像一个小型内存数据库。它一定可以通过类似 google protocol buffers 的协议来序列化为二进制流。它和内存数据结构是有区别的。主要是一些约束条件，让这件事情可以简单点解决，又能满足能想到的各种需求。</p>

<p>数据类型是有限的：</p>

<ol>
<li>nil</li>
<li>number</li>
<li>boolean</li>
<li>string</li>
<li>map</li>
<li>array</li>
</ol>

<p>以上 6 种类型足够描述所有的需求，这在 lua 中已得到了证实。不过这里把 lua 的 table 拆分为 map 和 array 是对 protobuf 的一种借鉴。这里的 map 是有 data scheme 的，而不是随意的字典。即 key 一定是事先定义好的原子，在储存上其实是一个整数 id ，而 value 则可以是其它所有类型。</p>

<p>array 则一定是同类型数据的简单集合，且不存在 array 的 array 。这种方式的可行性在 protobuf 的应用中也得到了证实。</p>

<p>本质上，任何一个实体的所有数据，都可以描述为一个 map 。也就是若干 key-value 对的集合。array 只是相同 key 的重复（相当于 protobuf 里的 repeated）。</p>

<p>这里可以看出，除了 string 外，所有的 value 都可以是等长的，适合在 C 里统一储存。每个条目就是 id - type - value - brother 的一组记录而已。</p>

<p>其中 map 用二叉树的方式储存就可以满足节点的定长需求，左子树是它的第一个儿子，右子树是它的兄弟。</p>

<p>我们用一个固定内存块来保存整块数据，里面都是等长的记录，map 的记录中，左右子树都用保存着全局记录序号。</p>

<p>string 需要单独储存，所有的 string 都额外保存在另一片内存中（也可以是同一片内存的另一端）。在记录表中，记录 string 内容在 string pool 中的位置。</p>

<p>这样做有什么好处？</p>

<p>由于数据有 scheme（可以直接用 protobuf 格式描述），所以数据在每个层次上的规模是可以预估的，数据都是以等长记录保存的，对整个数据块的修改都可以看成是对局部数据的修改或是对整体的追加。这两个操作恰巧都可以做成无锁的操作。</p>

<p>换句话说，每次对整颗树具体一个节点的修改，都绝对不会损坏其它节点的数据。</p>

<hr />

<p>有了这块组织好的数据结构有什么用呢？首先持久化问题就不是问题，但这只是一个附带的好处。这块数据虽然能完整的记录各种复杂的结构数据，但不利于快速检索。我们需要在对这颗树的访问点，制作一个索引结构。如果导入到 lua 中，就是一个索引表。当我们第一次需要访问这颗树的特定节点：体现为读写 xxx.yyy.zzz 的形式，我们遍历这颗树，可以方便的找到节点的位置。大约时间复杂度是 O(N^2) ：要遍历 N 个层次，每个层次上要遍历 M 个节点。当然这里 N 和 M 都很小。</p>

<p>一旦找到节点的位置，我们就可以在 lua 中记录下这个绝对位置。因为每个节点一旦生成出来，就不会改变位置了。下次访问时，可以通过这个位置直接读写上面的数据了。</p>

<hr />

<p>string 怎么办呢？我的想法是开一个 double buffer 来保存 string 。string 和节点是一一对应的关系。当节点上的 string 修改时，就新增加一个 string 到 pool 里，并改变引用关系。当一个 string pool 满后，可以很轻易的扫描整个 string pool ，找到那些正在引用的 string ，copy 到另一个 string pool 中。这个过程比一般的 GC 算法要简单的多。</p>

<hr />

<p>最后就是考虑读写锁的问题了。只有一些关键的地方需要加锁，而大部分情况下都可以无锁处理。甚至在特定条件下，整个设计都可以是无锁的。</p>

<p>btw, 这一周剩下来的时间就是实现了。多说无益，快速实现出来最有说服力。按照惯例，应该会开源。</p>
]]>
    </content>
</entry>
<entry>
    <title>pbc 库的 lua binding</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2011/12/pbc_lua_binding.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=732" title="pbc 库的 lua binding" />
    <id>tag:blog.codingnow.com,2011://1.732</id>
    
    <published>2011-12-14T14:44:20Z</published>
    <updated>2011-12-16T03:32:45Z</updated>
    
    <summary>前几天写的 pbc 初衷就是想可以方便的 binding 到动态语言中去用的。所以今天花了整整一天自己写了个简单的 lua binding 库，就是很自然的工作了。 写完了之后，我很好奇性能怎样，就写了一个非常简单的测试程序测了一下。当然这个测试不说明很多问题，因为测试用的数据实在是太简单了，等明天有空再弄个复杂点的来跑一下吧。我很奇怪，为什么 google 官方的 C++ 版性能这么差。 我的 lua 测试代码大约是这样的： local protobuf = require &quot;protobuf&quot; addr = io.open(&quot;../../build/addressbook.pb&quot;,&quot;rb&quot;) buffer = addr:read &quot;*a&quot; addr:close() protobuf.register(buffer) for i=1,1000000 do local person = { name = &quot;Alice&quot;,...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="lua与虚拟机" />
            <category term="网络与安全" />
            <category term="优化与技巧" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>前几天写的 <a href="https://github.com/cloudwu/pbc">pbc</a> 初衷就是想可以方便的 binding 到动态语言中去用的。所以今天花了整整一天自己写了个简单的 lua binding 库，就是很自然的工作了。</p>

<p>写完了之后，我很好奇性能怎样，就写了一个非常简单的测试程序测了一下。当然这个测试不说明很多问题，因为测试用的数据实在是太简单了，等明天有空再弄个复杂点的来跑一下吧。我很奇怪，为什么 google 官方的 C++ 版性能这么差。</p>

<p>我的 lua 测试代码大约是这样的：</p>

<pre>
local protobuf = require "protobuf"

addr = io.open("../../build/addressbook.pb","rb")
buffer = addr:read "*a"
addr:close()
protobuf.register(buffer)

for i=1,1000000 do
    local person = {
        name = "Alice",
        id = 123,
    }
    local buffer = protobuf.encode("tutorial.Person", person)
    local t = protobuf.decode("tutorial.Person", buffer)
end
</pre>

<p>100 万次的编码和解码在我目前的机器上，耗时 3.8s 。</p>
]]>
        <![CDATA[<p>为了适应性能要求极高的场合，我还提供了另一组高性能 api 。他们可以把数据平坦展开在 lua 栈上，而不构成 table 。只需要把循环里的代码换成</p>

<pre>
    local buffer = protobuf.pack(
        "tutorial.Person name id",
         "Alice", 123)
    protobuf.unpack("tutorial.Person name id", buffer)
</pre>

<p>就可以了。这个版本只需要耗时 0.9s 。</p>

<p>一个月前，我曾经自己用 luajit + ffi 实现过一个纯 lua 的版本（没有开源），我跑了一下这个 case ，那个版本也很给力，达到前面的接口的功能，只需要 2.1s 。</p>

<p>不过我相信我新写的 binding 慢主要还是慢在 lua 上， 我换上了 luajit 跑以后，果然快了很多。</p>

<p>table 版本的耗时 1.7s , 平坦展开版是 0.57s.</p>

<p>看来 luajit  的优化力度很大。</p>

<p>btw, 我去年早些时候还写过一个 lua binding ，今天也顺便测了一下，在 luajit 下跑的时间是 1.2s 。没有这次写的这个版本快。</p>

<p>最后，我随手写了一个 C++ 的版本。应该有不少优化途径。不过我想这也是某中常规用法。</p>

<pre class="mtc_block"><span class="c_Preproc def_Directive">#<span class="c_PreprocInclude def_Path def_URI">include <span class="c_IncludeOutline def_Outlined def_Special"><span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairStart def_Special">&lt;</span></span>iostream<span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairEnd def_Special">&gt;</span></span></span></span></span>
<span class="c_Preproc def_Directive">#<span class="c_PreprocInclude def_Path def_URI">include <span class="c_IncludeOutline def_Outlined def_Special"><span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairStart def_Special">&lt;</span></span>sstream<span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairEnd def_Special">&gt;</span></span></span></span></span>
<span class="c_Preproc def_Directive">#<span class="c_PreprocInclude def_Path def_URI">include <span class="c_IncludeOutline def_Outlined def_Special"><span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairStart def_Special">&lt;</span></span>string<span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairEnd def_Special">&gt;</span></span></span></span></span>
<span class="c_Preproc def_Directive">#<span class="c_PreprocInclude def_Path def_URI">include <span class="c_IncludeOutline def_Outlined def_Special"><span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairStart def_Special">&quot;</span></span>addressbook.pb.h<span class="c_PreprocIncludeEdge def_StringEdge def_String"><span class="def_PairEnd def_Special">&quot;</span></span></span></span></span>
<span class="c_KeywordANSI def_Keyword">using</span> namespace std<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

<span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">int</span> <span class="c_FuncOutline def_Outlined def_Special"><span class="c_KeywordLibFunctions def_FunctionKeyword def_Keyword">main</span></span><span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">int</span> argc<span class="c_Symbol def_Symbol">,</span> <span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">char</span><span class="c_Symbol def_Symbol">*</span> argv<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">[</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">]</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span> <span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
  GOOGLE_PROTOBUF_VERIFY_VERSION<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>


  <span class="c_KeywordANSI def_Keyword">for</span> <span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">int</span> i<span class="c_Symbol def_Symbol">=</span><span class="def_NumberDec def_Number">0</span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>i<span class="c_Symbol def_Symbol">&lt;</span><span class="def_NumberDec def_Number">1000000</span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>i<span class="c_Symbol def_Symbol">+</span><span class="c_Symbol def_Symbol">+</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span> <span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
      tutorial<span class="c_PrefixSymbol def_SymbolStrong def_Symbol">::</span>Person person<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

      person<span class="c_Symbol def_Symbol">.</span>set_name<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_String def_String">&quot;Alice&quot;</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
      person<span class="c_Symbol def_Symbol">.</span>set_id<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="def_NumberDec def_Number">123</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

      stringstream output<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

      person<span class="c_Symbol def_Symbol">.</span>SerializeToOstream<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol">&amp;</span>output<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
      output<span class="c_Symbol def_Symbol">.</span>str<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
      tutorial<span class="c_PrefixSymbol def_SymbolStrong def_Symbol">::</span>Person person2<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

      person2<span class="c_Symbol def_Symbol">.</span>ParseFromIstream<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol">&amp;</span>output<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

      person<span class="c_Symbol def_Symbol">.</span>name<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
      person<span class="c_Symbol def_Symbol">.</span>id<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
  <span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span>

  google<span class="c_PrefixSymbol def_SymbolStrong def_Symbol">::</span>protobuf<span class="c_PrefixSymbol def_SymbolStrong def_Symbol">::</span>ShutdownProtobufLibrary<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

  <span class="c_KeywordANSI def_Keyword">return</span> <span class="def_NumberDec def_Number">0</span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span></pre>

<p>这段代码在开了 -O2 编译后，在我的机器上依旧需要时间 1.9s。若是这么看，那简直是太慢了 (比 luajit + c binding 还慢)。很久没研究 C++ 的细节，也懒得看了，如果谁有兴趣研究一下为什么 C++ 这么慢，我很有兴趣知道原因。</p>

<hr />

<p>12 月 16 日</p>

<p>留言中 lifc0 说这段 C++ 代码中开销最大的是 stringstream 的构造和销毁, 所以我改了一段代码:</p>

<pre class="mtc_block">stringstream output<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
stringstream input<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

<span class="c_KeywordANSI def_Keyword">for</span> <span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_KeywordANSI_typenames c_KeywordANSI def_Keyword">int</span> i<span class="c_Symbol def_Symbol">=</span><span class="def_NumberDec def_Number">0</span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>i<span class="c_Symbol def_Symbol">&lt;</span><span class="def_NumberDec def_Number">1000000</span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>i<span class="c_Symbol def_Symbol">+</span><span class="c_Symbol def_Symbol">+</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span> <span class="def_SymbolStrong def_Symbol"><span class="def_PairStart def_Special">{</span></span>
    output<span class="c_Symbol def_Symbol">.</span>clear<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    output<span class="c_Symbol def_Symbol">.</span>str<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_String def_String">&quot;&quot;</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

    tutorial<span class="c_PrefixSymbol def_SymbolStrong def_Symbol">::</span>Person person<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    person<span class="c_Symbol def_Symbol">.</span>set_name<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_String def_String">&quot;Alice&quot;</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    person<span class="c_Symbol def_Symbol">.</span>set_id<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="def_NumberDec def_Number">123</span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

    person<span class="c_Symbol def_Symbol">.</span>SerializeToOstream<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol">&amp;</span>output<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

    input<span class="c_Symbol def_Symbol">.</span>clear<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    input<span class="c_Symbol def_Symbol">.</span>str<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span>output<span class="c_Symbol def_Symbol">.</span>str<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

    tutorial<span class="c_PrefixSymbol def_SymbolStrong def_Symbol">::</span>Person person2<span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

    person2<span class="c_Symbol def_Symbol">.</span>ParseFromIstream<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol">&amp;</span>input<span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>

    person2<span class="c_Symbol def_Symbol">.</span>name<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
    person2<span class="c_Symbol def_Symbol">.</span>id<span class="c_Symbol def_Symbol"><span class="def_PairStart def_Special">(</span></span><span class="c_Symbol def_Symbol"><span class="def_PairEnd def_Special">)</span></span><span class="c_StructureSymbol def_SymbolStrong def_Symbol">;</span>
<span class="def_SymbolStrong def_Symbol"><span class="def_PairEnd def_Special">}</span></span></pre>

<p>这样更符合现实应用, 每次初始化 stringstream 而不构造新的出来.</p>

<p>这样运行时间就从 1.90s 下降到 1.18s 了.</p>
]]>
    </content>
</entry>
<entry>
    <title>蒙特霍尔问题与我那餐盒饭</title>
    <link rel="alternate" type="text/html" href="http://blog.codingnow.com/2011/12/monty_hall.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://linode.codingnow.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=1/entry_id=731" title="蒙特霍尔问题与我那餐盒饭" />
    <id>tag:blog.codingnow.com,2011://1.731</id>
    
    <published>2011-12-11T15:17:12Z</published>
    <updated>2011-12-11T16:22:22Z</updated>
    
    <summary>前几天写的盒饭的问题 有很大争议。我并不认为我的结论一定正确，但我想讨论这个问题的人忽略了许多现实的复杂性。 我想说，这是个真实事件，并不是因为我想说明什么问题编的故事。我依然相信，我最后如果做一个交换，会更好一些。不过不想为这个事情争论下去 :) 我觉得这个问题和蒙特霍尔问题有相似之处，但并不相同。我也没想仔细去计算概率，直想快速判断，换或不换哪种得到正确结果的可能性更大。 下面我想向有兴趣讨论说说我上次说的比较含糊的一些条件。毕竟这些条件只有当事人才注意的比较清楚：...</summary>
    <author>
        <name>云风</name>
        <uri>http://www.codingnow.com</uri>
    </author>
            <category term="概率与桥牌" />
            <category term="简悦" />
    
    <content type="html" xml:lang="en" xml:base="http://blog.codingnow.com/">
        <![CDATA[<p>前几天写的<a href="http://blog.codingnow.com/2011/12/probability.html">盒饭的问题</a> 有很大争议。我并不认为我的结论一定正确，但我想讨论这个问题的人忽略了许多现实的复杂性。</p>

<p>我想说，这是个真实事件，并不是因为我想说明什么问题编的故事。我依然相信，我最后如果做一个交换，会更好一些。不过不想为这个事情争论下去 :)</p>

<p>我觉得这个问题和<a href="http://zh.wikipedia.org/zh/%E8%92%99%E6%8F%90%E9%9C%8D%E7%88%BE%E5%95%8F%E9%A1%8C">蒙特霍尔问题</a>有相似之处，但并不相同。我也没想仔细去计算概率，直想快速判断，换或不换哪种得到正确结果的可能性更大。</p>

<p>下面我想向有兴趣讨论说说我上次说的比较含糊的一些条件。毕竟这些条件只有当事人才注意的比较清楚：</p>
]]>
        <![CDATA[<p>先谈谈，蒙提霍尔问题中，为什么参加游戏的人更换选择是更好的选择。</p>

<p>假设主持人完全不了解门背后的东西的话，我认为主持人的任何选择都不足以改变游戏者选中的概率。因为他们是独立事件。</p>

<p>但，一旦主持人了解了门背后的东西，并依据他的信息做有倾向性的选择话，这个选择就改变了之前的概率。</p>

<p>ok 回头来看我的盒饭问题。如果帮我订盒饭的人和其他人没有差异，且所有人做订饭这件事情上做出的选择没有差异性的话，我认为如果我了解到剩下的盒饭中每一份是什么的信息后，永远都应该选择同样种类最多的那一份，直到最后当牛展和香菇一样多，我选哪一份概率都一样。这是因为这些订饭事件相互之间是独立事件，没有影响概率。</p>

<p>选择同样种类最多的一份是因为牛展和牛展是一样的，即使我选的不是我订的那一份特定的牛展，而只要我订的是这个，就可以和别的定牛展的人互换。</p>

<hr />

<p>事实却比上面说的要复杂一点，因为前提条件并不如假设般的明确。事实上，前提条件在我的脑子里也是在不断变化猜测的。</p>

<p>我并不认为牛展更受欢迎。因为一共有将近 20 份快餐，里面可能有多份牛展，也有多份香菇，还有别的一些什么，我并不了解。</p>

<p>一大堆快餐刚到的时候，我并不指望自己可以了解到我订的什么，所以也不着急去拿。只想等大家都拿完了，我去捡剩下的。直到剩的不多时，检查了剩下的四份的内容才有了上面的故事。</p>

<p>可以注意到，一开始，我们可以认为，订饭的同学前提都一样，反正是盒饭，也无所谓什么好吃什么不好吃。所以在菜单上选菜的时候（菜单上不只牛展和香菇两种）从我的角度都无法确定他们每个人选了什么。所以就预设为订了什么的概率都是相同的。也就是说，如果菜单上有四种东西，特定的人订牛展的概率就是 1/4 ，如果只有两样，那么订香菇的概率也就是 1/2 。这是从订饭者的角度看的。</p>

<p>不过在这件事上，我是把吃饭的各位同学与行政 mm 分开看的。因为行政并不吃饭，只是帮我订。（她在快餐送到的时候并不在场，最后也没有留她的一份，说明她中午并不在公司吃快餐）</p>

<p>订饭给自己吃，和帮别人订饭，选菜的概率就不均等了吧。</p>

<hr />

<p>我最后的逻辑其实一句话就能说清楚：</p>

<p>当我不知道行政帮我订餐的偏好和选择的行为方式时（我不知道她怎样给我选了中饭），面对四份快餐，两种选择，前两个人都来拿走了牛展后，存在第三个人取走香菇的可能性是比较小的。</p>

<p>如果这么说还不清晰；那么假设有 100 个人来取饭，剩下有 100 份牛展，一份香菇；如果存在一个人定的香菇的话，那么根本不会让我等到最后两份这个人才出现。</p>

<p>因为，如果存在这个人，那么，他可能是第一个出现，可能是第二个出现，也可能是第三个出现。这个出现时机绝对是等概率的。没有任何理由，吃香菇的人就比别人晚吃饭。而我没有看到他出现，就很可能这个人并不存在。</p>

<p>如果这么说还不清晰；那么假设有 100 个人来取饭，剩下有 100 份牛展，一份香菇；如果存在一个人定的香菇的话，那么根本不会让我等到最后两份这个人才出现。</p>

<p>这样，回头从另一个角度看，大家订的什么饭却很难说是一个等概率事件了。作为自己的中餐，可能牛展更好吃，会偏好多一点；也有可能有从重心理，在订的时候看到别人订了这个，就也选这个，等等等等。</p>

<p>我可以认为吃饭的同学在做自己中饭选择的时候，前提条件相同；但可以认为帮我订饭却自己不吃的人在做选择的时候有另外的逻辑。最后为什么剩下更多的牛展，而不是四份不同的菜，或是两份牛展两份香菇，有可能是因为随机选择的一种选择，也可能是参与订饭的人的立场不同导致差异的正常结果。</p>

<p>这些差异，能够使我做出不同的选择。但我不了解所有的前提条件，所以只能通过已知的条件做判断：过来取饭的次序（他们出现的时间相差不到一分钟）的概率要比每个人选了什么菜的概率要明确的多。</p>

<hr />

<p>和蒙特霍尔问题类似的地方在于，主持人和游戏者立场不同；帮我订饭的人和给自己订饭的那些同学的立场也不同。</p>

<p>记得 10 年前跟人争论蒙特霍尔问题时，我编造过一个类似的问题，来说明我的观点：如果三个门后有一个有汽车，你和你的朋友去挑；你仔细检查了门，发现它们都是一样的，所以你觉得很难判断哪个几率更大，所以你选了其中一个；这时候你的朋友也上了，他也仔细检查了一遍，然后对你说，我比较确定是这一个（不是你选的那个）。这时你拉开你选的，失望的发现你的运气不佳；这个时候给你一个选择再选一次，你是选你朋友选的呢（相信他的判断），还是选另一扇呢？</p>

<p>生活中遇到需要抉择的问题时，我不会在脑子里去计算精确的概率。但我会简单的相信：我碰不到小概率事件。如果碰上了貌似很小概率才会发生的事情时，我更多的倾向于相信：一些我所不了解的事情中隐含了某些导致这个结果的条件。</p>

<p>对于上面最后那个问题，我会选择我的朋友选中的那扇门。因为我确定他和我的目的相同，都希望选中汽车。或许他只是和我一样是碰运气；但也可能是我没发现的线索，他却发现了。而后者的几率略大一些。</p>
]]>
    </content>
</entry>

</feed> 


