<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Liarod.v.RomanGol</title>
  
  <subtitle>RomanGoliard</subtitle>
  <link href="https://lijuanru.com/blog/atom.xml" rel="self"/>
  
  <link href="https://lijuanru.com/blog/"/>
  <updated>2024-08-18T13:56:38.365Z</updated>
  <id>https://lijuanru.com/blog/</id>
  
  <author>
    <name>Liarod.v.RomanGol</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Python Unix Domain Socket的UDP阻塞之谜</title>
    <link href="https://lijuanru.com/blog/2024/08/python_unix_udp/"/>
    <id>https://lijuanru.com/blog/2024/08/python_unix_udp/</id>
    <published>2024-08-17T22:00:00.000Z</published>
    <updated>2024-08-18T13:56:38.365Z</updated>
    
    <content type="html"><![CDATA[<p>此前开发网络数据发送和接收功能，一直使用C语言和标准的socket系统库代码，或是在高吞吐量的情况下使用Boost的ASIO库，最近需要测试一些简单的功能，就用Python写了一些基本的测试代码，但是在使用socket的过程中遇到了一些奇怪的问题。在解决问题的过程中，发现现在的搜索引擎的结果已经完全垃圾化了，可能前面10页提供的信息都是烂大街的网页（大家都忙着SEO，根本没有人关心质量，相信以后AI也会跟着傻瓜化）。最后还是通过自己写代码测试和翻阅官方文档解决了这个问题，遂决定写一篇博客来分享心得。</p><p>先说一下背景和问题：本地有一对进程A和B，通过Unix Domain Socket进行数据通信，A只负责发送信息，B只负责接收。由于不想要维护状态，使用的是UDP的通信方式，在Python代码里面就只需要简简单单写如下代码就可以发送数据了：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> socketsock <span class="token operator">=</span> socket<span class="token punctuation">.</span>socket<span class="token punctuation">(</span>socket<span class="token punctuation">.</span>AF_UNIX<span class="token punctuation">,</span> socket<span class="token punctuation">.</span>SOCK_DGRAM<span class="token punctuation">)</span>addr <span class="token operator">=</span> <span class="token string">"/root/domainSocketTest"</span>data <span class="token operator">=</span> <span class="token string">"domain socket test data"</span>sock<span class="token punctuation">.</span>sendto<span class="token punctuation">(</span>data<span class="token punctuation">,</span> addr<span class="token punctuation">)</span> <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接收侧的代码完全照抄官方，不过如果是Python 3.9以上的版本，官方代码用了<code>with</code>来启动<code>socketserver</code>，而下面的用法可以在3.9以下的Python环境运行。另外注意到接收侧作为服务器，要先手动删除一下指定的Unix Domain Socket文件（在这个例子里面是<code>/root/domainSocketTest</code>）。</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> socketserver<span class="token keyword">import</span> os<span class="token keyword">class</span> <span class="token class-name">SendNoticeHandler</span><span class="token punctuation">(</span>socketserver<span class="token punctuation">.</span>BaseRequestHandler<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">def</span> <span class="token function">handle</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>data <span class="token operator">=</span> self<span class="token punctuation">.</span>request<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span>data <span class="token operator">=</span> data<span class="token punctuation">.</span>decode<span class="token punctuation">(</span><span class="token string">"ascii"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>sock <span class="token operator">=</span> socketserver<span class="token punctuation">.</span>UnixDatagramServer<span class="token punctuation">(</span>addr<span class="token punctuation">,</span> SendNoticeHandler<span class="token punctuation">)</span>    addr <span class="token operator">=</span> <span class="token string">"/root/domainSocketTest"</span>sock<span class="token punctuation">.</span>serve_forever<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>看起来一点问题都没有，但是在实际生产环境里面给它点压力，就出现了奇怪的问题——<strong>如果服务器端这边在收到数据后进行一些计算量比较大的运算，似乎发送端那边的性能也会受到影响</strong>？可是这完全不应该啊，明明用的UDP的<code>sendto</code>发送方式，随便在网上搜索文档，都会告诉你这个函数是非阻塞的，而且用到了Unix Domain Socket，也不存在什么网络延迟，怎么会出现接收端和发送端“联动”的问题？</p><p>对于2024年的技术开发人员，遇到疑难杂症问AI和问搜索引擎，可能花（浪）费的时间比自己去想办法调试一下还要多得多。实际上，对于数据发送端，我们注意到两种特殊情况——<em>服务端代码还未启动</em>和<em>Unix Domain Socket文件被占用</em>——会导致代码运行出现异常，分别对应<code>FileNotFoundError</code>和<code>ConnectionRefusedError</code>，这个让人丈二和尚摸不着头脑，怎么你一个UDP数据传输还能感知到服务器端的状态了？</p><p>为了验证，我们不妨改写一下服务器端的代码，让它<strong>在收到数据后就sleep一秒钟</strong>：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> socketserver<span class="token keyword">import</span> os<span class="token keyword">class</span> <span class="token class-name">SendNoticeHandler</span><span class="token punctuation">(</span>socketserver<span class="token punctuation">.</span>BaseRequestHandler<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">def</span> <span class="token function">handle</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>data <span class="token operator">=</span> self<span class="token punctuation">.</span>request<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span>data <span class="token operator">=</span> data<span class="token punctuation">.</span>decode<span class="token punctuation">(</span><span class="token string">"ascii"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span>time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment"># take a rest</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>    addr <span class="token operator">=</span> <span class="token string">"/root/domainSocketTest"</span>    os<span class="token punctuation">.</span>system<span class="token punctuation">(</span><span class="token string">"rm "</span> <span class="token operator">+</span> addr<span class="token punctuation">)</span>    sock <span class="token operator">=</span> socketserver<span class="token punctuation">.</span>UnixDatagramServer<span class="token punctuation">(</span>addr<span class="token punctuation">,</span> SendNoticeHandler<span class="token punctuation">)</span>sock<span class="token punctuation">.</span>serve_forever<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后在发送端写一个for循环去发送10000条信息看看？果然被阻塞住了！！！这简直是不科学！！！</p><p>翻阅官方文档（参见 <a href="https://docs.python.org/3/library/socket.html">https://docs.python.org/3/library/socket.html</a> 可选择不同的Python版本，但是大同小异）会发现，在Python中<code>socket</code>是可以设置Non-blocking mode的，但是文档并没有说清楚socket在UDP而不是TCP的情况下会怎么样。因为我们在初始化的时候已经显式指定了<code>SOCK_DGRAM</code>，应该假设它会自动设置Non-blocking mode，难道代码没有这样处理？</p><p>为了深入验证，我们把发送端的代码改写一下，用class的形式来包装一下，并且在初始化的时候设置一下非阻塞模式：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">RegSocket</span><span class="token punctuation">:</span><span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> addr<span class="token punctuation">)</span><span class="token punctuation">:</span>self<span class="token punctuation">.</span>sock <span class="token operator">=</span> socket<span class="token punctuation">.</span>socket<span class="token punctuation">(</span>socket<span class="token punctuation">.</span>AF_UNIX<span class="token punctuation">,</span> socket<span class="token punctuation">.</span>SOCK_DGRAM<span class="token punctuation">)</span>self<span class="token punctuation">.</span>sock<span class="token punctuation">.</span>setblocking<span class="token punctuation">(</span><span class="token boolean">False</span><span class="token punctuation">)</span> <span class="token comment"># set as Non-blocking mode</span>self<span class="token punctuation">.</span>addr <span class="token operator">=</span> addr<span class="token keyword">def</span> <span class="token function">msg</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>data <span class="token operator">=</span> data<span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">"ASCII"</span><span class="token punctuation">)</span><span class="token keyword">try</span><span class="token punctuation">:</span>self<span class="token punctuation">.</span>sock<span class="token punctuation">.</span>sendto<span class="token punctuation">(</span>data<span class="token punctuation">,</span> self<span class="token punctuation">.</span>addr<span class="token punctuation">)</span> <span class="token keyword">except</span> <span class="token punctuation">(</span>FileNotFoundError<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"unix domain addr %s does not exist"</span><span class="token operator">%</span>self<span class="token punctuation">.</span>addr<span class="token punctuation">)</span><span class="token keyword">except</span> <span class="token punctuation">(</span>ConnectionRefusedError<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"cannot connect to unix domain addr %s"</span><span class="token operator">%</span>self<span class="token punctuation">.</span>addr<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后写个测试代码</p><pre class="line-numbers language-python" data-language="python"><code class="language-python">s <span class="token operator">=</span> RegSocket<span class="token punctuation">(</span><span class="token string">"/root/domainSocketTest"</span><span class="token punctuation">)</span><span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    m <span class="token operator">=</span> <span class="token string">"test for %d\n"</span><span class="token operator">%</span>i    <span class="token keyword">print</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span>s<span class="token punctuation">.</span>msg<span class="token punctuation">(</span>m<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>结果呢？这个循环运行不到10000次就抛出了<code>BlockingIOError</code>异常，果然是阻塞模式的错。这里实际上应该是系统的Unix Domain Socket缓冲区用完了（因为发送速度远远大于接收速度），而本来我们的网络编程知识储备默认的UDP发送方是不关心这个情况的（发送方只管把数据发出去，至于是网络协议栈的缓冲区满了还是服务器端收不到数据，根本不需要管），而在Python网络编程这里风格有点变化，因此需要程序员手工去处理下。如果需要处理，我们可以把上面那个class定义改一下，增加一个<code>BlockingIOError</code>异常处理，如果遇到阻塞就直接不管了（就是丢包嘛，没什么大不了的）：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">RegSocket</span><span class="token punctuation">:</span><span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> addr<span class="token punctuation">)</span><span class="token punctuation">:</span>self<span class="token punctuation">.</span>sock <span class="token operator">=</span> socket<span class="token punctuation">.</span>socket<span class="token punctuation">(</span>socket<span class="token punctuation">.</span>AF_UNIX<span class="token punctuation">,</span> socket<span class="token punctuation">.</span>SOCK_DGRAM<span class="token punctuation">)</span>self<span class="token punctuation">.</span>sock<span class="token punctuation">.</span>setblocking<span class="token punctuation">(</span><span class="token boolean">False</span><span class="token punctuation">)</span> <span class="token comment"># set as Non-blocking mode</span>self<span class="token punctuation">.</span>addr <span class="token operator">=</span> addr<span class="token keyword">def</span> <span class="token function">msg</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>data <span class="token operator">=</span> data<span class="token punctuation">.</span>encode<span class="token punctuation">(</span><span class="token string">"ASCII"</span><span class="token punctuation">)</span><span class="token keyword">try</span><span class="token punctuation">:</span>self<span class="token punctuation">.</span>sock<span class="token punctuation">.</span>sendto<span class="token punctuation">(</span>data<span class="token punctuation">,</span> self<span class="token punctuation">.</span>addr<span class="token punctuation">)</span> <span class="token keyword">except</span> <span class="token punctuation">(</span>FileNotFoundError<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"unix domain addr %s does not exist"</span><span class="token operator">%</span>self<span class="token punctuation">.</span>addr<span class="token punctuation">)</span><span class="token keyword">except</span> <span class="token punctuation">(</span>ConnectionRefusedError<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"cannot connect to unix domain addr %s"</span><span class="token operator">%</span>self<span class="token punctuation">.</span>addr<span class="token punctuation">)</span><span class="token keyword">except</span> <span class="token punctuation">(</span>BlockingIOError<span class="token punctuation">)</span><span class="token punctuation">:</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"send buf is full, throw: %s"</span><span class="token operator">%</span>data<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当然如果你还是不希望发送方丢包（那就得等待），那就注释掉<code>init</code>函数里面设置阻塞模式的代码就好了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;此前开发网络数据发送和接收功能，一直使用C语言和标准的socket系统库代码，或是在高吞吐量的情况下使用Boost的ASIO库，最近需要测试一些简单的功能，就用Python写了一些基本的测试代码，但是在使用socket的过程中遇到了一些奇怪的问题。在解决问题的过程中，发现现</summary>
      
    
    
    
    
    <category term="Coding" scheme="https://lijuanru.com/blog/tags/Coding/"/>
    
  </entry>
  
  <entry>
    <title>十二年，我们的约定</title>
    <link href="https://lijuanru.com/blog/2006/07/germany_football_2006/"/>
    <id>https://lijuanru.com/blog/2006/07/germany_football_2006/</id>
    <published>2006-07-04T22:00:00.000Z</published>
    <updated>2024-08-19T01:16:37.646Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2006-07-05 20:07</p></blockquote><p>十二年来，我第一次看着你们哭，也是第一次想哭。你们是英雄。</p><p>十二年前，足球对于我来说是新奇，是和美利坚对于哥伦布一样的新大陆，而你们已经在比赛前的报纸上引起了我的注意：“卫冕冠军，拥有马特乌斯，布雷默，克林斯曼，哈斯勒四驾马车……”，这些人是谁，我不知道，卫冕冠军的概念是什么，我也不知道，可是你们还是带给了我人生第一场足球比赛，忘记了体育场的名字，只记得草皮不是横条纹而是椭圆形的深浅交替，而你们也战胜了玻利维亚。</p><p>不去说你们3：2击败韩国的艰苦，不去说对战比利时的比赛中，当届世界杯最佳门将普雷德霍姆最后时刻冲入白衫阵营试图扳平比分的惊险。无数的人都记得了莱切科夫的秃头，斯托伊奇科夫的弧线，记得了《我爱我家》里面文兴宇的半带调侃的赌球。那一年巴乔是悲剧的男主角，但是我记得了一支足球队的名字，那是一见钟情么，我不知道，我知道从此以后，在我的记忆中只有你们是永远以一个团队的名字出现的，是我心里唯一的球队，不自觉的我将你们的命运和自己绑定在一起，似乎我们一起在守望着什么，可是这一望，便是十二年。</p><p>1996年我第一次离开家乡出去旅游，但是也错过了12年来你们的唯一一次狂欢。这是命运么。</p><p>1998年的夏天，很多我这个年纪的人，都在这一年等来了人生中第一次美丽而难忘的世界杯，很多人都从此爱上了巴西，荷兰，爱上了意大利，法国。可是我不能忘记你们，不能忘记4年前就埋在心中的那份等待。还记得南斯拉夫么，那支曾经在1990年1：4臣服的球队，他们让你们0：2落后，可是他们没有能够守住，没有能够守住世界上最坚韧的球队，也是世界上最应该以球队而不是以11人称呼的团体。尽管你们都已经老了，金色轰炸机已经不再翱翔，马特乌斯和哈斯勒，你们也不再虎虎生风。可是你们依然稳定的前进，直到那个“克罗地亚之夜”。</p><p>每次我回忆这场比赛，都会提到，这是我人生第一次熬夜看球，牺牲睡眠是不后悔的，因为在别人的睡梦中，我的人生中的一部分因为足球而与众不同的精彩。</p><p>可是我不能忘记这场比赛的伤痛，那种痛到那一瞬间，似乎张开口连话都说不出，呆呆的，静静的，悲伤的送别着那些老英雄，可是那种痛，从4年前带到了那一天。</p><p>2000，2004的夏天的痛和1994，和1998是不一样的痛，那是一种惨痛，一种输得无话可说，输得一无所有的惨。那时候支持着心爱的球队，看着其他的球队，心里面的酸楚无以言表，可是你们是我心中的球队，唯一的一支球队，每一个进球每一次胜利都能带给我最大的欢乐，每一次失败，只会愈发的增加我们的感情，每一次落寞后我都会静静的陪你们一起，我们都清楚，我们都有一个心愿，期待着重温旧日的辉煌的那一天。我知道，那一天来到的时候，我们会是多么的激动，为了那一天，我可以等下去，哪怕是一生。</p><p>这是一种感情吧，一种与子偕老的感情么，也许。</p><p>这一天差一点就来到了，就在2002年的夏天。</p><p>1998年过去后的4年里，每一天我都在关注着你们，一个一个的年轻人在成长，一个一个仍然在足球场上的老将也都在坚持。终于到了你们爆发的一天，8：0，第一次你们就爆发出了惊人的战斗力。卡恩，永远不会忘记你的英雄之歌，凭借一己之力将整个德国的球门牢牢的守住，那每一次扑救都如同海顿交响乐里面的一次华彩，在我心中留下难以抹去的激动。克洛泽，永远不会忘记你的头球和空翻，你让我看到了当年的穆勒，当年的鲁梅尼格，当年的克林斯曼，德国的前锋传统在你身上得到了继承。巴拉克，不会忘记你倒在半决赛的黄牌时候的那种悲伤，那种英雄落寞的悲伤，可是你依然战斗着将德国送入了决赛。还有梅策尔德，林克，你们的出色的防守，施奈德，你的出色的中场进攻……对的，我不会忘记，你们始终是一支球队，一支叫做德国的球队，一支以意志和团结为保证的球队，和你们一起压抑了8年后，我心中的那支德国队绝没有死去，他浴火重生了。</p><p>尽管只差一步，但是支持你们的所有人都满意了，是的，我们支持你们，但是你们也从来不曾让我们失望，每一次，你们都是战斗着离开，你们都是如英雄一样离开。当然，我们有一个2006的约定，名字叫冠军。</p><p>4年时光如梭，尽管4年间太多太多你们的坏消息，但是2006年你们还是如约定一样以最强者的身份来了，我等来了欢乐，是你们在世界面前带给我欢乐，是胜利的欢乐。4：2，首战你们就让我的支持没有落空，而且还附带了更多的惊喜。1：0，那是多么紧张的90分钟，甚至到最后一刻连坚持着和你们相守十二年的我都要放弃了，可是你们没有放弃，你们不仅仅是赢得了一场比赛，更是赢得了通向冠军的精神。3：0，你们吹响了最后总攻的号角。那一刻，我都要幸福得流泪了。</p><p>2：0，那是多么轻松的胜利，是多么酣畅淋漓的胜利。那一天你们赢了，那一天，我们所有人心中都有一个口号：“柏林，我们要去柏林！”。</p><p>我们在柏林等来了第一场恶战，等来了传说中你们最强的点球大战，那一刻我紧张得浑身发抖，那一刻我配不上德意志的精神，我是多么的害怕，你们是好样的，我们的约定，在4：2那一瞬间，永远无法更改！</p><p>十二年的光阴转眼即逝，每一次期待的机会都是那么的宝贵，带着最紧张最期待的心情，经历了5场恶战，经历了20几天的相随，终于这一次，我看到了我们的那个约定离我们是如此的近。我们真的要庆祝那个日子了！</p><p>可是十二年的梦想在119分钟轻轻的被击碎了，十二年的梦想就这样悄悄的，在清晨5点和我们挥手作别了，我明白你们为什么哭泣，因为我也在哭泣，泪水在这一刻尽情的释放吧，带着十二年前就预定下的期望。哭吧，哭完我们高昂着头。十二年来是你们一直在战斗，你们从来不曾让我失去信心，你们是我的英雄。不管失败还是胜利，永远支持着你们，这是别的球队永远不能取代的。</p><p>2006年7月5日的凌晨，我再一次和你们约定，我们要一起守望那个日子，那个高高举起奖杯，重温旧日的辉煌的日子。请相信我，德国队，You will never walk alone.</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2006-07-05 20:07&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;十二年来，我第一次看着你们哭，也是第一次想哭。你们是英雄。&lt;/p&gt;
&lt;p&gt;十二年前，足球对于我来说是新奇，是和美利坚对于哥伦布一样的新大陆，而你们已经在比赛前的报纸上引起了</summary>
      
    
    
    
    
    <category term="Football" scheme="https://lijuanru.com/blog/tags/Football/"/>
    
  </entry>
  
  <entry>
    <title>青春的衣裾</title>
    <link href="https://lijuanru.com/blog/2005/03/garment_of_youth/"/>
    <id>https://lijuanru.com/blog/2005/03/garment_of_youth/</id>
    <published>2005-03-11T23:00:00.000Z</published>
    <updated>2024-08-19T01:11:35.519Z</updated>
    
    <content type="html"><![CDATA[<p>我是一条清澈的河流<br />绕过你伫立的沙洲<br />在那个晴朗的夏日<br />有着许多白云的午后</p><p>你青春的衣裾<br />在风里飘扬<br />倒映在我心中<br />又像一条温柔的水草</p><p>带着甜蜜的痛楚<br />我频频回顾<br />我将流过，不再重回<br />此生将无法与你再相会</p><p>我知道冬必将来临<br />芦花也会凋尽<br />两岸的悲欢将如云烟<br />只留下群星在遥远的天边</p><p>在冰封之前，我将流入大海<br />而在幽暗的海底<br />我会将你想起<br />还有你那，还有你那<br />青春的衣裾</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;我是一条清澈的河流&lt;br /&gt;
绕过你伫立的沙洲&lt;br /&gt;
在那个晴朗的夏日&lt;br /&gt;
有着许多白云的午后&lt;/p&gt;
&lt;p&gt;你青春的衣裾&lt;br /&gt;
在风里飘扬&lt;br /&gt;
倒映在我心中&lt;br /&gt;
又像一条温柔的水草&lt;/p&gt;
&lt;p&gt;带着甜蜜的痛楚&lt;br /&gt;
我频频回顾</summary>
      
    
    
    
    
    <category term="Arts" scheme="https://lijuanru.com/blog/tags/Arts/"/>
    
  </entry>
  
  <entry>
    <title>软件开发的葵花宝典（原载于《程序员》2002年第6期）</title>
    <link href="https://lijuanru.com/blog/2002/06/sunflower_book_200206/"/>
    <id>https://lijuanru.com/blog/2002/06/sunflower_book_200206/</id>
    <published>2002-05-31T22:00:00.000Z</published>
    <updated>2024-08-19T07:59:22.983Z</updated>
    
    <content type="html"><![CDATA[<p>中国人大都喜欢用武侠小说来比较软件开发，但是在实战武功中，只有葵花宝典才是最厉害的，也只有掌握了葵花宝典，才能称为&quot;不败&quot;。 但什么才是软件开发的葵花宝典？</p><p>让我们先从一些现象出发。我们的前提是，软件开发是一项智力密集型劳动。对于智力密集型劳动，我们观察到的现象是，个体的表现差异很大，团队的表现差异很大，组织的表现差异很大，国家的表现差异很大。这不象体力占主要的劳动，象百米王跑百米的速度也仅比我快50%。但在棋类运动中，一个高手可以车轮战数位低手，而且毫无例外地将他们一一击败！</p><p>这些智力运动员表现出的特点是，计算精确而且速度快。其行为很象东方不败。虽然关于葵花宝典的传说很多，但最准确的描述只有一个字&quot;快&quot;。东方不败已经快到了吓人的地步。就象卡斯帕罗夫已快到了深蓝的地步。</p><p>有一则关于物理学家玻尔的轶事，有一次玻尔在普林斯顿大学听两个年青教授演讲他们的工作成果。期间玻尔突然发言说，如果照你们的研究算下去，会得到一个很有意思的推论。结果两个年青教授回去计算了两天，果然得出了同样的结论。玻尔是如何做到这样快的？</p><p>在软件开发中，我们同样注意到这样一种高手，他们可以每天写出一千行左右的高品质代码。他们可以运用已有的一些软件包，迅速完成一个新的产品。他们可以在很短的时间内，学会一项新的程序语言或是新技术。他们表现出一种神奇的速度。</p><p>在武侠小说中，所有的高手都有一些凡人不能企及的表现。象张无忌学太极，用龙爪手击败龙爪手名家；乔峰用太祖长拳击败天下英雄；姑苏慕容以其人之道还治其人之身，令狐冲一剑剌瞎十几双眼睛等等。我认为，之所以他们能做到这样，关键是在于他们快。</p><p>快并不意味着不准或品质差。快与品质并不矛盾。</p><p>高手的快，其实包含着很高的品质在其中。如果你因为高手的快，就质疑其品质，那就相当于在问：东方不败出手那么快，会不会刺不准？东方不败并不满足于刺死对手，他会在对手身上刺朵花。他把杀人变成了艺术。准确来说，他真正的兴趣不在杀人，而在于艺术。</p><p>退一步说，就算东方不败第一击有点偏差，他稍作修正后，马上跟上的第二第三击，也会击中他想击中的地方。在武功差的对手剑还没拨出来的时候，他已杀死对方并刺上了一朵花。</p><p>所以真正的软件高手，他并不满足于他的代码能有效地工作了，他认为编程是艺术，并醉心于其中。在低手能写出一个版本的时间里，他已经写出了第十版。其品质当然不可同日而语。就象一个九段棋手，在给定的时间里，他能计算十种可能，并将每种可能计算到100手之后，从中选择一种最有利的下法。低手岂有苟全的机会？</p><p>高手写软件总是不停地在重构(refactoring）。高手喜欢迭代式开发。高手说，增量就是打补丁，迭代就是推倒重来。对于软件这种东西，写一遍它可能OK（做到这一点也不容易），写十遍就是一个伟大的产品，再多写一遍它就更伟大些。</p><p>高手快的诀窍在于他很熟悉各种东西。高手看书很快，因为每一本新书里，值得他好好看的新技术只有一两章的内容。他能迅速看完，并准确领会这本书的中心思想和价值。而对于一个新手，每句话都是新的，他都需要去理解，每一段例子，他都需要去试。</p><p>很少看到一种100%全新的技术或理论。就象Java language specification里说的，Java没有使用任何新技术，用的都是业界久经考验的技术。对于高手来说，那些技术都是他所熟悉的。自然，很快他就从一个C++高手变成了Java高手。如果一个编程新手学Java，学两年也不如一个高手学两个月的。高手学新东西快。</p><p>高手写代码速度快。统计结果说，人均每人月的有效代码速度大概是300至400行。但那是业界平均生产效率。对于高手来说，这个数字太低了。每天写300至400行是完全有可能的。因为在写代码时，所有知识都已具备，已经没有任何需要他多花时间的事情了。他甚至很少需要Debug。</p><p>高手重用代码的能力很强，熟悉新的API的速度很快。这也是因为，他曾经使用过很多的API，重用过很多的代码。他知道哪些是可用的，哪些有缺陷。他既过用Qt，也用过gtk+，也用过windows API &amp; MFC，也用过AWT &amp; SWING。新的API对他来说，也是老熟人。</p><p>高手喜欢用轻量级的工具，象vi，notepad，最多到UltraEdit这样复杂的。高手用这种工具写出很多的东西。这些工具就象东方不败的针。那根针已具有神奇的魔力，有时候它可以当激光枪来用。</p><p>对于一些重量级的工具，高手虽不常用，但一经使出也威力大于常人。如果让东方不败用剑，最厉害的剑术名家也会败得很难看。高手其实用过很多的重量级工具，而且深知其优缺点。所以使出来，就会把威力发挥到最大，而把缺陷减少到最小。而低手则不然，总是把缺陷加以大大的发扬而浑不知其精髓何在。就象很多人学用UML、RUP、XP、Design pattern那样。</p><p>高手所学博杂且融会贯通。高手做什么都快，当低手还在一愁莫展的时候，高手已经圆满解决问题，去干别的事去了。</p><p>在成为高手的路上，要有热情，要循序渐进，要持之以恒。</p><p>要逼自己，书要快快地看。要试图迅速理解其主旨。其实你快快看所接受的信息量，与慢慢看接受的差不多。能明白多少很大程度上取决于你的功底。以后用到再回过头来看。一本对你来说新东西太多的书，不要指望看一次就全理解吸收。就象很多功力不够的人看design patterns那本书一样。慢慢看还不如找到多种信息来源，都快快看一遍。对于一个完全陌生的领域，只看一本书很远远不够的。</p><p>要逼自已，事要快快做。有一个朋友，几年前我介绍他去玩玩linux，他也表示想玩，但他现在还没碰过。他失去了很多机会。</p><p>平时要有意识提高自己写代码的速度，其实你一天写15行有效代码，与你写50行有效代码，其品质是差不多的。你应该把那些业界平均水平抛诸脑后，把超越自己做为唯一目标。等到你写了很多各式各样的代码，你的水平就不一般了。一个老师曾向我介绍他的学英语的决窍，他说你去啃原版小说，啃到50本，就和一般人有很大距离了。就是这个理。如果你写得太慢，怎么能写得多？水平怎么能提高？</p><p>要逼自己，学很多别人怕学的东西。低手总会说：这么多东西怎么学得过来啊。于是就少学或不学。这样就成不了高手了。高手有非常广的知识面，有很丰富的经验。知道很多低手不知道的事。玩过很多低手听都没听过的东西。</p><p>要逼自己，努力满足客户的各种需求。个人技能是在满足客户的各种需求的过程中提高的。比如你喜欢用Delphi，客户说一定要用VB，那你就答应他，然后把自己培养成为VB的高手。用户的需求看似变态，但对你是一个机会。</p><p>怎样才能做到看书快，写代码快，学新东西快，一个显而易见的途径就是将工作并行化。你在一台机器上make时，同时可以在看别的文档和聊天。对于计算机是这样，对人也是这样。如果你只能串行地处理问题，你的速度将提高有限。你的大脑有很大潜力可挖，它应该是一个多任务分时系统。努力减少它idle的时间。搞经济的Samuelson被人称为human brain main frame，可见他的大脑有多快。</p><p>让你的思维快起来，你就会区别于那些反应迟钝的人。如果你不能让人生的道路变长，就让它变宽。这世界变化快，需要你变得比它快才行。</p><p>这样加快并不会让你短命，相反，你有更多的时间来享受生活和锻炼身体。你的生活将更有品质，更丰富，更有意义。面对变化，你将立于不败之地。我们都是和自己赛跑的人，需要跑得比昨天的自己更快。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;中国人大都喜欢用武侠小说来比较软件开发，但是在实战武功中，只有葵花宝典才是最厉害的，也只有掌握了葵花宝典，才能称为&amp;quot;不败&amp;quot;。 但什么才是软件开发的葵花宝典？&lt;/p&gt;
&lt;p&gt;让我们先从一些现象出发。我们的前提是，软件开发是一项智力密集型劳动。对于智力密集型</summary>
      
    
    
    
    
    <category term="Coding" scheme="https://lijuanru.com/blog/tags/Coding/"/>
    
  </entry>
  
  <entry>
    <title>一种生活</title>
    <link href="https://lijuanru.com/blog/1985/05/aboutme/"/>
    <id>https://lijuanru.com/blog/1985/05/aboutme/</id>
    <published>1985-05-01T22:00:00.000Z</published>
    <updated>2024-08-19T01:10:59.099Z</updated>
    
    <content type="html"><![CDATA[<h2 id="原则"><a class="markdownIt-Anchor" href="#原则"></a> 原则</h2><ul><li>总想探寻未知领域的好奇心 + 凡事总想知道为什么的研究欲 + 总想拿出自己的东西的创新精神 + 解决问题的耐心和恒心</li><li>给理想以时间和勇气</li><li>源代码背后，了无秘密</li><li>Pauca sed matura</li><li>感性、冷静、客观与真正了解的精神</li><li>Wir muessen wissen. Wir werden wissen</li></ul><h2 id="白日梦"><a class="markdownIt-Anchor" href="#白日梦"></a> 白日梦</h2><ul><li>FC Bayern Fan</li><li>crAckEr</li><li>程序员 &amp; 0dayer</li><li>球探</li><li>业余自行车手</li><li>书店老板</li><li>教师</li><li>电视编导</li><li>天文爱好者</li><li>收藏家</li><li>棋手</li><li>伪装者</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;原则&quot;&gt;&lt;a class=&quot;markdownIt-Anchor&quot; href=&quot;#原则&quot;&gt;&lt;/a&gt; 原则&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;总想探寻未知领域的好奇心 + 凡事总想知道为什么的研究欲 + 总想拿出自己的东西的创新精神 + 解决问题的耐心和恒心&lt;/li&gt;
&lt;l</summary>
      
    
    
    
    
    <category term="Life" scheme="https://lijuanru.com/blog/tags/Life/"/>
    
  </entry>
  
</feed>
