<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Computer, Electron and Technology &#187; 优化</title>
	<atom:link href="http://www.donevii.com/post/tag/%e4%bc%98%e5%8c%96/feed" rel="self" type="application/rss+xml" />
	<link>http://www.donevii.com</link>
	<description>关注技术、移动互联网以及一切 GEEK &#38; NERD 的事情</description>
	<lastBuildDate>Wed, 21 Dec 2011 10:49:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>管理者，请你远离产品开发/设计部门[转]</title>
		<link>http://www.donevii.com/post/740.html</link>
		<comments>http://www.donevii.com/post/740.html#comments</comments>
		<pubDate>Mon, 12 Jan 2009 13:03:53 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[life]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[debug]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/740.html</guid>
		<description><![CDATA[先说一个身边真正发生过的故事，可能大家会对故事情节感觉非常熟悉。 一家小型电子商务公司，管理者就是公司的创始人，他有些技术开发背景。因为产品面向海外市场，他也比较关注产品... ]]></description>
			<content:encoded><![CDATA[<p>先说一个身边真正发生过的故事，可能大家会对故事情节感觉非常熟悉。</p>
<p>一家小型电子商务公司，管理者就是公司的创始人，他有些技术开发背景。因为产品面向海外市场，他也比较关注产品设计与可用性。由于他对自己的技术、设计能力的自信，总是对开发部与设计部成员的能力持怀疑态度，经常插手他们的工作。</p>
<p>一次，开发人员就新产品需求、设计文档的要求，提出开发方案A。这位管理者看后，以其经验判断认为不理想，并给出方案B。开发人员觉得方案B有些问题，但一时又没有找到有说服力的例证，无奈只好以此方案进行开发。</p>
<p>果然，在开发过程中，由于管理者仓促间所提出方案，在设计上确实存在不合理的疏漏，导致项目出现了一些问题。当开发人员想方设法将问题一一解决后，突然发现，这个DEBUG后的开发方案B，已经变得与他们最初提出的方案A并无二致。</p>
<p>而此时，该项目已经严重超期，那位管理者还为此非常不满，认为是开发人员能力的问题导致的。此后，管理者对开发工作的介入变得更多，而开发人员也开始变<br />
化，一部分对领导的这种超权行为越来越不满意，矛盾变得更加激化，后面工作中的沟通变得更加困难；另一部分则变得越来越没有积极性，领导要求怎么做就怎么<br />
做。</p>
<p>刚好看到UCDChina.com中的一篇文章《<a href="http://ucdchina.com/blog/?p=4" target="_blank">管理者不应直接参与产品的开发与设计</a>》，感觉说得太有道理了。上面这个故事，可以用来充实文章的论据。</p>
<p>其实团队管理者关注团队产品的成败，是非常正常的心态。他们插手设计、开发工作，无非是希望把产品更得更好，动机是无庸质疑的。尽管如此，还是不建议管理<br />
者过多的介入具体的开发与设计工作，即使是领域专家型的管理者，过多的插手具体设计、开发工作，也得不到预期的结果，可能对于项目来说，往往事与愿违，由<br />
此产生的恶性干扰多于良性的指导，这是得不偿失的。</p>
<p>为什么会这样？原因很多：</p>
<p>1、因为管理者与团队成员的权重不同，有管理者参与的项目，往往不能非常公平地对待其它项目涉众。尽管，管理者本意是以其对项目的理解和自身的经验，为设<br />
计、开发团队提供一些指导和建议，至少也会增加一种思路。但往往事与愿违，管理者介入后，一般会导致所有项目涉众的思维模式，会被管理者所左右，就因为他<br />
是团队的管理者。</p>
<p>大家想想，是否经常遇到这样的情况，设计、开发人员深思熟虑后形成的方案，经常因为管理者的一点儿质疑，而被否决；而管理者的灵光一闪，都可能形成一个决<br />
策，如果其它团队成员想到推翻它，必须经过周密、详实的举证与论述，这本就不公平，对项目也绝对没有好处，这会影响到团队成员的思路，甚至导致他们逐渐推<br />
动思考的动力。</p>
<p>2、因为技术背景与对项目的关注点不同，管理者与设计、开发人员会处在不同层面，看问题角度肯定是不同的。其实，能从多个不同角度看待产品，这本来是好事，可以把问题考虑得更全面一些。但如果以管理者的角度，去干扰设计、开发中的问题，那就是另外一码儿事了。</p>
<p>《论语•泰伯》：“子曰：不在其位，不谋其政。”</p>
<p>这句话说得非常好，这不是推卸责任意思，而恰恰是出于更负责的角度考虑。为了便于理解，这里把“位”理解为“职位”。</p>
<p>每个职位有不同的职责，职责会影响人的立场，不同立场、知识背景的人理解、处理同一个问题，其结果肯定是不一样的。也许管理者认为自己可以做到“换位思考<br />
”，即便如此也是搁靴搔痒的状态，其结果绝对差强人意。比如，管理者明白不能让UI设计师去考虑代码优化的问题，那又为什么自己去犯同样的错误呢？</p>
<p>正所谓术业有专攻，专业的工作还是让专业的人去做，那才可能达到令人满意的结果。</p>
<p>3、有人说，在IT行业，许多管理者都是优秀程序员或策划、设计师出身，这种领域专家型的管理者插手做具体设计、开发工作，应该有对项目、对团队成员有帮助。</p>
<p>其实不然，前面提到的两点问题，对于这样的管理者也依然存在。而且，文章开头提到的那位管理者也曾经是开发人员，本身也是位不错的产品设计师。为什么团队成员在他的指导下，项目进展依然不顺利呢？</p>
<p>从事过产品设计、程序开发工作的朋友应该很清楚，这样的工作需要投入大量的时间与精力，这是一个反复尝试与迭代的过程。好的设计与优秀的编码，绝对不是通<br />
过纸上谈兵或灵光一闪就可以得到的（当然，这个世界是有天才存在的，也许他们拍拍脑袋就可以把产品设计得非常完美，程序编码也可以没有任何BUG，但毕竟<br />
这种天才少得没有说服力）。有的时候，即使写好伪代码，也不能百之百的确保业务逻辑没问题。</p>
<p>而团队的管理者，往往事务缠身，他们没有精力（也不应该）深入于其中任何一项具体工作，那样就会犯下过于关注细节的管理大忌。而浅尝辄止的心态，恰恰又是做好设计与开发工作的大忌。这样看来，管理者兼顾设计、开发工作，并取得好的结果，根本是一项不可能完成的任务。</p>
<p>当然，领域专家型的管理者是可遇而不可求的，他丰富的经验与思路都是对团队成员有帮助的，但仅此而已，具体的设计、开发工作还是应该让设计师和程序员来做。</p>
<p>基于上面分析，如果管理者仍执意要插手设计与开发工作，就必须保证自己的观点（或决策）是100%准确与全面的，这样才不会对项目有负面影响。</p>
<p>当然，这样的要求是不合理的，也没有人敢做此承诺，所以，管理者们还去做好本职工作吧，那里才是发挥你们特长的地方。好的管理者，要懂得放权，要对团队成员应该有足够的信任，相信他们的工作能力，也相信他们的工作态度。正所谓：用人不疑，疑人不用。</p>
<p>如果管理者实在想参与设计、开发工作，那就干脆转行吧。</p>
<p>转自：http://<a href="http://www.donevii.com/post/tag/blog" class="st_tag internal_tag" rel="tag" title="Posts tagged with blog">blog</a>.sina.com.cn/s/<a href="http://www.donevii.com/post/tag/blog" class="st_tag internal_tag" rel="tag" title="Posts tagged with blog">blog</a>_564cabe30100bxmb.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/740.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>加快Flex应用启动速度的5种方式</title>
		<link>http://www.donevii.com/post/523.html</link>
		<comments>http://www.donevii.com/post/523.html#comments</comments>
		<pubDate>Tue, 30 Sep 2008 04:42:35 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[life]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[flash]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[缓存]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/523.html</guid>
		<description><![CDATA[Jun Heider在O’Reilly的InsideRIA站点上发表了一篇精彩的文章，该文章就如何加快Flex应用的启动速度提出了很多建议，以帮助用户减少看见讨厌的“Loading”对话框的出现时间。他深入探讨了问题的... ]]></description>
			<content:encoded><![CDATA[<p>Jun Heider在O’Reilly的InsideRIA站点上发表了一篇精彩的文章，该文章就<a href="http://www.insideria.com/2008/04/flex-ria-performance-considera.html">如何加快Flex应用的启动速度提出了很多建议</a>，以帮助用户减少看见讨厌的“Loading”对话框的出现时间。他深入探讨了问题的不同方面，并对每种技术的优势和劣势进行了评判。     </p>
<p><strong>从外部加载媒体（Media）</strong>     <br />Heider提到了一个常用的Flex最佳实践——限制嵌入到应用/SWF文件中的媒体的数量，如图像、影片及mp3等资源都可以从外部的SWF文件加载。 Flex框架可以直接将图片、mp3及字体等资源编译到SWF中。当你想让最终用户获得全部资源时，这种方式确实能派上用场，但是这会导致你的应用长时间停留在“Loading”阶段。中国最大的RIA分享社区-与中国闪客一起成长和发展！</p>
<p><strong>在嵌入式字体中限制字符集</strong>     <br />Heider建议在嵌入式字体中限制字符集以降低SWF文件的总下载时间： 当你在Flex中嵌入一种字体时，你就会获得该字体的全部字符的支持。尽管这可能是你想要的，但你确信你需要全部字符么？例如，在一个只面向英文的应用中，你确信你真的想花时间下载中文字符数据么？     <br /><strong>缓存框架</strong></p>
<p>Heider回顾了<a href="http://labs.adobe.com/wiki/index.php/Flex_3:Feature_Introductions:Flex_3_RSLs">Flex 3 support for runtime-shared-libraries （RSL）</a>这篇文章：从Flex 3开始，你可以将Adobe签名的框架——RSLs缓存到Flash Player的cache中。这有两个好处。首先，缓存在Flash Player cache中的签名的框架RSLs可由所有配置好的Flex应用共享。换句话说，如果某人的应用已经下载了500k的签名的框架RSL，并且该RSL仍旧 在Flash Player cache中，那么你的应用就可以使用缓存下来的RSL。其次，即使某人清空了其浏览器缓存，对Flash Player cache也没有任何影响。</p>
<p><strong>考虑模块化</strong>     <br />Heider谈到了将Flex应用划分成模块的好处：减少字体加载时间的另一种方式就是将你的Flex应用划分成模块。使用模块的一个好处在于当加载和卸载模块时你能完全操控它。     <br />之所以要划分成模块的最后一个原因是他们更快，而且我能即时加载它们。换句话说，在启动时唯一需要加载的模块就是 Step1.swf模块。因此，在使用模块的情况下，最终用户节省了启动时间，但是当他从一个模块切换到另一个模块时却需要花更多时间，因为每个模块都需 要以JIT形式加载。在我的应用中，只有当用户首次在steps 1-5之间切换时需要花更多时间。</p>
<p><strong>推迟实例化      <br /></strong>Heider围绕着Flex组件的“creationPolicy”属性及何时实例化应用的不同部分给出了很多建议。如果你想减少从数据下载到用户真正可以使用的总时间，当务之急就是推迟实例化。这项技术背后的理念就是直到应用真正使用的时候才在内存中创建对象。尽管推迟实例化技术会在应用的整个使用过程中导致少许——通常不那么明显——的延迟，但与长时间的启动延迟相比，它还是可接受的。推迟实例化的另一个好处在于内存使用的优化。 Heider还谈到了一个“实验性”的条款——“使用流”，这是他在讨论Dirk Eismann的帖子（<u>Building monolithic </u><a href="http://blog.csdn.net/lixinye0123/archive/2008/06/01/Flex" class="broken_link">Flex</a><u> SWFs that still startup quickly.”</u>）时谈及的。Eismann提出一项技术以利用Flash Player中的多个frames以在部分应用中达到流的目的。查看<a href="http://www.insideria.com/2008/04/flex-ria-performance-considera.html">所有的帖子</a>以更多地了解该技术及关于加快Flex启动速度的建议。中国最大的RIA分享社区-与中国闪客一起成长和发展！) </p>
<p><strong>原文出处：</strong><a href="http://www.infoq.com/cn/news/2008/05/flex-startup-time">http://www.infoq.com/cn/news/2008/05/flex-startup-time</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/523.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>google背后的分布式架构</title>
		<link>http://www.donevii.com/post/504.html</link>
		<comments>http://www.donevii.com/post/504.html#comments</comments>
		<pubDate>Tue, 23 Sep 2008 08:48:29 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[life]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[类]]></category>
		<category><![CDATA[缓存]]></category>
		<category><![CDATA[芯片]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/504.html</guid>
		<description><![CDATA[　　Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用 (比如那个大堂里的地球模型)，更在于其有别常规的IT策略…… 　　加利福尼亚州山景城(Mountain View)Google公司(Google... ]]></description>
			<content:encoded><![CDATA[<p>　　Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用  (比如那个大堂里的地球模型)，更在于其有别常规的IT策略……</p>
<p>　　加利福尼亚州山景城(Mountain  View)Google公司(Google，下称Google)总部有一个43号大楼，该建筑的中央大屏幕上显示着一个与Google地球(Google  Earth)相仿的世界地图，一个转动的地球上不停地闪动着五颜六色的光点，恍如罗马宫廷的千万烛灯，每一次闪动标志着地球的这个角落一名Google用  户发起了一次新的搜索。</p>
<p>　　这同时意味着Google又一次满足了人们对未知信息的好奇与渴望。</p>
<p>　　Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用  (比如那个大堂里的地球模型)，更在于其有别常规的IT策略。从人们的常理来看，简单的硬件商品和免费软件是无法构建出一个帝国的，但是Google做到  了。在性能调整后，Google把它们变成一个无可比拟的分布式计算平台，该平台能够支持大规模的搜索和不断涌现的新兴应用。我们原本认为这些应用都是个  人消费级别的，但是Google改变了这一切。现在商业世界也在使用它们，这就令这家搜索公司显得那么与众不同。</p>
<p>　　GoogleWeb  服务背后的IT架构对无数使用搜索引擎的用户来说也许并不是非常重要，但它是Google几百位致力于把全球信息组织起来，实现“随处可达，随时可用”目  标的工程师们的最核心工作。这就需要一个在覆盖范围和野心上都与Google的商业愿景完全相符的IT蓝图作为支撑。</p>
<p>　　Google  的经理们一直对公司的IT策略话题保持沉默，他们厌恶谈及特定的厂商或者产品，当被问到他们的服务器和数据中心时，他们总是闭口不谈。但与几位  Google的IT领导一起呆了一天后，我们最终得以揭示该公司的IT是如何运作的，那可不仅仅是一个运行在无数服务器集群上的、表面看来非常简单的搜索  引擎。在其简单的外表下，蕴涵着许多内部研发软件、定制硬件、人工智能，以及对性能的执着追求和打破常规的人力管理模式。</p>
<p>　　IT理念方面，Google对同行有一条建议：尽量避免那些人人都在使用的系统和软件，以自己的方式做事会更有独特的竞争优势。</p>
<p>　　“企业文化决定了你的做事方式。”道格拉斯&#8221;美林(Douglas  Merrill)，这位Google工程副总裁和事实上的首席信息官(CIO)  指出，“到了我们这样的发展阶段，企业观念和文化非常与众不同，这也反过来鞭策我们必须要采用与众不同的方式来运行那些他人看来很常规的系统。”<br />
　　Google  最大的IT优势在于它能建造出既富于性价比(并非廉价)又能承受极高负载的高性能系统。因此IT顾问史蒂芬&#8221;阿诺德(Stephen  Arnold)指出，Google与竞争对手，如亚马逊网站(Amazon)、电子港湾公司(eBay)、微软公司(Microsoft，下称微软)和雅 虎公司  (Yahoo，下称雅虎)等公司相比，具有更大的成本优势。Google程序员的效率比其他Web公司同行们高出50%～100%，原因是Google已  经开发出了一整套专用于支持大规模并行系统编程的定制软件库。据他估算，其他竞争公司可能要花上四倍的时间才能获得同等的效果。</p>
<p>　　打造服务器</p>
<p>　　Google  究竟是怎样做到这点的呢?其中一个手段，美林认为，“是因为我们自己动手打造硬件。”Google并不制造计算机系统，但它根据自己的参数定制硬件，然后  像MTV的节目“靓车打造”(Pimp My Ride)那样自己安装和调整硬件系统。开源程序经理克里斯&#8221;迪博纳(Chris  DiBona)评论道：“我们很善于购买商业服务器，并且改造他们为我们所用，最后把性能压榨和发挥到极致，以致有时候他们热得像要融化了似的。”</p>
<p>　　这种亲手打造的方式，来源于Google从车库诞生时与生俱来的节俭风格，更与Google那超大型的系统规模息息相关，良好的习惯一直延续至  今。据说 Google在65个数据中心拥有20万～45万台服务器—这个数目会有偏差(取决于你如何定义服务器和由谁来做这项统计)。但是，不变的是持续上升的趋势。</p>
<p>　　Google不会去讨论这些资产，因为它认为保密也是一种竞争优势。事实上，Google之所以喜欢开源软件也是因为它的私密性。“如果我们购  买了软件许可或代码许可，人们只要对号入座，就可以猜出Google的IT基础架构。”迪博纳分析说，  “使用开源软件，就使我们多了一条把握自己命运的途径。”</p>
<p>　　Google喜欢规模化的服务器运行方式。当有成百上千台机器时，定制服务器的优势也会成倍增加，效果也会更趋明显。Google正在俄勒冈州  哥伦比亚河边的达勒斯市建造一个占地30亩的数据中心，在那儿它可以获得运算和降温需要的低价水力电力能源(参见边栏《Google数据中心自有一套》)。</p>
<p>　　Google以“单元”(Cell)的形式组织这些运行  Linux操作系统的服务器，迪博纳把这种形式比喻成互联网服务的“磁盘驱动器”(但别和一直谣传的Google存储服务Gdrive混淆了，“并没有  Gdrive这回事。”一位Google女发言人明确表示。)，公司的软件程序都驻扎在这些并不昂贵的电脑机箱里，由程序员决定它们的冗余工作量。这种由  很多单元组成的文件系统代替了商业存储设备;迪博纳表示Google这些单元设备更易于建造和维护，他还暗示他们能处理更大规模的数据。</p>
<p>　　Google  不会漏过对任何技术细节的关注。多年来，公司的工程师就在研究微处理器的内部工作机制，随着Google规模的持续壮大，必然会用到特别定制和调节过的芯  片。知名工程师路易斯&#8221;巴罗索(Luiz  Barroso)去年在一篇发表在工业杂志上的论文中证实，近年来Google的主要负荷都由单核设计的系统承担着。但许多服务器端的应用，如  Google搜索索引服务，所需的并行计算在单核芯片的指令级别上执行得并不好。</p>
<p>　　曾在数据设备公司(Digital  Equipment)和康柏公司(Compaq)当过芯片设计师的巴罗索认为，随着AMD公司、英特尔公司(Intel)、太阳计算机系统公司(Sun)开始制造多核芯片，必将会出现越来越多芯片级别的并行计算。</p>
<p>　　Google  也曾考虑过自己制造计算机芯片，但从业界潮流来看，这个冒险的举动似乎不是很必要。“微处理器的设计非常复杂而且成本昂贵，”运营高级副总裁乌尔斯&#8221;霍尔 茨勒(Urs  Holzle)表示。Google宁愿与芯片制造商合作，让他们去理解自己的应用并设计适合的芯片。这是一种客户建议式的设计，其关注点在于总体吞吐量、  效能，以及耗电比，而不是看单线程的峰值性能。霍尔茨勒表示，“这也是最近多核CPU的设计潮流与未来方向。”<br />
裁缝般地定制软件</p>
<p>　　为了能尽量压榨硬件性能，Google开发了相当数量的定制软件。创新产品主要包括用于简化处理和创建大规模数据集的编程模型  MapReduce;用于存储和管理大规模数据的系统BigTable;分析分布式运算环境中大规模数据集的解释编程语言Sawzall;用于数据密集型  应用的分布式文件系统的 “Google文件系统”(Google File  System);还有为处理分布式系统队列分组和任务调度的“Google工作队列”(Google  Workqueue)。</p>
<p>　　正是从Sawzall这些工具里体现出Google对计算效率的执著关注。并不是每家公司都能从底层去解决效率问题，但是对Google来说，  为常规关系型数据库无法容纳的大规模数据集专门设计一种编程语言是完全合理的。即使其他编程工具可以解决问题，Google的工程师们仍然会为了追求效率  而另外开发一套定制方案。Google工程师认为，Sawzall能与C++中的MapReduce相媲美，而且它更容易编写一些。</p>
<p>　　Google  对效率的关注使它不可能对标准Linux内核感到满意;Google会根据自己的需要运行修改过的内核版本。通过调整Linux的底层性能，Google  工程师们在提高了整体系统可靠性的基础上，还一并解决了数据损坏和数据瓶颈等一系列棘手问题。对内核的修改也使Google的计算机集群系统因为通信效率  的提高而运行得更快。</p>
<p>　　当然，Google偶尔也会出现系统故障，情况一旦发生，无数的用户就会受到影响了。三年前一次持续30分钟的系统故障使20%的搜索流量受到影响。</p>
<p>　　Google  开发了自己的网站服务器却没有使用开源的Apache服务器，尽管它在网站服务器的市场占有率超过60%。迪博纳认为，Google的网站服务器可以运行  在更多数量的主机上，对Google站点上内容庞大又彼此互相依赖的应用程序来说，这种服务器的负载均衡能力远比Apache的能力更高。同时，在用标准  公共网关接口(CGI)访问数据库动态网页方面，Google服务器的编程难度要比  Apache更高，但是最终运行速度却更快。“如果我们能够压榨出10%～20%的性能，我们就可以节省出更多系统资源、电量和人力了。”迪博纳在总结中指出。</p>
<p>　　Google还设计了自己的客户关系管理(CRM)系统用于支持自己基于竞价和点击的互联网广告收费业务。但对是否需要设计自己的工具，Google的态度也不是一成不变的。比如在财会软件上，它就使用了甲骨文公司(Oracle)的Financials软件。</p>
<p>　　美林拿着一只叉子举例说明现成的产品也可以带来价值。但在有些场合现成的软件产品就不一定适用了。“我们的文化在各个层面对我们的运作都有深远影响，”他表示，“所以我们不想让购买所得的工具改变我们的工作方式和文化层面。”<br />
Google&#8217;s  BigTable 原理 （翻译）</p>
<p>    题记：google 的成功除了一个个出色的创意外，还因为有 Jeff Dean  这样的软件架构天才。<br />
                                                  &#8212;&#8212; 编者</p>
<p>官方的  Google Reader <a href="http://www.donevii.com/post/tag/blog" class="st_tag internal_tag" rel="tag" title="Posts tagged with blog">blog</a> 中有对BigTable 的解释。这是Google 内部开发的一个用来处理大数据量的系统。这种系统适合处理半结构化的数据比如  RSS 数据源。 以下发言  是 Andrew Hitchcock  在 2005 年10月18号 基于： Google 的工程师 Jeff Dean  在华盛顿大学的一次谈话 (Creative Commons License).</p>
<p>首先，BigTable 从 2004  年初就开始研发了，到现在为止已经用了将近8个月。（2005年2月）目前大概有100个左右的服务使用BigTable，比如： Print,Search  History,Maps和 Orkut。根据Google的一贯做法，内部开发的BigTable是为跑在廉价的PC机上设计的。BigTable  让Google在提供新服务时的运行成本降低，最大限度地利用了计算能力。</p>
<p>BigTable 是建立在 GFS ，Scheduler ，Lock Service 和  MapReduce 之上的。</p>
<p>每个Table都是一个多维的稀疏图 sparse map。Table 由行和列组成，并且每个存储单元 cell  都有一个时间戳。在不同的时间对同一个存储单元cell有多份拷贝，这样就可以记录数据的变动情况。在他的例子中，行是URLs  ，列可以定义一个名字，比如：contents。Contents  字段就可以存储文件的数据。或者列名是：”language”，可以存储一个“EN”的语言代码字符串。</p>
<p>为了管理巨大的Table，把Table根据行分割，这些分割后的数据统称为：Tablets。每  个Tablets大概有 100-200 MB，每个机器存储100个左右的  Tablets。底层的架构是：GFS。由于GFS是一种分布式的文件系统，采用Tablets的机制后，可以获得很好的负载均衡。比如：可以把经常响应  的表移动到其他空闲机器上，然后快速重建。</p>
<p>Tablets在系统中的存储方式是不可修改的 immutable  的SSTables，一台机器一个日志文件。当系统的内存满后，系统会压缩一些Tablets。由于Jeff在论述这点的时候说的很快，所以我没有时间把听到的都记录下来，因此下面是一个大概的说明：</p>
<p>压缩分为：主要和次要的两部分。次要的压缩仅仅包括几个Tablets，而主要的压缩时关于整个系统的压缩。主压缩有回收硬盘空间的功能。Tablets的位置实际上是存储在几个特殊的BigTable的存储单元cell中。看起来这是一个三层的系统。<br />
客户端有一个指向METAO的Tablets的指针。如果METAO的Tablets被频繁使用，那个这台机器就会放弃其他的tablets专门支持  METAO这个Tablets。METAO tablets  保持着所有的META1的tablets的记录。这些tablets中包含着查找tablets的实际位置。（老实说翻译到这里，我也不太明白。）在这个系统中不存在大的瓶颈，因为被频繁调用的数据已经被提前获得并进行了缓存。</p>
<p>     现在我们返回到对列的说明：列是类似下面的形式：  family:optional_qualifier。在他的例子中，行：www.search-analysis.com   也许有列：”contents:其中包含html页面的代码。 “ anchor:cnn.com/news” 中包含着  相对应的url，”anchor:www.search-analysis.com/” 包含着链接的文字部分。列中包含着类型信息。<br />
     (翻译到这里我要插一句，以前我看过一个关于万能数据库的文章，当时很激动，就联系了作者，现在回想起来，或许google的 bigtable  才是更好的方案，切不说分布式的特性，就是这种建华的表结构就很有用处。)</p>
<p>     注意这里说的是列信息，而不是列类型。列的信息是如下信息，一般是：属性/规则。 比如：保存n份数据的拷贝或者保存数据n天长等等。当 tablets  重新建立的时候，就运用上面的规则，剔出不符合条件的记录。由于设计上的原因，列本身的创建是很容易的，但是跟列相关的功能确实非常复杂的，比如上文提到 的  类型和规则信息等。为了优化读取速度，列的功能被分割然后以组的方式存储在所建索引的机器上。这些被分割后的组作用于 列 ,然后被分割成不同的  SSTables。这种方式可以提高系统的性能，因为小的，频繁读取的列可以被单独存储，和那些大的不经常访问的列隔离开来。</p>
<p>在一台机器上的所有的  tablets  共享一个log，在一个包含1亿的tablets的集群中，这将会导致非常多的文件被打开和写操作。新的log块经常被创建，一般是64M大小，这个GFS的块大小相等。当一个机器down掉后，控制机器就会重新发布他的log块到其他机器上继续进行处理。这台机器重建tablets然后询问控制机器处理结构的存储位置，然后直接对重建后的数据进行处理。这个系统中有很多冗余数据，因此在系统中大量使用了压缩技术。</p>
<p>     Dean 对压缩的部分说的很快，我没有完全记下来，所以我还是说个大概吧：压缩前先寻找相似的 \行，列，和时间数据。</p>
<p>    他们使用不同版本的：  BMDiff 和 Zippy <a href="http://www.donevii.com/post/tag/%e6%8a%80%e6%9c%af" class="st_tag internal_tag" rel="tag" title="Posts tagged with 技术">技术</a>。</p>
<p>   BMDiff 提供给他们非常快的写速度： 100MB/s – 1000MB/s 。Zippy 是和 LZW  类似的。Zippy 并不像 LZW 或者 gzip 那样压缩比高，但是他处理速度非常快。</p>
<p>    Dean 还给了一个关于压缩 <a href="http://www.donevii.com/post/tag/web" class="st_tag internal_tag" rel="tag" title="Posts tagged with web">web</a>  蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面，行按照以下的方式命名：“com.cnn.www/index.<a href="http://www.donevii.com/post/tag/html" class="st_tag internal_tag" rel="tag" title="Posts tagged with html">html</a>:http”.在未压缩前的web  page 页面大小是：45.1 TB ，压缩后的大小是：4.2 TB ， 只是原来的 9.2%。Links 数据压缩到原来的 13.9% ,  链接文本数据压缩到原来的 12.7%。<br />
<span id="more-504"></span><br />
Google 还有很多没有添加但是已经考虑的功能。<br />
    1.   数据操作表达式，这样可以把脚本发送到客户端来提供修改数据的功能。<br />
    2. 多行数据的事物支持。<br />
    3.   提高大数据存储单元的效率。<br />
    4. BigTable 作为服务运行。<br />
    好像：每个服务比如： maps 和 search history  历史搜索记录都有他们自己的集群运行 BigTable。<br />
    他们还考虑运行一个全局的 BigTable  系统，但这需要比较公平的分割资源和计算时间。</p>
<p>大表(Bigtable):结构化数据的分布存储系统</p>
<p>http://labs.google.com/papers/bigtable-osdi06.pdf</p>
<p>｛中是译者评论,程序除外｝<br />
{本文的翻译可能有不准确的地方,详细资料请参考原文.}</p>
<p>摘要<br />
bigtable是设计来分布存储大规模结构化数据的，从设计上它可以扩展到上２^50字节，分布存储在几千个普通服务器上．google的很多项目使用  bt来存储数据，包括网页查询，google  earth和google金融．这些应用程序对bt的要求各不相同：数据大小（从URL到网页到卫星图象）不同，反应速度不同（从后端的大批处理到实时数  据服务）．对于不同的要求，bt都成功的提供了灵活高效的服务．在本文中，我们将描述bt的数据模型．这个数据模型让用户动态的控制数据的分布和结构．我  们还将描述BT的设计和实现．<br />
１．介绍<br />
在过去两年半里，我们设计，实现并部署了BT．BT是用来分布存储和管理结构化数据的．BT的设计使它能够管理2^50  bytes(petabytes)数据，并可以部署到上千台机器上．BT完成了以下目标：应用广泛，可扩展，高性能和高可用性（high availability）.  包括google analytics, google finance, orkut, personalized search, writely和google  earth在内的60多个项目都使用BT.这些应用对BT的要求各不相同，有的需要高吞吐量的批处理，有的需要快速反应给用户数据．它们使用的BT集群也各不相同，有的只有几台机器，有的有上千台，能够存储2^40字节(terabytes)数据．<br />
BT在很多地方和数据库很类似：它使用了很多数据库的实现策略．并行数据库[14]和内存数据库[13]有可扩展性和高性能，但是BT的界面不同．BT不支持完全的关系数据模型；而是为客户提供了简单的数据模型，让客户来动态控制数据的分布和格式{就是只存储字串，格式由客户来解释}，并允许客户推断底层存储数据的局部性｛以提高访问速度｝．数据下标是行和列的名字，数据本身可以是任何字串．BT的数据是字串，没有解释｛类型等｝．客户会在把各种结构或者半结构化的数据串行化｛比如说日期串｝到数据中．通过仔细选择数据表示，客户可以控制数据的局部化．最后，可以使用BT模式来控制数据是放在内存里还是在硬盘上．｛就是说用模式，你可以把数据放在离应用最近的地方．毕竟程序在一个时间只用到一块数据．在体系结构里，就是：locality,  locality,  locality｝<br />
第二节描述数据模型细节．第三节关于客户API概述．第四节简介BT依赖的google框架．第五节描述BT的实现关键部分．第6节叙述提高BT性  能的一些调整．第7节提供BT性能的数据．在第8节，我们提供BT的几个使用例子，第9节是经验教训．在第10节，我们列出相关研究．最后是我们的结论．<br />
２．数据模型<br />
BT是一个稀疏的，长期存储的｛存在硬盘上｝，多维度的，排序的映射表．这张表的索引是行关键字，列关键字和时间戳．每个值是一个不解释的字符数组．｛数据都是字符串，没类型，客户要解释就自力更生吧｝．<br />
(row:string,  column:string,time:int64)-&gt;string  {能编程序的都能读懂，不翻译了}<br />
我们仔细查看过好些类似bigtable的系统之后定下了这个数据模型。举一个具体例子（它促使我们做出某些设计决定），  比如我们想要存储大量网页及相关信息，以用于很多不同的项目；我们姑且叫它Webtable。在Webtable里，我们将用URL作为行关键字，用网页  的某些属性作为列名，把网页内容存在contents:列中并用获取该网页的时间戳作为标识，如图一所示。<br />
图一：一个存储Web网页的范例列表片断。行名是一个反向URL｛即com.cnn.www｝。contents列族｛原文用  family，译为族，详见列族｝ 存放网页内容，anchor列族存放引用该网页的锚链接文本。CNN的主页被Sports  Illustrater｛即所谓SI，CNN的王牌体育节目｝和MY-look的主页引用，因此该行包含了名叫“anchor:cnnsi.com”和  “anchhor:my.look.ca”的列。每个锚链接只有一个版本｛由时间戳标识，如t9，t8｝；而contents列则有三个版本，分别由时间  戳t3，t5，和t6标识。<br />
行<br />
表中的行关键字可以是任意字符串（目前支持最多64KB，多数情况下10－100字节足够了）。在一个行关键字下的每一个读写操作都是原子操作（不管读写这一行里多少个不同列），这是一个设计决定，这样在对同一行进行并发操作时，用户对于系统行为更容易理解和掌控。<br />
Bigtable通过行关键字的字典序来维护数据。一张表可以动态划分成多个连续行。连续行在这里叫做“子表”｛tablet｝，是数据分布和负载  均衡的单位。这样一来，读较少的连续行就比较有效率，通常只需要较少机器之间的通信即可。用户可以利用这个属性来选择行关键字，从而达到较好数据访问地域  性｛locality｝。举例来说，在Webtable里，通过反转URL中主机名的方式，可以把同一个域名下的网页组织成连续行。具体来说，可以把  maps.google.com/index.html中的数据存放在关键字com.google.maps/index.html下。按照相同或属性相  近的域名来存放网页可以让基于主机和基于域名的分析更加有效。<br />
列族<br />
一组列关键字组成了“列族”，这是访问控制的基本单位。同一列族下存放的所有数据通常都是同一类型（同一列族下的数据可压缩在一起）。列族必须先创  建，然后在能在其中的列关键字下存放数据；列族创建后，族中任何一个列关键字均可使用。我们希望，一张表中的不同列族不能太多（最多几百个），并且列族在  运作中绝少改变。作为对比，一张表可以有无限列。<br />
列关键字用如下语法命名：列族：限定词。  列族名必须是看得懂｛printable｝的字串，而限定词可以是任意字符串。比如，Webtable可以有个列族叫language，存放撰写网页的语  言。我们在language列族中只用一个列关键字，用来存放每个网页的语言标识符。该表的另一个有用的列族是anchor；给列族的每一个列关键字代表  一个锚链接，如图一所示。而这里的限定词则是引用该网页的站点名；表中一个表项存放的是链接文本。<br />
访问控制，磁盘使用统计，内存使用统计，均可在列族这个层面进行。在Webtable举例中，我们可以用这些控制来管理不同应用：有的应用添加新的基本数据，有的读取基本数据并创建引申的列族，有的则只能浏览数据（甚至可能因为隐私权原因不能浏览所有数据）。<br />
时间戳<br />
Bigtable表中每一个表项都可以包含同一数据的多个版本，由时间戳来索引。Bigtable的时间戳是64位整型。可以由Bigtable来  赋值，表示准确到毫秒的“实时”；或者由用户应用程序来赋值。需要避免冲突的应用程序必须自己产生具有唯一性的时间戳。不同版本的表项内容按时间戳倒序排  列，即最新的排在前面。<br />
为了简化对于不同数据版本的数据的管理，我们对每一个列族支持两个设定，以便于Bigtable对表项的版本自动进行垃圾清除。用户可以指明只保留表项的最后n个版本，或者只保留足够新的版本（比如，只保留最近7天的内容）。<br />
在Webtable举例中，我们在contents:列中存放确切爬行一个网页的时间戳。如上所述的垃圾清除机制可以让我们只保留每个网页的最近三个版本。<br />
3.API<br />
BT的API提供了建立和删除表和列族的函数．还提供了函数来修改集群，表和列族的元数据，比如说访问权限．<br />
//  Open the table<br />
Table *T = OpenOrDie(”/bigtable/web/webtable”);<br />
// Write a  new anchor and delete an old anchor<br />
RowMutation r1(T,  “com.cnn.www”);<br />
r1.Set(”anchor:www.c-span.org”,  “CNN”);<br />
r1.Delete(”anchor:www.abc.com”);<br />
Operation op;<br />
Apply(&amp;op,  &amp;r1);<br />
图 2:  写入Bigtable.<br />
在BT中，客户应用可以写或者删除值，从每个行中找值，或者遍历一个表中的数据子集．图2的c++代码是使用RowMutation抽象表示来进行一系列的更新（为保证代码精简，没有包括无关的细节）．调用Apply函数，就对Ｗebtable进行了一个原子修改：它为http://www.cnn.com/增加了一个锚点，并删除了另外一个锚点．<br />
Scanner  scanner(T);<br />
ScanStream *stream;<br />
stream =  scanner.FetchColumnFamily(”anchor”);<br />
stream-&gt;SetReturnAllVersions();<br />
scanner.Lookup(”com.cnn.www”);<br />
for  (; !stream-&gt;Done(); stream-&gt;Next()) {<br />
printf(”%s %s %lld  %s\n”,<br />
scanner.RowName(),<br />
stream-&gt;ColumnName(),<br />
stream-&gt;MicroTimestamp(),<br />
stream-&gt;Value());<br />
}<br />
图3:  从Bigtable读数据.<br />
图3的C++代码是使用Scanner抽象来遍历一个行内的所有锚点．客户可以遍历多个列族．有很多方法可以限制一次扫描中产生的行，列和时间戳．  例如，我们可以限制上面的扫描，让它只找到那些匹配正则表达式*.cnn.com的锚点，或者那些时间戳在当前时间前10天的锚点．<br />
BT还支持其他一些更复杂的处理数据的功能．首先，BT支持单行处理．这个功能可以用来对存储在一个行关键字下的数据进行原子的读-修改-写操作．  BT目前不支持跨行关键字的处理，但是它有一个界面，可以用来让客户进行批量的跨行关键字处理操作．其次，BT允许把每个表项用做整数记数器．最后，BT  支持在服务器的地址空间内执行客户端提供的脚本程序．脚本程序的语言是google开发的Sawzall[28]数据处理语言．目前，我们基于的  Sawzall的API还不允许客户脚本程序向BT内写数据，但是它允许多种形式的数据变换，基于任何表达式的过滤和通过多种操作符的摘要．<br />
BT可以和MapReduce[12]一起使用．MapReduce是google开发的大规模并行计算框架．我们为编写了一套外层程序，使BT可以作为MapReduce处理的数据源头和输出结果．<br />
4.建立BT的基本单元<br />
BT是建立在其他数个google框架单元上的．BT使用google分布式文件系统(GFS)[17]来存储日志和数据文件{yeah,  right, what else can it use,  FAT32?}．一个BT集群通常在一个共享的机器池中工作，池中的机器还运行其他的分布式应用{虽然机器便宜的跟白菜似的，可是一样要运行多个程序，命苦的象小白菜}，BT和其他程序共享机器｛BT的瓶颈是ＩＯ/内存，可以和CPU要求高的程序并存｝．BT依赖集群管理系统来安排工作，在共享的机器上管理资源，处理失效机器并监视机器状态｛典型的server  farm结构，BT是上面的应用之一｝．<br />
BT内部存储数据的格式是google  SSTable格式．一个SSTable提供一个从关键字到值的映射，关键字和值都可以是任意字符串．映射是排序的，存储的｛不会因为掉电而丢失｝，不可改写的．可以进行以下操作：查询和一个关键字相关的值；或者根据给出的关键字范围遍历所有的关键字和值．在内部，每个SSTable包含一列数据块（通常每个块的大小是64KB,但是大小是可以配置的｛索引大小是16  bits，应该是比较好的一个数｝）．块索引（存储在SSTable的最后）用来定位数据块；当打开SSTable的时候，索引被读入内存｛性能｝．每次查找都可以用一个硬盘搜索完成｛根据索引算出数据在哪个道上，一个块应该不会跨两个道，没必要省那么点空间｝：首先在内存中的索引里进行二分查找找到数据块的位置，然后再从硬盘读去数据块．最佳情况是：整个SSTable可以被放在内存里，这样一来就不必访问硬盘了．｛想的美，前面是谁口口声声说要跟别人共享机器来着？你把内存占满了别人上哪睡去？｝<br />
BT还依赖一个高度可用的，存储的分布式数据锁服务Chubby[8]｛看你怎么把这个high  performance给说圆喽｝．一个Chubby服务由5个活的备份｛机器｝构成，其中一个被这些备份选成主备份，并且处理请求．这个服务只有在大多数备份都活着并且互相通信的时候才是活的｛绕口令？去看原文吧，是在有出错的前提下的冗余算法｝．当有机器失效的时候，Chubby使用Paxos算法[9,23]来保证备份的一致性｛这个问题还是比较复杂的，建议去看引文了解一下问题本身｝．Chubby提供了一个名字空间，里面包括了目录和小文件｛万变不离其宗｝．每个目录或者文件可以当成一个锁来用，读写文件操作都是原子化的．Chubby客户端的程序库提供了对Chubby文件的一致性缓存｛究竟是提高性能还是降低性能？如果访问是分布的，就是提高性能｝．每个Chubby客户维护一个和Chubby服务的会话．如果一个客户不能在一定时间内更新它的会话，这个会话就过期失效了｛还是针对大server  farm里机器失效的频率设计的｝．当一个会话失效时，其拥有的锁和打开的文件句柄都失效｛根本设计原则：失效时回到安全状态｝．Chubby客户可以在文件和目录上登记回调函数，以获得改变或者会话过期的通知．｛翻到这里，有没有人闻到java的味道了？｝<br />
BT使用Chubby来做以下几个任务：保证任何时间最多只有一个活跃的主备份；来存储BT数据的启动位置（参考5.1节）；发现小表  （tablet）服务器，并完成tablet服务器消亡的善后（5.2节）；存储BT数据的模式信息（每张表的列信息）；以及存储访问权限列表．如果有相当长的时间Chubby不能访问，BT就也不能访问了｛任何系统都有其弱点｝．最近我们在使用11个Chubby服务实例的14个BT集群中度量了这个效果，由于Chubby不能访问而导致BT中部分数据不能访问的平均百分比是0.0047%,这里Chubby不能访问的原因是Chubby本身失效或者网络问题．单个集群里，受影响最大的百分比是0.0326%｛基于文件系统的Chubby还是很稳定的｝.<br />
GFS是一个可扩展的分布式文件系统，用于大型的、分布式的、对大量数据进行访问的应用。它运行于廉价的普通硬件上，但可以提供容错功能。它可以给大量的用户提供总体性能较高的服务。<br />
出处：http://labs.google.com/papers/gfs.html<br />
1、设计概览<br />
（1）设计想定<br />
GFS与过去的分布式文件系统有很多相同的目标，但GFS的设计受到了当前及预期的应用方面的工作量及技术环境的驱动，这反映了它与早期的文件系统明显不同的设想。这就需要对传统的选择进行重新检验并进行完全不同的设计观点的探索。<br />
GFS与以往的文件系统的不同的观点如下：<br />
1、部件错误不再被当作异常，而是将其作为常见的情况加以处理。因为文件系统由成百上千个用于存储的机器构成，而这  些机器是由廉价的普通部件组成并被大量的客户机访问。部件的数量和质量使得一些机器随时都有可能无法工作并且有一部分还可能无法恢复。所以实时地监控、错  误检测、容错、自动恢复对系统来说必不可少。<br />
2、按照传统的标准，文件都非常大。长度达几个GB的文件是很平常的。每个文件通常包含很多应用对象。当经常要处理  快速增长的、包含数以万计的对象、长度达TB的数据集时，我们很难管理成千上万的KB规模的文件块，即使底层文件系统提供支持。因此，设计中操作的参数、  块的大小必须要重新考虑。对大型的文件的管理一定要能做到高效，对小型的文件也必须支持，但不必优化。<br />
3、大部分文件的更新是通过添加  新数据完成的，而不是改变已存在的数据。在一个文件中随机的操作在实践中几乎不存在。一旦写完，文件就只可读，很多数据都有这些特性。一些数据可能组成一  个大仓库以供数据分析程序扫描。有些是运行中的程序连续产生的数据流。有些是档案性质的数据，有些是在某个机器上产生、在另外一个机器上处理的中间数据。  由于这些对大型文件的访问方式，添加操作成为性能优化和原子性保证的焦点。而在客户机中缓存数据块则失去了吸引力。<br />
4、工作量主要由两种读操作构成：对大量数据的流方式的读操作和对少量数据的随机方式的读操作。在前一种读操作中，  可能要读几百KB，通常达  1MB和更多。来自同一个客户的连续操作通常会读文件的一个连续的区域。随机的读操作通常在一个随机的偏移处读几个KB。性能敏感的应用程序通常将对少量  数据的读操作进行分类并进行批处理以使得读操作稳定地向前推进，而不要让它来来回回的读。<br />
5、工作量还包含许多对大量数据进行的、连续的、向文件添加数据的写操作。所写的数据的规模和读相似。一旦写完，文件很少改动。在随机位置对少量数据的写操作也支持，但不必非常高效。<br />
6、系统必须高效地实现定义完好的大量客户同时向同一个文件的添加操作的语义。<br />
（2）系统接口<br />
GFS提供了一个相似地文件系统界面，虽然它没有向POSIX那样实现标准的API。文件在目录中按层次组织起来并由路径名标识。<br />
（3）体系结构：<br />
一个GFS集群由一个master和大量的chunkserver构成，并被许多客户（Client）访问。如图1  所示。Master和  chunkserver通常是运行用户层服务进程的Linux机器。只要资源和可靠性允许，chunkserver和client可以运行在同一个机器  上。<br />
文件被分成固定大小的块。每个块由一个不变的、全局唯一的64位的chunk－handle标识，chunk－ handle是在块创建时由  master分配的。ChunkServer将块当作Linux文件存储在本地磁盘并可以读和写由chunk－handle和位区间指定的数据。出于可靠  性考虑，每一个块被复制到多个chunkserver上。默认情况下，保存3个副本，但这可以由用户指定。<br />
Master维护文件系统所以的元数据（metadata），包括名字空间、访问控制信息、从文件到块的映射以及块  的当前位置。它也控制系统范围的活动，如块租约（lease）管理，孤儿块的垃圾收集，chunkserver间的块迁移。Master定期通过  HeartBeat消息与每一个  chunkserver通信，给chunkserver传递指令并收集它的状态。<br />
与每个应用相联的GFS客户代码实现了文件系统的API并与master和chunkserver通信以代表应用程序读和写数据。客户与master的交换只限于对元数据（metadata）的操作，所有数据方面的通信都直接和chunkserver联系。<br />
客户和chunkserver都不缓存文件数据。因为用户缓存的益处微乎其微，这是由于数据太多或工作集太大而无法  缓存。不缓存数据简化了客户程序和整个系统，因为不必考虑缓存的一致性问题。但用户缓存元数据（metadata）。Chunkserver也不必缓存文  件，因为块时作为本地文件存储的。<br />
（4）单master。<br />
只有一个master也极大的简化了设计并使得master可以根据全局情况作出先进的块放置和复制决定。但是我们  必须要将master对读和写的参与减至最少，这样它才不会成为系统的瓶颈。Client从来不会从master读和写文件数据。Client只是询问  master它应该和哪个  chunkserver联系。Client在一段限定的时间内将这些信息缓存，在后续的操作中Client直接和chunkserver交互。<br />
以图1解释一下一个简单的读操作的交互。<br />
1、client使用固定的块大小将应用程序指定的文件名和字节偏移转换成文件的一个块索引（chunk  index）。<br />
2、给master发送一个包含文件名和块索引的请求。<br />
3、master回应对应的chunk  handle和副本的位置（多个副本）。<br />
4、client以文件名和块索引为键缓存这些信息。（handle和副本的位置）。<br />
5、Client  向其中一个副本发送一个请求，很可能是最近的一个副本。请求指定了chunk handle（chunkserver以chunk  handle标识chunk）和块内的一个字节区间。<br />
6、除非缓存的信息不再有效（<a href="http://www.donevii.com/post/tag/cache" class="st_tag internal_tag" rel="tag" title="Posts tagged with cache">cache</a> for a limited  time）或文件被重新打开，否则以后对同一个块的读操作不再需要client和master间的交互。<br />
通常Client可以在一个请求中询问多个chunk的地址，而master也可以很快回应这些请求。<br />
（5）块规模：<br />
块规模是设计中的一个关键参数。我们选择的是64MB，这比一般的文件系统的块规模要大的多。每个块的副本作为一个普通的Linux文件存储，在需要的时候可以扩展。<br />
块规模较大的好处有：<br />
1、减少client和master之间的交互。因为读写同一个块只是要在开始时向master请求块位置信息。对于读写大型文件这种减少尤为重要。即使对于访问少量数据的随机读操作也可以很方便的为一个规模达几个TB的工作集缓缓存块位置信息。<br />
2、Client在一个给定的块上很可能执行多个操作，和一个chunkserver保持较长时间的TCP连接可以减少网络负载。<br />
3、这减少了master上保存的元数据（metadata）的规模，从而使得可以将metadata放在内存中。这又会带来一些别的好处。<br />
不利的一面：<br />
一个小文件可能只包含一个块，如果很多Client访问改文件的话，存储这些块的chunkserver将成为访问的热点。但在实际应用中，应用程序通常顺序地读包含多个块的文件，所以这不是一个主要问题。<br />
（6）元数据（metadata）：<br />
master存储了三中类型的metadata：文件的名字空间和块的名字空间，从文件到块的映射，块的副本的位  置。所有的metadata都放在内存中。前两种类型的metadata通过向操作日志登记修改而保持不变，操作日志存储在master的本地磁盘并在几  个远程机器上留有副本。使用日志使得我们可以很简单地、可靠地更新master的状态，即使在master崩溃的情况下也不会有不一致的问题。相反，  mater在每次启动以及当有  chuankserver加入的时候询问每个chunkserver的所拥有的块的情况。<br />
A、内存数据结构：<br />
因为metadata存储在内存中，所以master的操作很快。进一步，master可以轻易而且高效地定期在后台扫描它的整个状态。这种定期地扫描被用于实现块垃圾收集、chunkserver出现故障时的副本复制、为平衡负载和磁盘空间而进行的块迁移。<br />
这种方法的一个潜在的问题就是块的数量也即整个系统的容量是否受限与master的内存。实际上，这并不是一个严重  的问题。Master为每个  64MB的块维护的metadata不足64个字节。除了最后一块，文件所有的块都是满的。类似的，每个文件的名字空间数据也不足64个字节，因为文件名  是以一种事先确定的压缩方式存储的.如果要支持更大的文件系统，那么增加一些内存的方法对于我们将元数据（metadata）保存在内存种所获得的简单  性、可靠性、高性能和灵活性来说，这只是一个很小的代价。<br />
B、块位置：<br />
master并不为chunkserver所拥有的块的副本的保存一个不变的记录。它在启动时通过简单的查询来获得这些信息。Master可以保持这些信息的更新，因为它控制所有块的放置并通过HeartBeat消息来监控chunkserver的状态。<br />
这样做的好处：因为chunkserver可能加入或离开集群、改变路径名、崩溃、重启等，一个集群重有成百个server，这些事件经常发生，这种方法就排除了master与chunkserver之间的同步问题。<br />
另一个原因是：只有chunkserver才能确定它自己到底有哪些块，由于错误，chunkserver中的一些块可能会很自然的消失，这样在master中就没有必要为此保存一个不变的记录。<br />
C、操作日志：<br />
操作日志包含了对metadata所作的修改的历史记录。它作为逻辑时间线定义了并发操作的执行顺序。文件、块以及它们的版本号都由它们被创建时的逻辑时间而唯一地、永久地被标识。<br />
操作日志是如此的重要，我们必须要将它可靠地保存起来，并且只有在metadata的改变固定下来之后才将变化呈现给用户。所以我们将操作日志复制到数个远程的机器上，并且只有在将相应的日志记录写到本地和远程的磁盘上之后才回答用户的请求。<br />
Master可以用操作日志来恢复它的文件系统的状态。为了将启动时间减至最小，日志就必须要比较小。每当日志的长度增长到超过一定的规模后，master就要检查它的状态，它可以从本地磁盘装入最近的检查点来恢复状态。<br />
创建一个检查点比较费时，master的内部状态是以一种在创建一个检查点时并不耽误即将到来的修改操作的方式来组  织的。Master切换到一个新的日子文件并在一个单独的线程中创建检查点。这个新的检查点记录了切换前所有的修改。在一个有数十万文件的集群中用一分钟  左右就能完成。创建完后，将它写入本地和远程的磁盘。<br />
（7）数据完整性<br />
名字空间的修改必须是原子性的，它们只能有master处理：名字空间锁保证了操作的原子性和正确性，而master的操作日志在全局范围内定义了这些操作的顺序。<br />
文  件区间的状态在修改之后依赖于修改的类型，不论操作成功还是失败，也不论是不是并发操作。如果不论从哪个副本上读，所有的客户都看到同样的数据，那么文件  的这个区域就是一致的。如果文件的区域是一致的并且用户可以看到修改操作所写的数据，那么它就是已定义的。如果修改是在没有并发写操作的影响下完成的，那  么受影响的区域是已定义的，所有的client都能看到写的内容。成功的并发写操作是未定义但却是一致的。失败的修改将使区间处于不一致的状态。<br />
Write操作在应用程序指定的偏移处写入数据，而record  append操作使得数据（记录）即使在有并发修改操作的情况下也至少原子性的被加到GFS指定的偏移处，偏移地址被返回给用户。<br />
在一系列成功的修改操作后，最后的修改操作保证文件区域是已定义的。GFS通过对所有的副本执行同样顺序的修改操作并且使用块版本号检测过时的副本（由于chunkserver退出而导致丢失修改）来做到这一点。<br />
因为用户缓存了会位置信息，所以在更新缓存之前有可能从一个过时的副本中读取数据。但这有缓存的截止时间和文件的重新打开而受到限制。<br />
在修改操作成功后，部件故障仍可以是数据受到破坏。GFS通过master和chunkserver间定期的handshake，借助校验和来检测对数据的破坏。一旦检测到，就从一个有效的副本尽快重新存储。只有在GFS检测前，所有的副本都失效，这个块才会丢失。<br />
2、系统交互<br />
（1）租约（lease）和修改顺序<br />
（2）数据流<br />
我们的目标是充分利用每个机器的网络带宽，避免网络瓶颈和延迟<br />
为了有效的利用网络，我们将数据流和控制流分离。数据是以流水线的方式在选定的chunkerserver链上线性的传递的。每个机器的整个对外带宽都被用作传递数据。为避免瓶颈，每个机器在收到数据后，将它收到数据尽快传递给离它最近的机器。<br />
（3）原子性的record  Append：<br />
GFS提供了一个原子性的添加操作：record  append。在传统的写操作中，client指定被写数据的偏移位置，向同一个区间的并发的写操作是不连续的：区间有可能包含来自多个client的数  据碎片。在record append中，  client只是指定数据。GFS在其选定的偏移出将数据至少原子性的加入文件一次，并将偏移返回给client。<br />
在分布式的应用中，不同机器上的许多client可能会同时向一个文件执行添加操作，添加操作被频繁使用。如果用传  统的write操作，可能需要额外的、复杂的、开销较大的同步，例如通过分布式锁管理。在我们的工作量中，这些文件通常以多个生产者单个消费者队列的方式  或包含从多个不同 client的综合结果。<br />
Record  append和前面讲的write操作的控制流差不多，只是在primary上多了一些逻辑判断。首先，client将数据发送到文件最后一块的所有副本  上。然后向primary发送请求。Primary检查添加操作是否会导致该块超过最大的规模（64M）。如果这样，它将该块扩充到最大规模，并告诉其它  副本做同样的事，同时通知client该操作需要在下一个块上重新尝试。如果记录满足最大规模的要求，primary就会将数据添加到它的副本上，并告诉  其它的副本在在同样的偏移处写数据，最后primary向client报告写操作成功。如果在任何一个副本上record  append操作失败，client将重新尝试该操作。这时候，同一个块的副本可能包含不同的数据，因为有的可能复制了全部的数据，有的可能只复制了部  分。GFS不能保证所有的副本每个字节都是一样的。它只保证每个数据作为一个原子单元被写过至少一次。这个是这样得出的：操作要是成功，数据必须在所有的  副本上的同样的偏移处被写过。进一步，从这以后，所有的副本至少和记录一样长，所以后续的记录将被指定到更高的偏移处或者一个不同的块上，即使另一个副本  成了primary。根据一致性保证，成功的record  append操作的区间是已定义的。而受到干扰的区间是不一致的。<br />
（4）快照（snapshot）<br />
快照操作几乎在瞬间构造一个文件和目录树的副本，同时将正在进行的其他修改操作对它的影响减至最小。<br />
我们使用copy-on-write技术来实现snapshot。当master受到一个snapshot请求时，  它首先将要snapshot的文件上块上的lease。这使得任何一个向这些块写数据的操作都必须和master交互以找到拥有lease的副本。这就给  master一个创建这个块的副本的机会。<br />
副本被撤销或终止后，master在磁盘上登记执行的操作，然后复制源文件或目录树的metadata以对它的内存状态实施登记的操作。这个新创建的snapshot文件和源文件（其metadata）指向相同的块（chunk）。<br />
Snapshot之后，客户第一次向chunk  c写的时候，它发一个请求给master以找到拥有lease的副本。Master注意到chunk c的引用记数比1大，它延迟对用户的响应，选择一个chunk  handle C’,然后要求每一有chunk c的副本的chunkserver创建一个块C’。每个chunkserver在本地创建chunk  C’避免了网络开销。从这以后和对别的块的操作没有什么区别。<br />
3、MASTER操作<br />
MASTER执行所有名字空间的操作，除此之外，他还在系统范围管理数据块的复制：决定数据块的放置方案，产生新数据块并将其备份，和其他系统范围的操作协同来确保数据备份的完整性，在所有的数据块服务器之间平衡负载并收回没有使用的存储空间。<br />
3.1  名字空间管理和加锁<br />
与传统文件系统不同的是，GFS没有与每个目录相关的能列出其所有文件的数据结构，它也不支持别名（unix中的硬连接或符号连接），不管是对文件或是目录。GFS的名字空间逻辑上是从文件元数据到路径名映射的一个查用表。<br />
MASTER在执行某个操作前都要获得一系列锁，例如，它要对/d1/d2…/dn/leaf执行操作，则它必须获  得/d1，/d1/d2，…，  /d1/d2/…/dn的读锁，/d1/d2…/dn/leaf的读锁或写锁（其中leaf可以使文件也可以是目录）。MASTER操作的并行性和数据的  一致性就是通过这些锁来实现的。<br />
3.2  备份存储放置策略<br />
一个GFS集群文件系统可能是多层分布的。一般情况下是成千上万个文件块服务器分布于不同的机架上，而这些文件块服  务器又被分布于不同机架上的客户来访问。因此，不同机架上的两台机器之间的通信可能通过一个或多个交换机。数据块冗余配置策略要达到连个目的：最大的数据  可靠性和可用性，最大的网络带宽利用率。因此，如果仅仅把数据的拷贝置于不同的机器上很难满足这两个要求，必须在不同的机架上进行数据备份。这样即使整个  机架被毁或是掉线，也能确保数据的正常使用。这也使数据传输，尤其是读数据，可以充分利用带宽，访问到多个机架，而写操作，则不得不涉及到更多的机架。<br />
3.3  产生、重复制、重平衡数据块<br />
当MASTER产生新的数据块时，如何放置新数据块，要考虑如下几个因素：（1）尽量放置在磁盘利用率低的数据块服  务器上，这样，慢慢地各服务器的磁盘利用率就会达到平衡。（2）尽量控制在一个服务器上的“新创建”的次数。（3）由于上一小节讨论的原因，我们需要把数  据块放置于不同的机架上。<br />
MASTER在可用的数据块备份低于用户设定的数目时需要进行重复制。这种情况源于多种原因：服务器不可用，数据被  破坏，磁盘被破坏，或者备份数目被修改。每个被需要重复制的数据块的优先级根据以下几项确定：第一是现在的数目距目标的距离，对于能阻塞用户程序的数据  块，我们也提高它的优先级。最后，  MASTER按照产生数据块的原则复制数据块，并把它们放到不同的机架内的服务器上。<br />
MASTER周期性的平衡各服务器上的负载：它检查chunk分布和负载平衡，通过这种方式来填充一个新的服务器而  不是把其他的内容统统放置到它上面带来大量的写数据。数据块放置的原则与上面讨论的相同，此外，MASTER还决定那些数据块要被移除，原则上他会清除那  些空闲空间低于平均值的那些服务器。<br />
3.4  垃圾收集<br />
在一个文件被删除之后，GFS并不立即收回磁盘空间，而是等到垃圾收集程序在文件和数据块级的的检查中收回。<br />
当一个文件被应用程序删除之后，MASTER会立即记录下这些变化，但文件所占用的资源却不会被立即收回，而是重新  给文件命了一个隐藏的名字，并附上了删除的时间戳。在MASTER定期检查名字空间时，它删除超过三天（可以设定）的隐藏的文件。在此之前，可以以一个新  的名字来读文件，还可以以前的名字恢复。当隐藏的文件在名字空间中被删除以后，它在内存中的元数据即被擦除，这就有效地切断了他和所有数据块的联系。<br />
在一个相似的定期的名字空间检查中，MASTER确认孤儿数据块（不属于任何文件）并擦除他的元数据，在和MASTER的心跳信息交换中，每个服务器报告他所拥有的数据块，MASTER返回元数据不在内存的数据块，服务器即可以删除这些数据块。<br />
3.5  过时数据的探测<br />
在数据更新时如果服务器停机了，那么他所保存的数据备份就会过时。对每个数据块，MASTER设置了一个版本号来区别更新过的数据块和过时的数据块。<br />
当MASTER授权一个新的lease时，他会增加数据块的版本号并会通知更新数据备份。MASTER和备份都会记  录下当前的版本号，如果一个备份当时不可用，那么他的版本号不可能提高，当ChunkServer重新启动并向MASTER报告他的数据块集时，  MASTER就会发现过时的数据。<br />
MASTER在定期的垃圾收集程序中清除过时的备份，在此以前，处于效率考虑，在各客户及英大使，他会认为根本不存  在过时的数据。作为另一个安全措施，  MASTER在给客户及关于数据块的应答或是另外一个读取数据的服务器数据是都会带上版本信息，在操作前客户机和服务器会验证版本信息以确保得到的是最新  的数据。<br />
4、容错和诊断<br />
4.1 高可靠性<br />
4.1.1  快速恢复<br />
不管如何终止服务，MASTER和数据块服务器都会在几秒钟内恢复状态和运行。实际上，我们不对正常终止和不正常终止进行区分，服务器进程都会被切断而终止。客户机和其他的服务器会经历一个小小的中断，然后它们的特定请求超时，重新连接重启的服务器，重新请求。<br />
4.1.2  数据块备份<br />
如上文所讨论的，每个数据块都会被备份到放到不同机架上的不同服务器上。对不同的名字空间，用户可以设置不同的备份级别。在数据块服务器掉线或是数据被破坏时，MASTER会按照需要来复制数据块。<br />
4.1.3  MASTER备份<br />
为确保可靠性，MASTER的状态、操作记录和检查点都在多台机器上进行了备份。一个操作只有在数据块服务器硬盘上  刷新并被记录在MASTER和其备份的上之后才算是成功的。如果MASTER或是硬盘失败，系统监视器会发现并通过改变域名启动它的一个备份机，而客户机  则仅仅是使用规范的名称来访问，并不会发现MASTER的改变。<br />
4.2  数据完整性<br />
每个数据块服务器都利用校验和来检验存储数据的完整性。原因：每个服务器随时都有发生崩溃的可能性，并且在两个服务器间比较数据块也是不现实的，同时，在两台服务器间拷贝数据并不能保证数据的一致性。<br />
每个Chunk按64kB的大小分成块，每个块有32位的校验和，校验和和日志存储在一起，和用户数据分开。<br />
在读数据时，服务器首先检查与被读内容相关部分的校验和，因此，服务器不会传播错误的数据。如果所检查的内容和校验  和不符，服务器就会给数据请求者返回一个错误的信息，并把这个情况报告给MASTER。客户机就会读其他的服务器来获取数据，而MASTER则会从其他的  拷贝来复制数据，等到一个新的拷贝完成时，MASTER就会通知报告错误的服务器删除出错的数据块。<br />
附加写数据时的校验和计算优化了，因为这是主要的写操作。我们只是更新增加部分的校验和，即使末尾部分的校验和数据已被损坏而我们没有检查出来，新的校验和与数据会不相符，这种冲突在下次使用时将会被检查出来。<br />
相反，如果是覆盖现有数据的写，在写以前，我们必须检查第一和最后一个数据块，然后才能执行写操作，最后计算和记录校验和。如果我们在覆盖以前不先检查首位数据块，计算出的校验和则会因为没被覆盖的数据而产生错误。<br />
在空闲时间，服务器会检查不活跃的数据块的校验和，这样可以检查出不经常读的数据的错误。一旦错误被检查出来，服务器会拷贝一个正确的数据块来代替错误的。<br />
4.3  诊断工具<br />
广泛而细致的诊断日志以微小的代价换取了在问题隔离、诊断、性能分析方面起到了重大的作用。GFS服务器用日志来记  录显著的事件（例如服务器停机和启动）和远程的应答。远程日志记录机器之间的请求和应答，通过收集不同机器上的日志记录，并对它们进行分析恢复，我们可以  完整地重现活动的场景，并用此来进行错误分析。<br />
5 测量<br />
5.1  测试环境<br />
一台主控机，两台主控机备份，16台数据块服务器，16台客户机。<br />
每台机器：2块PIII1.4G处理器，2G内存，2块80G5400rpm的硬盘，1块100Mbps全双工网卡<br />
19台服务器连接到一个HP2524交换机上，16台客户机俩接到领外一台交换机上，两台交换机通过1G的链路相连。</p>
<p>原文转自：http://bigweb.group.javaeye.com/group/topic/5749</p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/504.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[转] C语言的setjmp: 异常处理与构建协作式多任务系统</title>
		<link>http://www.donevii.com/post/356.html</link>
		<comments>http://www.donevii.com/post/356.html#comments</comments>
		<pubDate>Sun, 02 Dec 2007 23:57:48 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[c/c++/c#]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[c]]></category>
		<category><![CDATA[goto]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[longjmp]]></category>
		<category><![CDATA[setjmp]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[堆栈]]></category>
		<category><![CDATA[多任务]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[异常]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[类]]></category>
		<category><![CDATA[缓冲]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/356.html</guid>
		<description><![CDATA[转至 http://www.upsdn.net/html/2004-11/47.html 在C标准库中有一对非常有趣的函数setjmp()函数与longjmp()函数,用来实现代替goto实现一些非常重要的功能，如异常处理。C语言中，标准库函数setjmp和longjmp形成... ]]></description>
			<content:encoded><![CDATA[<p>转至 <a href="http://www.upsdn.net/html/2004-11/47.html" title="http://www.upsdn.net/html/2004-11/47.html">http://www.upsdn.net/html/2004-11/47.html</a></p>
<p>在C标准库中有一对非常有趣的函数setjmp()函数与longjmp()函数,用来实现代替goto实现一些非常重要的功能，如异常处理。C语言中，标准库函数setjmp和longjmp形成了结构化异常工具的基础。简单的说即setjmp实例化异常处理程序，而longjmp产生异常。</p>
<p>先介绍setjmp<br />
int <a href="http://www.donevii.com/post/tag/setjmp" class="st_tag internal_tag" rel="tag" title="Posts tagged with setjmp">setjmp</a>(jmp_buf envbuf)<br />
宏函数setjmp()在缓冲区envbuf中保存系统堆栈里的内容，供longjmp()以后使用,setjmp()必须使用头文件setjmp.h。<br />
调用setjmp()宏时，返回值为0，然而longjmp()把一个变原传递给setjmp(),该值（恒不为0）就是调用longjmp()后出现的setjmp()的值。<br />
setjmp函数用于保存程序的运行时的堆栈环境，接下来的其它地方，你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。当setjmp和longjmp组合一起使用时，它们能提供一种在程序中实现“非本地局部跳转”（&#8221;non-local <a href="http://www.donevii.com/post/tag/goto" class="st_tag internal_tag" rel="tag" title="Posts tagged with goto">goto</a>&#8221;）的机制。并且这种机制常常被用于来实现，把程序的控制流传递到错误处理模块之中；或者程序中不采用正常的返回（return）语句，或函数的正常调用等方法，而使程序能被恢复到先前的一个调用例程（也即函数）中。<br />
对setjmp函数的调用时，会保存程序当前的堆栈环境到env参数中；接下来调用longjmp时，会根据这个曾经保存的变量来恢复先前的环境， 并且当前的程序控制流，会因此而返回到先前调用setjmp时的程序执行点。此时，在接下来的控制流的例程中，所能访问的所有的变量（除寄存器类型的变量 以外），包含了longjmp函数调用时，所拥有的变量。<br />
void <a href="http://www.donevii.com/post/tag/longjmp" class="st_tag internal_tag" rel="tag" title="Posts tagged with longjmp">longjmp</a>(jmp_buf envbuf,int status);<br />
     函数longjmp()使程序在最近一次调用setjmp()出重新执行。setjmp()和longjmp()提供了一种在函数间调转的手段，必须使用头部文件setjmp.h。<br />
     函数longjmp()通过把堆栈复位成envbuf中描述的状态进行操作，envbuf的设置是由预先调用setjmp()生成的。这样使程序的执行在 setjmp()调用后的下一个语句从新开始，使计算机认为从未离开调用setjmp()的函数。从效果上看，longjmp()函数似乎“绕”过了时间 和空间（内存）回到程序的原点，不必执行正常的函数返回过程。<br />
     缓冲区envbuf具有&lt;setjmp.h&gt;中定义的buf_jmp类型，它必须调用longjmp()前通过调用setjmp()来设置好。<br />
    值status变成setjmp()的返回值，由此确定长调转的来处。不允许的唯一值是0，0是程序直接调用函数setjmp()时由该函数返回的，不是间接通过执行函数longjmp()返回的。<br />
     longjmp()函数最常用于在一个错误发生时，从一组深层嵌套的实用程序中返回。<br />
longjmp函数用于恢复先前程序中调用的setjmp函数时所保存的堆栈环境。setjmp和longjmp组合一起使用时，它们能提供一种在程序中实现“非本地局部跳转”（&#8221;non-local goto&#8221;）的机制。并且这种机制常常被用于来实现，把程序的控制流传递到错误处理模块，或者不采用正常的返回（return）语句，或函数的正常调用等方法，使程序能被恢复到先前的一个调用例程（也即函数）中。</p>
<p>　 　对setjmp函数的调用时，会保存程序当前的堆栈环境到env参数中；接下来调用longjmp时，会根据这个曾经保存的变量来恢复先前的环境，并且 因此当前的程序控制流，会返回到先前调用setjmp时的执行点。此时，value参数值会被setjmp函数所返回，程序继续得以执行。并且，在接下来 的控制流的例程中，它所能够访问到的所有的变量（除寄存器类型的变量以外），包含了longjmp函数调用时，所拥有的变量；而寄存器类型的变量将不可预 料。setjmp函数返回的值必须是非零值，如果longjmp传送的value参数值为0，那么实际上被setjmp返回的值是1。</p>
<p>　　在调用setjmp的函数返回之前，调用longjmp，否则结果不可预料。</p>
<p>在使用longjmp时，请遵守以下规则或限制：<br />
　　· 不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后，通过setjmp所返回的控制流中，例程中寄存器类型的变量将不会被恢复。<br />
　　· 不要使用longjmp函数，来实现把控制流，从一个中断处理例程中传出，除非被捕获的异常是一个浮点数异常。在后一种情况下，如果程序通过调用 _fpreset函数，来首先初始化浮点数包后，它是可以通过longjmp来实现从中断处理例程中返回。<br />
如何实现异常处理<br />
首先设置一个跳转点（setjmp() 函数可以实现这一功能），然后在其后的代码中任意地方调用 longjmp() 跳转回这个跳转点上，以此来实现当发生异常时，转到处理异常的程序上，在其后的介绍中将介绍如何实现。 setjmp() 为跳转返回保存现场并为异常提供处理程序，longjmp() 则进行跳转（抛出异常），setjmp() 与 longjmp() 可以在函数间进行跳转，这就像一个全局的 goto 语句，可以跨函数跳转。</p>
<p><strong>jmp_buf 异常结构</strong></p>
<p>        使用 setjmp() 及 longjmp() 函数前，需要先认识一下 jmp_buf 异常结构。jmp_buf 将使用在 setjmp() 函数中，用于保存当前程序现场（保存当前需要用到的寄存器的值），jmp_buf 结构在 setjmp.h 文件内声明：</p>
<p>        typedef struct<br />
        {<br />
                unsigned j_sp;  // 堆栈指针寄存器<br />
                unsigned j_ss;  // 堆栈段<br />
                unsigned j_flag;  // 标志寄存器<br />
                unsigned j_cs;  // 代码段<br />
                unsigned j_ip;  // 指令指针寄存器<br />
                unsigned j_bp; // 基址指针<br />
                unsigned j_di;  // 目的指针<br />
                unsigned j_es; // 附加段<br />
                unsigned j_si;  // 源变址<br />
                unsigned j_ds; // 数据段<br />
        } jmp_buf;</p>
<p>        jmp_buf 结构存放了程序当前寄存器的值，以确保使用 longjmp() 后可以跳回到该执行点上继续执行。</p>
<p>setjmp() 与 longjmp() 函数都使用了 jmp_buf 结构作为形参，它们的调用关系是这样的：<br />
        首先调用 setjmp() 函数来初始化 jmp_buf 结构变量 jmpb，将当前CPU中的大部分影响到程序执行的积存器存入 jmpb，为 longjmp() 函数提供跳转，setjmp() 函数是一个有趣的函数，它能返回两次，它应该是所有库函数中唯一一个能返回两次的函数，第一次是初始化时，返回零，第二次遇到 longjmp() 函数调用后，longjmp() 函数使 setjmp() 函数发生第二次返回，返回值由 longjmp() 的第二个参数给出（整型，这时不应该再返回零）。<br />
        在使用 setjmp() 初始化 jmpb 后，可以其后的程序中任意地方使用 longjmp() 函数跳转会 setjmp() 函数的位置，longjmp() 的第一个参数便是 setjmp() 初始化的 jmpb，若想跳转回刚才设置的 setjmp() 处，则 longjmp() 函数的第一个参数是 setjmp() 所初始化的 jmpb 这个异常，这也说明一件事，即 jmpb 这个异常，一般需要定义为全局变量，否则，若是局部变量，当跨函数调用时就几乎无法使用（除非每次遇到函数调用都将 jmpb 以参数传递，然而明显地，是不值得这样做的）；longjmp() 函数的第二个参数是传给 setjmp() 的第二次返回值，这在介绍 setjmp() 函数时已经介绍过。<br />
        下面是 setjmp() 函数与 longjmp() 函数的一个示意图：<br />
<img border="0" src="http://blog.bc-cn.net/UploadFiles/2006-9/926566599.bmp" /></p>
<p>通过示意图可以看出 setjmp() 函数与 longjmp() 函数的关系，如何跳转。</p>
<p><img width="554" src="http://51cmm.csai.cn/ExpertEyes/Images/no152.gif" height="480" /></p>
<p>呵呵！现在是否对程序的执行流程一目了然，其中最关键的就是setjjmp和longjmp函数的调用处理。我们分别来分析之。<br />
　　当程序运行到第②步时，调用setjmp函数，这个函数会保存程序当前运行的一些状态信息，主要是一些系统寄存器的值，如ss，cs，eip， eax，ebx，ecx，edx，eflags等寄存器，其中尤其重要的是eip的值，因为它相当于保存了一个程序运行的执行点。这些信息被保存到 mark变量中，这是一个C标准库中所定义的特殊结构体类型的变量。<br />
　　调用setjmp函数保存程序状态之后，该函数返回0值，于是接下来程序执行到第③步和第④步中。在第④步中语句执行时，如果变量n2为0值，于是便 引发了一个浮点数计算异常，，导致控制流转入fphandler函数中，也即进入到第⑤步。<br />
　　然后运行到第⑥步，调用longjmp函数，这个函数内部会从先前的setjmp所保存的程序状态，也即mark变量中，来恢复到以前的系统寄存器的 值。于是便进入到了第⑦步，注意，这非常有点意思，实际上，通过longjmp函数的调用后，程序控制流（尤其是eip的值）再次戏剧性地进入到了 setjmp函数的处理内部中，但是这一次setjmp返回的值是longjmp函数调用时，所传入的第2个参数，也即-1，因此程序接下来进入到了第⑧ 步的执行之中。<br />
因为 jmp_buf 全局只能存放一个 jmpb 结构，使得只有最后一组宏可以响应异常；    这是无法嵌套异常的原因，要实现多重嵌套可以建立一个全局堆栈来维护一组 jmpb 结构<br />
实现机理<br />
setjmp()保存其调用返回点的ebx, esi, edi, ebp, esp, eip,对于sigsetjmp(), 还保存当前的信号屏蔽字, longjmp恢复这些寄存器及信号屏蔽字,同时传递一个返回值。<br />
ongjmp只是使CPU的状态和setjmp时的状态一致，相同的CPU初始状态执行相同的代码并不意味着产生相同的结果,setjmp, longjmp并不关心内存变量的变化，而只关心CPU的状态,<br />
内存变量的确定性应该根据上下文来灵活处理.<br />
如果将函数之间的调用关系看成一种树型结构, 将可执行文件的入口函数看成它的最内层节点(根节点), 那么setjmp提供了标记节点的方法, longjmp提供了从外层节点直接返回到更内层节点的方法,<br />
在同一层节点之间或者从内层节点到外层节点的longjmp是没有意义的, 因为目标节点在此时根本不存在.<br />
<strong>摘要：</strong>讨论一个利用标准C语言setjmp库函烽实现查询式协作多任务系统，给出完整的内核和样例程序并对源代码进行说明。该系统具有简单易用的特点，只需要编写存取堆栈指针的宏就可方便地移植到新的平台上。文章详述了系统的优缺点，讨论一些性能扩展的方法。该内核适用<br />
于中小规模的嵌入式软件。</p>
<p><strong>关键词：</strong>协作式多任务 C语言 setjmp</p>
<p><strong>引言</strong></p>
<p>本文介绍的是利用标准C语言setjmp库函数实现的具备此特点的协作式多任务系统。从本 质上讲，实时多任务操作系统应该具备按照优先级抢占调度的内核。然而，在实际应用中，抢中式的多任务某种程序上带来了用户程序设计时数据保护的困难，并 且，具备抢占功能的多任务内核设计时困难也比较多，这会增加操作系统自身的代码，也使它在小资源单片机系统中应用较少；而协作多任务系统的调度只在用户指 定的时机发生，这会大大简化内核和用户系统的设计，尤其本文实现的系统通过条件查询来放弃CPU，既符合传统单片机程序设计的思维，又带来了多任务、模块 化、可重入的编程便利。</p>
<p>Setjmp是标准C语言库函数的组成部分，它可以实现程序执行中的远程转操作。具体来 说，它可以在一个函数中使用setjmp来初始化一个全局标号，然后只要该函数未曾返回，那么在其它任何地方都可以通过longjmp调用来跳转到 setjmp的下一条语句执行。实际上，setjmp函数将发生调用处的局部环境保存在一个jmp_buf的结构当中，只要主调函数中对应的内存未曾释放 （函数返回时局部内存就失效了），那么在调用longjmp的时候就可以根据已保存的jmp_buf参数恢复到setjmp的地方执行。我们的系统中就是 分析了setjmp标准库函数的特点，以简单的方式实现了协作式多任务。</p>
<p><strong>1 演示程序</strong></p>
<p>为了便于理解，首先给出多任务演示程序的源代码。这个程序演示了协作式多任务切换、任务的 动态生成、多任务共用代码等功能，一共使用了init_coos初始化根任务（也就是C语言main函数）、creat_task创建新任务和 WAITFOR查询条件这3个基本的系统调用。由于面向嵌入式系统，因而程序不会中止并且运行中也没有进行任何输出，需要借助适合的调试工具来理解多任务 系统的运行。</p>
<p>example.c文件清单：</p>
<p>#include&lt;stdlib.h&gt;</p>
<p>#include“co-os.h”</p>
<p>void tskfunc1(int argc,void *argv);</p>
<p>void tskfunc2(int argc,void *argv);</p>
<p>void subfunc(void);</p>
<p>volatile int cnt,test;</p>
<p>int main(void){</p>
<p>int i;</p>
<p>init_coos(400);</p>
<p>creat_tsk(tskfunc1,12,NULL,400);</p>
<p>creat_tsk(tskfunc2,0,NULL,400);</p>
<p>i=0；</p>
<p>while(1){</p>
<p>WAITFOR(cnt= =8);</p>
<p>while(i++&lt;cnt)test=i;</p>
<p>cnt++;</p>
<p>}</p>
<p>}</p>
<p>void tskfunc1(int argc,void *argv){</p>
<p>int i;</p>
<p>static int creat=0;</p>
<p>if(!creat){</p>
<p>creat_tsk(tskfunc1,9,NULL,400);</p>
<p>creat=1;</p>
<p>}</p>
<p>i=0；</p>
<p>while(1){</p>
<p>WAITFOR(cnt&gt;argc);</p>
<p>test=0&#215;55;</p>
<p>/*使用函数调用在子程序中测试WAITFOR*/</p>
<p>subfunc()；</p>
<p>while(i++&lt;cnt)test=i^0xAA;</p>
<p>}</p>
<p>}</p>
<p>void tskfunc2(int argc,void *argv){</p>
<p>while(1){</p>
<p>WAITFOR(++cnt&gt;15);</p>
<p>cnt=0;</p>
<p>}</p>
<p>}</p>
<p>void subfunc(void){</p>
<p>int i;</p>
<p>WAITFOR(cnt&lt;5);</p>
<p>for(i=0;i&lt;++)test=0&#215;10*i；</p>
<p>}</p>
<p><strong>2 内核构成</strong></p>
<p>内核包括一个供外部用户程序包含的头文件（co-os.h）和具体实现的源文件（co-os.<a href="http://www.donevii.com/post/tag/c" class="st_tag internal_tag" rel="tag" title="Posts tagged with c">c</a>），它们提供了演示程序中用到的3个系统调用。</p>
<p>内核的实现代码假定了CPU堆栈是向下增长的，并且通过宏来直接操作堆栈指针。以下代码在 Microsoft VC6 for x86、Borland C++ Builder 5.5、SDS CrossCode7.0 for 68K和GCC3.2 for AVR四种平台中测试过，只需在co-os.h头文件中定义相应的平台类型即可顺利编译。</p>
<p>（1）co-os.h文件清单</p>
<p>#include&lt;setjmp.h&gt;</p>
<p>/*选择X86_VC6，X86_BC5，AVR_GCC或M68H_SDS.*/</p>
<p>#define X86_VC6</p>
<p>#define MAX_TSK 10</p>
<p>typedef struct {</p>
<p>void (*entry)(int argc,void *argv);</p>
<p>jmp_buf env;</p>
<p>int argc;</p>
<p>void *argv;</p>
<p>}TVB;</p>
<p>extern TCB tcb[MAX_TSK];</p>
<p>extern int task_num,tskid;</p>
<p>void init_coos(int mainstk);</p>
<p>int creat_tsk(void(*entry)(int argc,void *argv),int argc,void *argv,int stksize);</p>
<p>#define WAITFOR(condition)do{</p>
<p>setjmp(tcb[tskid].env);</p>
<p>if(!(condition)){</p>
<p>tskid++;</p>
<p>if(tskid&gt;=task_num)tskid=0;</p>
<p>longijmp(tcb[tskid].env,1);</p>
<p>}</p>
<p>}while(0)</p>
<p>(2)co-os.c文件清单</p>
<p>#include &#8220;co-os.h&#8221;</p>
<p>#if defined(X86_VC6)||defined(X86_BC5)</p>
<p>#define SAVE_SP(p) _asm mov p,esp</p>
<p>#define RESTORE_SP(p) _asm mov esp,p</p>
<p>#elif defined(AVR_GCC)</p>
<p>#include&lt;io.h&gt;</p>
<p>#define SAVE_SP(p) p=(int*)SP</p>
<p>#define RESTORE_SP(p) SP=(int)p</p>
<p>#elif defined(M68K_SDS)</p>
<p>#define SAVE_SP(p) asm(&#8220;MOVE.L A7,{&#8220;#p&#8221;}&#8221;)</p>
<p>#define RESTORE_SP(p) asm(&#8220;MOVE.L {&#8220;#p&#8221;},A7&#8243;)</p>
<p>#endif</p>
<p>TCB tcb[MAX_TSK];</p>
<p>Int task_num=1;</p>
<p>Int tskid;</p>
<p>Static int stktop,oldsp;</p>
<p>Void init_coos(int mainstk){</p>
<p>SAVE_SP(stktop);</p>
<p>stktop=stktop+sizeof(void(*)(void))/sizeof(int)</p>
<p>-(mainstk+sizeof(int)-1)/sizeof(int);</p>
<p>}</p>
<p>int creat_tsk(void(*entry)(int argc,void *argv),</p>
<p>int argc,void *argv,int stksize){</p>
<p>if(task_num&gt;=MAX_TSK)terurn-1;</p>
<p>SAVE_SP(oldsp);</p>
<p>RESTORE_SP(stktop);</p>
<p>If(!setjmp(tcb[task_num].env)){</p>
<p>RESTORE_SP(oldsp);</p>
<p>tcb[task_num].entry=entry;</p>
<p>tcb[task_num].argc=argc;</p>
<p>tcb[task_num].argv=argv;</p>
<p>task_num++;</p>
<p>stktop-=(stksize+sizeof(int)-1)/sizeof(int);</p>
<p>}</p>
<p>else tcb[tskid].entry(tcb[tskid].argc,tcb[tskid].argv);</p>
<p>return 0;</p>
<p>}</p>
<p><strong>3 代码说明</strong></p>
<p>任务代码通过执行setjmp设置本任务下次查询时的返回点，然后在等待条件放弃掉CPU 跳转到下一任务的返回点处执行。如此周而复始，让各任务都获得轮转运行的机会，也要求各任务都需要主动通过等待条件的方式放弃掉CPU。系统中除了中断服 务程序之外，所有任务都是平等的，都应该遵循同样的规则和其它任务一起协作运行。基本系统中没有设计杀死任务的调用，这要求各任务都应当设计成某种形式的 无限循环。</p>
<p>任务中等待的条件可以是任务复杂的表达式或都函数调用，也可以是中断服务程序设置的全局变 量（注意加volatile定义）。一般在任务执行时会让下次等待的条件不再满足，避免某个任务一直霸占CPU将系统饿死。在嵌入式软件中还经常会遇到任 务定时启动和超时等待在I/O操作上，在我们的系统中可以维护一个时间计数器，只需在适当的地方记录时刻，然后在任务查询条件中判断当前计数器和记录时刻 之间的差值就可以了。</p>
<p>内核实现的代码则相当简法。由于主要的保护和恢复任务现场的工作都由C语言标准库 setjmp实现了，我们就只需要操纵一下堆栈指针防止不同的任务使用了重叠的局部环境，这个工作在初始化和创建任务的时候通过预定义的两个宏来实现。在 init_coos函数中，记录了主任务（main函数）保留mainstk字节堆栈后的新栈顶位置（stktop），然后在每次creat_task时 都根据要求为每个任务保留stksize字节的堆栈并重新计算下一个stktop。在creat_task函数中利用了setjmp函数的返回值（直接返 回时为0，longjmp跳转返回时非0），使得一方面creat_task能正常回到调用者，又让下次轮转到新任务时能够找到创建时的入口。</p>
<p>co-os.h中定义的WAITFOR使用了一个do{…while(0)实现多语句宏的 C语言小技巧，这样能保证在任何情况下WAITFOR都可以如单条语句一样在源程序中使用，需要担心多了或者少了大括弧破坏if/else匹配之类的问 题，并且，所有的编译器都会优化掉这个假循环。</p>
<p>为了尽量使程序简单并说明问题，以上代码中没有考虑中断相关的数据保护问题。实际运行的系 统中，如果首先写堆栈指针不能一步完成（如AVR这样的8位机），那么，在写操作正在进行的时候绝对不能允许中断；另外，在任务中查询的条件如果和中断有 关，那么也必须考虑数据的完整性。</p>
<p><strong>4 性能分析</strong></p>
<p>所有的协作式多任务系统中任务切换时间都和用户代码（是否长期占用CPU）、就绪时刻有 关，比标准系统略差的是，我们的简单系统中不支持任务优先级，并且完成一轮查询调度的时间还和任务数目有关。这也是为了达到简单和可移植性目标而不得已作 出的牺牲。在各任务间切换查询条件通过C语言标准库函数longjmp实现，一般来讲longjmp函数要从内存中恢复大部分CPU寄存器，执行它也需要 若干条指令的时间。</p>
<p>为了面向嵌入式系统应用，任务控制块（TCB）采用静态数组来实现，这样要求预先确定系统 的最大任务数（co-os.h中的MAX_TSK）。如果需要，也可以通过环波链表来动态管理任务控制块（TCB），这时可以简单实现任务的动态创建和删 除，并且通过指针来访问TCB也要比通过下标（tskid）略快一点。</p>
<p>在某些情况下，如果在中断返回后需要执行某关键任务，可以考虑通过设置“高级中断”的方法 来实现。具体地讲，就是在中断返回前改变返回地址到某函数入口（“高级中断服务程序”），同时保留原返回地址到堆栈中，这样在“高级中断服务程序”完成后 执行return就又回到了正常的多任务查询流程。使用“高级中断”时要注意现场保护的衔接，并且这种技巧显然和CPU和体系结构有关。</p>
<p><strong>5 结论</strong></p>
<p>setjmp是标准C语言中用于远程跳转的库函数，利用它可方便实现一个简单易移植的协作式多任务系统。该系统功能完备、编程简单、易于学习，适合一些中小规模的嵌入式软件使用；并且，以此为基础，还可以用一些与平台相关的编程技巧提高其实时性和灵活性。</p>
<p>部分资料来源于希赛,单片机与嵌入式系统应用</p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/356.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[转] MTK平台芯片概说</title>
		<link>http://www.donevii.com/post/354.html</link>
		<comments>http://www.donevii.com/post/354.html#comments</comments>
		<pubDate>Mon, 26 Nov 2007 05:11:28 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[mtk]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[手机]]></category>
		<category><![CDATA[联发科技]]></category>
		<category><![CDATA[芯片]]></category>
		<category><![CDATA[蕊片]]></category>

		<guid isPermaLink="false">http://www.donevii.com/post/354.html</guid>
		<description><![CDATA[转至 http://blog.sina.com.cn/mobliephone 目前联发科技已开发出MT6205、MT6217、MT6218、MT6219、MT6226、MT6227、MT6228等系列平台,其中MT6205、MT6217、MT6218、MT6219、MT6226、MT6227、MT6228、MT6229、MT6225、MT6223、MT6230均... ]]></description>
			<content:encoded><![CDATA[<p>转至 <a href="http://blog.sina.com.cn/mobliephone" title="http://blog.sina.com.cn/mobliephone">http://blog.sina.com.cn/mobliephone</a></p>
<p>目前联发科技已开发出MT6205、MT6217、MT6218、MT6219、MT6226、MT6227、MT6228等系列平台,其中MT6205、MT6217、MT6218、MT6219、MT6226、MT6227、MT6228、MT6229、MT6225、MT6223、MT6230均为基带芯片，所有芯片均采用ARM7的核。<br />
MT6305为电源管理芯片，有MT6305、MT6305N、MT6305BN；</p>
<p>MT6129、MT6139是射频芯片。MT6129为早期的射频RF芯片，一般与MT6205的CPU一起使用。现在用的多的是MT6129<a href="http://www.donevii.com/post/tag/c" class="st_tag internal_tag" rel="tag" title="Posts tagged with c">C</a>、MT6129N、MT6129D，其中MT6129<a href="http://www.donevii.com/post/tag/c" class="st_tag internal_tag" rel="tag" title="Posts tagged with c">C</a>、MT6129N一般用在MT6217、MT6218、MT6219的CPU的机器上，MT6129D一般用在MT6226、MT6227的CPU的机器上。RF3146（7×7mm）、RF3146D（双频）、RF3166（6×6mm）为RFMD的PA。</p>
<p>MT6205为最早的方案，只有GSM的基本功能，不支持GPRS、WAP、MP3等功能。(2003年MP)；<br />
MT6218为在MT6205基础上增加GPRS、WAP、MP3功能。MT6217为MT6218的cost down方案，与MT6128 PIN TO PIN，只是软件不同而已，另外MT6217支持16bit数据。（2004年MP）<br />
MT6219为MT6218上增加内置AIT的1.3M camera处理IC，增加MP4功能。8bit数据。（2005年MP）<br />
MT6226为MT6219 cost 升级产品，内置0.3M 摄相处理IC，支持GPRS、WAP、MP3、MP4等，内部配置比MT6219优化及改善，比如配蓝牙是可用很便宜的芯片CSR的BC03模块USD3即可支持数据传输（如听立体声MP3等）功能。<br />
MT6226M为MT6226高配置设计，内置的是1.3M摄像处理IC。（2006年MP）<br />
MT6227与MT6226功能基本一样，PIN TO PIN，只是内置的是2.0M 摄像处理IC。（2006年MP）<br />
MT6228比MT6227增加TV OUT功能，内置3.0M 摄像处理IC，支持支持GPRS、WAP、MP3、MP4。（2006年MP）<br />
从MT6226后软件均可支持网络摄像头功能，也就是说你的机子可以用于QQ视频。</p>
<p>MT6229平台支持EDGE功能，其他功能和6228基本一致。</p>
<p>MT6225是6217的代替产品，可以接cam但是没有isp，也就是没有特效，变焦，但是其主频很高和6228/6229一样达到了104mhz,可以接wifi,并且给设计公司提出了更高的要求——如何利用104m的资源去实现mp4的编解码，如何用104m的资源跑更多的应用，这些都是设计公司做的,对设计公司的要求也非常得高。</p>
<p>MT6223是6205的替代，支持语音，短信，MP3,不支持T_F卡，USB盘，没有集成ISP，PMIC内签。</p>
<p>目前市面上出的双卡双待手机,一般是采取的方案分为以下几种:MT6226+6205,MT6225+6205和MT6225+6223</p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/354.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>做网站的一些定律</title>
		<link>http://www.donevii.com/post/317.html</link>
		<comments>http://www.donevii.com/post/317.html#comments</comments>
		<pubDate>Fri, 30 Mar 2007 05:43:33 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[web]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=317</guid>
		<description><![CDATA[1.250定律 拉德认为：每一位顾客身后，大体有250名亲朋好友。如果您赢得了一位顾客的好感，就意味着赢得了250个人的好感；反之，如果你得罪了一名顾客，也就意味着得罪了250 名顾客。&#160;&... ]]></description>
			<content:encoded><![CDATA[<p><strong>1.250定律</strong> </p>
<p>拉德认为：每一位顾客身后，大体有250名亲朋好友。如果您赢得了一位顾客的好感，就意味着赢得了250个人的好感；反之，如果你得罪了一名顾客，也就意味着得罪了250 名顾客。&nbsp;&nbsp; 在你的网站访客中，一个访客可能可以带来一群访客，任何网站都有起步和发展的过程，这个过程中此定律尤其重要。</p>
<p><strong>2.达维多定律</strong></p>
<p>达维多认为，一个企业要想在市场上总是占据主导地位，那么就要做到第一个开发出新产品，又第一个淘汰自己的老产品。&nbsp;&nbsp; 国内网站跟风太严重，比如前段时间的格子网，乞讨网，博客网，一个成功了，大家一拥而上。但实际效果是，第一个出名的往往最成功，所以在网站的定位上，要动自己的脑筋，不是去捡人家剩下的客户。同理，买人家出售的数据来建站效果是很糟糕的。</p>
<p><strong>3.木桶定律</strong></p>
<p>水桶定律是指，一只水桶能装多少水，完全取决于它最短的那块木板。这就是说任何一个组织都可能面临的一个共同问题，即构成组织的各个部分往往决定了整个组织的水平。&nbsp;&nbsp; 注意审视自己的网站，是速度最糟糕？美工最糟糕？宣传最糟糕？你首先要做的，不是改进你最强的，而应该是你最薄弱的。</p>
<p><strong>4.马太效应</strong></p>
<p>《新约》中有这样一个故事，一个国王远行前，交给三个仆人每人一锭银子，吩咐他们：&ldquo;你们去做生意，等我回来时，再来见我。&rdquo;国王回来时，第一个仆人说： &ldquo;主人，你交给我们的一锭银子，我已赚了10锭。&rdquo;于是国王奖励他10座城邑。第二个仆人报告说：&ldquo;主人，你给我的一锭银子，我已赚了5锭。&rdquo; 于是国王例奖励了他5座城邑。第三个仆人报告说：&ldquo;主人，你给我的一锭银子，我一直包在手巾里存着，我怕丢失，一直没有拿出来。&rdquo;于是国王命令将第三个仆人的一锭银子也赏给第一个仆人，并且说：&ldquo;凡是少的，就连他所有的也要夺过来。凡是多的，还要给他，叫他多多益善。&rdquo;这就是马太效应。&nbsp;&nbsp; 在同类网站中，马太效应是很明显的。一个出名的社区，比一个新建的社区，更容易吸引到新客户。启示是，如果你无法把网站做大，那么你要做专。作专之后再做大就更容易。</p>
<p><strong>5.手表定理</strong></p>
<p>手表定理是指一个人有一只表时，可以知道现在是几点钟，而当他同时拥有两只表时却无法确定。<br />一个网站，你只需要关注你特定的用户群需求。不要在意不相干人的看法。</p>
<p><strong>6.不值得定律</strong></p>
<p>不值得定律：不值得做的事情，就不值得做好&nbsp;&nbsp; 不要过度seo，如果你不是想只做垃圾站。不要把时间浪费在美化再美化页面，优化再优化程序，在你网站能盈利后，这些事情可以交给技术人员完成。</p>
<p><strong>7.彼得原理</strong></p>
<p>劳伦斯.彼得认为：在各种组织中，由于习惯于对在某个等级上称职的人员进行晋升提拔，因而雇员总是趋向于晋升到其不称职的地位。<br />不要轻易改变自己网站的定位。如博客网想变门户，盛大想做娱乐，大家拭目以待吧。</p>
<p><strong>8.零和游戏原理</strong></p>
<p>当你看到两位对弈者时，你就可以说他们正在玩&ldquo;零和游戏&rdquo;。因为在大多数情况下， 总会有一个赢，一个输，如果我们把获胜计算为得1分，而输棋为-1分，那么，这两人得分之和就是：1+（-1）=0&nbsp;&nbsp; 不要把目光一直盯在你的竞争网站上，不要花太多时间抢它的访客。我们把这些时间用来寻找互补的合作网站，挖掘新访客。</p>
<p><strong>9.华盛顿合作规律</strong></p>
<p>华盛顿合作规律说的是： 一个人敷衍了事，两个人互相推诿， 三个人则永无成事之日。<br />如果你看准一个方向，你自己干，缺人手就招。不要轻易找同伴一起搞网站，否则你会发现，日子似乎越过越快了，事情越做越慢了。</p>
<p><strong>10.邦尼人力定律</strong></p>
<p>一个人一分钟可以挖一个洞，六十个人一秒种却挖不了一个洞。合作是一个问题，如何合作也是一个问题。你需要有计划。</p>
<p><strong>11.牛蛙效应</strong></p>
<p>把一只牛蛙放在开水锅里，牛蛙会很快跳出来；但当你把它放在冷水里，它不会跳出来，然后慢慢加热，起初牛蛙出于懒惰，不会有什么动作，当水温高到它无法忍受的时候，想出来，但已经没有了力气。&nbsp;&nbsp; 如果你是soho，注意关注你的财务。不要等到没钱了再想怎么挣，你会发现那时候挣钱更难。</p>
<p><strong>12.蘑菇管理</strong></p>
<p>蘑菇管理是许多组织对待初出茅庐者的一种管理方法，初学者被置于阴暗的角落（不受重视的部门，或打杂跑腿的工作），浇上一头大粪（无端的批评、指责、代人受过），任其自生自灭（得不到必要的指导和提携）。<br />&nbsp; 做网站毕竟要遭遇这样的阶段，搜索引擎不理你，友情链接找不到，访客不上门。这是磨练。</p>
<p><strong>13.奥卡姆剃刀定律</strong></p>
<p>如无必要，勿增实体。<br />把网站做得简单，再简单，简单到非常实用，而不是花俏。google的首页为什么比雅虎好？</p>
<p><strong>14.巴莱多定律(Paredo 也叫二八定律)</strong></p>
<p>你所完成的工作里80%的成果，来自于你20%的付出；而80%的付出，只换来20%的成果。<br />随时衡量你所做的工作，哪些是最有效果的。 </p>
<p>1.马蝇效应</p>
<p>林肯少年时和他的兄弟在肯塔基老家的一个农场里犁玉米地，林肯吆马，他兄弟扶犁，而那匹马很懒，慢慢腾腾，走走停停。可是有一段时间马走得飞快。 林肯感到奇怪，到了地头，他发现有一只很大的马蝇叮在马身上，他就把马蝇打落了。看到马蝇被打落了，他兄弟就抱怨说：&rdquo;哎呀，你为什么要打掉它，正是那家伙使马跑起来的嘛！&rdquo;&nbsp;&nbsp; 在你心满意足的时候，去寻找你的马蝇。没有firefox，不会有ie7，firefox就是微软的马蝇之一。马蝇不可怕，怕的是会一口吃掉你的东西，像 ie当初对网景干的那样。 </p>
<p>2.最高气温效应</p>
<p>每天最热总是下午2 时左右，我们总认为这个时候太阳最厉害，其实这时的太阳早已偏西，不再是供给最大热量的时候了。此时气温之所以最高，不过是源于此前的热量积累。<br />你今天的网站流量，是你一个星期或更长时间前所做的事带来的。 </p>
<p>3.超限效应(溢出效应)</p>
<p>刺激过多、过强和作用时间过久而引起心理极不耐烦或反抗的心理现象，称之为&ldquo;超限效应&rdquo;。 别到别人论坛里发太多广告。别在自己网站上放太多广告。别在自己的论坛里太多地太明显地诱导话题。 </p>
<p>4.懒蚂蚁效应</p>
<p>生物学家研究发现，成群的蚂蚁中，大部分蚂蚁很勤劳，寻找、搬运食物争先恐后，少数蚂蚁却东张西望不干活。当食物来源断绝或蚁窝被破坏时，那些勤快的蚂蚁一筹莫展。&ldquo;懒蚂蚁&rdquo;则&ldquo;挺身而出&rdquo;，带领众伙伴向它早已侦察到的新的食物源转移。&nbsp;&nbsp; 不要把注意力仅仅放在一个网站上，即使这个网站现在为你带来一切。你要给自己一些时间寻找新的可行的方向，以备万一。 </p>
<p>5.长尾理论</p>
<p>ChrisAnderson认为，只要存储和流通的渠道足够大，需求不旺或销量不佳的产品共同占据的市场份额就可以和那些数量不多的热卖品所占据的市场份额相匹敌甚至更大。&nbsp;&nbsp; 对于搜索引擎，未必你需要一个热门词排在第一位，如果有一千个冷门词排在第一位，效果不但一样，还会更稳定更长远。 </p>
<p>6.破窗理论</p>
<p>栋建筑上的一块玻璃，又没有及时修好，别人就可能受到某些暗示性的纵容，去打碎更多的玻璃。&nbsp;&nbsp; 管理论坛时，如果你发现第一个垃圾贴，赶紧删掉他吧。想想：落伍现在为什么那么多&times;&times;贴？现在控制比最初控制难多了。 </p>
<p>7.&ldquo;羊群效应&rdquo;，又称复制原则(Copy Strategy)</p>
<p>一个羊群(集体)是一个很散乱的组织，平时大家在一起盲目地左冲右撞。如果一头羊发现了一片肥沃的绿草地，并在那里吃到了新鲜的青草，后来的羊群就会一哄而上，争抢那里的青草，全然不顾旁边虎视眈眈的狼，或者看不到其它地方还有更好的青草。<br />不要轻易跟风，保持自己思考的能力。 </p>
<p>8.墨菲定律</p>
<p>如果坏事情有可能发生，不管这种可能性多么小，它总会发生，并引起最大可能的损失。<br />除非垃圾站，否则不要作弊，对搜索引擎不要，对广告也不要。 </p>
<p>9.光环效应</p>
<p>人们对人的某种品质或特点有清晰的知觉，印象比较深刻、突出， 这种强烈的知觉， 就像月晕形式的光环一样，向周围弥漫、扩散，掩盖了对这个人的其他品质或特点的认识。<br />不要轻易崇拜一个人或者公司、一个概念、一种做法。 </p>
<p>10.蝴蝶效应</p>
<p>一只亚马逊河流域热带雨林中的蝴蝶，偶尔扇动几下翅膀，两周后，可能在美国德克萨斯州引起一场龙卷风。<br />不管你做什么，网站或者其他，你都应该关注新闻。机遇或者灾难可能就在那。 </p>
<p>11.阿尔巴德定理</p>
<p>一个企业经营成功与否，全靠对顾客的要求了解到什么程度。&nbsp;&nbsp; 我赞同别人的点评：看到了别人的需要，你就成功了一半；满足了别人的需求，你就成功了全部。尤其是做网站。 </p>
<p>12.史密斯原则</p>
<p>如果你不能战胜他们，你就加入到他们之中去。&nbsp;&nbsp; <br />不要试图做孤胆英雄。如果潮流挡不住，至少，你要去思考为什么。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/317.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP5 效率优化</title>
		<link>http://www.donevii.com/post/290.html</link>
		<comments>http://www.donevii.com/post/290.html#comments</comments>
		<pubDate>Sat, 13 Jan 2007 10:53:44 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[doc]]></category>
		<category><![CDATA[blog]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[ror]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[server]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[测试]]></category>
		<category><![CDATA[类]]></category>
		<category><![CDATA[缓存]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=290</guid>
		<description><![CDATA[静态调用的成员一定要定义成 static&#160;&#160;(PHP5 ONLY) PHP 5 引入了静态成员的概念，作用和 PHP 4 的函数内部静态变量一致，但前者是作为类的成员来使用。静态变量和 Ruby 的类变量(class variable)... ]]></description>
			<content:encoded><![CDATA[<p><strong><font color="#0000ff" size="2">静态调用的成员一定要定义成 static&nbsp;&nbsp;(<a href="http://www.donevii.com/post/tag/php" class="st_tag internal_tag" rel="tag" title="Posts tagged with php">PHP</a>5 ONLY)</font></strong></p>
<p>PHP 5 引入了静态成员的概念，作用和 PHP 4 的函数内部静态变量一致，但前者是作为类的成员来使用。静态变量和 Ruby 的类变量(<a href="http://www.donevii.com/post/tag/class" class="st_tag internal_tag" rel="tag" title="Posts tagged with class">class</a> variable)差不多，所有类的实例共享同一个静态变量。</p>
<p><font face="Courier New"><font color="#008080">&lt;?php<br /></font><font color="#0000ff">class </font><font color="#008080">foo </font></font><font face="Courier New"><font color="#0000ff">{<br />&nbsp; &nbsp; function </font><font color="#008080">bar</font></font><font face="Courier New"><font color="#0000ff">() {<br />&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;echo </font><font color="#008000">&#8216;foobar&#8217;</font></font><font face="Courier New"><font color="#0000ff">;<br />&nbsp; &nbsp; }<br />}</p>
<p></font><font color="#008080">$foo </font><font color="#0000ff">= new </font><font color="#008080">foo</font></font><font face="Courier New" color="#0000ff">;</p>
<p></font><font face="Courier New"><font color="#808080">// instance way</p>
<p></font><font color="#008080">$foo</font><font color="#0000ff">-&gt;</font><font color="#008080">bar</font></font><font face="Courier New" color="#0000ff">();</p>
<p></font><font face="Courier New"><font color="#808080">// static way</p>
<p></font><font color="#008080">foo</font><font color="#0000ff">::</font><font color="#008080">bar</font></font><font face="Courier New"><font color="#0000ff">();<br /></font><font color="#008080">?&gt;</font></p>
<p></font>静态地调用非 static 成员，效率会比静态地调用 static 成员慢 50-60%。主要是因为前者会产生 E_STRICT 警告，内部也需要做转换。</p>
<p><strong><font color="#0000ff">使用类常量 (PHP5 ONLY)</font></strong></p>
<p>PHP 5 新功能，类似于 <a href="http://www.donevii.com/post/tag/c" class="st_tag internal_tag" rel="tag" title="Posts tagged with c">C</a>++ 的 const。</p>
<p>使用类常量的好处是：</p>
<p>- 编译时解析，没有额外开销<br />- 杂凑表更小，所以内部查找更快<br />- 类常量仅存在于特定「命名空间」，所以杂凑名更短<br />- 代码更干净，使除错更方便</p>
<p><strong><font color="#0000ff">(暂时)不要使用 require/include_once</font></strong></p>
<p>require/include_once 每次被调用的时候都会打开目标文件！</p>
<p>- 如果用绝对路径的话，PHP 5.2/6.0 不存在这个问题<br />- 新版的 APC 缓存系统已经解决这个问题</p>
<p>文件 I/O 增加 =&gt; 效率降低</p>
<p>如果需要，可以自行检查文件是否已被 require/include。</p>
<p><strong><font color="#0000ff">不要调用毫无意义的函数</font></strong></p>
<p>有对应的常量的时候，不要使用函数。</p>
<p><font face="Courier New"><font color="#008080">&lt;?php<br />php_uname</font><font color="#0000ff">(</font><font color="#008000">&#8216;s&#8217;</font><font color="#0000ff">) == </font><font color="#008080">PHP_OS</font></font><font face="Courier New"><font color="#0000ff">;<br /></font><font color="#008080">php_version</font><font color="#0000ff">() == </font><font color="#008080">PHP_VERSION</font></font><font face="Courier New"><font color="#0000ff">;<br /></font><font color="#008080">php_sapi_name</font><font color="#0000ff">() == </font><font color="#008080">PHP_SAPI</font></font><font face="Courier New"><font color="#0000ff">;<br /></font><font color="#008080">?&gt;</font><br /></font>虽然使用不多，但是效率提升大概在 3500% 左右。</p>
<p><strong><font color="#0000ff">最快的 Win32 检查</font></strong></p>
<p><font face="Courier New"><font color="#008080">&lt;?php<br />$is_win </font><font color="#0000ff">= </font><font color="#008080">DIRECTORY_SEPARATOR </font><font color="#0000ff">== &#8216;\\&#8217;</font></font><font face="Courier New"><font color="#008080">;<br />?&gt;</font></p>
<p></font>- 不用函数<br />- Win98/NT/2000/XP/Vista/Longhorn/Shorthorn/Whistler&#8230;通用<br />- 一直可用</p>
<p><strong><font color="#ffa500">时间问题 (PHP&gt;5.1.0 ONLY)</font></strong></p>
<p>你如何在你的软件中得知现在的时间？简单，「time() time() again, you ask me&#8230;」。</p>
<p>不过总归会调用函数，慢。</p>
<p>现在好了，用 $_<a href="http://www.donevii.com/post/tag/server" class="st_tag internal_tag" rel="tag" title="Posts tagged with server">SERVER</a>['REQUEST_TIME']，不用调用函数，又省了。</p>
<p><strong><font color="#0000ff">加速 PCRE</font></strong></p>
<p>&nbsp;对于不用保存的结果，不用 ()，一律用 (?:)</p>
<p>这样 PHP 不用为符合的内容分配内存，省。效率提升 15% 左右。</p>
<p>- 能不用正则，就不用正则，在分析的时候仔细阅读手册「字符串函数」部分。有没有你漏掉的好用的函数？</p>
<p><strong><font color="#0000ff">加速 strtr</font></strong></p>
<p>如果需要转换的全是单个字符的时候，用字符串而不是数组来做 strtr：</p>
<p><font face="Courier New"><font color="#008080">&lt;?php<br />$addr </font><font color="#0000ff">= </font><font color="#008080">strtr</font><font color="#0000ff">(</font><font color="#008080">$addr</font><font color="#0000ff">, </font><font color="#008000">&quot;abcd&quot;</font><font color="#0000ff">, </font><font color="#008000">&quot;efgh&quot;</font><font color="#0000ff">); </font></font><font face="Courier New"><font color="#808080">// good<br /></font><font color="#008080">$addr </font><font color="#0000ff">= </font><font color="#008080">strtr</font><font color="#0000ff">(</font><font color="#008080">$addr</font><font color="#0000ff">, array(</font><font color="#008000">&#8216;a&#8217; </font><font color="#0000ff">=&gt; </font><font color="#008000">&#8216;e&#8217;</font></font><font face="Courier New" color="#0000ff">,<br />&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;</font><font face="Courier New"><font color="#808080">// &#8230;<br />&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;</font><font color="#0000ff">)); </font></font><font face="Courier New"><font color="#808080">// bad<br /></font><font color="#008080">?&gt;</font></font><br />效率提升：10 倍。</p>
<p><strong><font color="#ffa500">不要做无谓的替换</font></strong></p>
<p>即使没有替换，str_replace 也会为其参数分配内存。很慢！解决办法：</p>
<p>- 用 strpos 先查找(非常快)，看是否需要替换，如果需要，再替换</p>
<p>效率：</p>
<p>- 如果需要替换：效率几乎相等，差别在 0.1% 左右。<br />- 如果不需要替换：用 strpos 快 200%。</p>
<p><strong><font color="#0000ff">邪恶的 @ 操作符</font></strong></p>
<p>不要滥用 @ 操作符。虽然 @ 看上去很简单，但是实际上后台有很多操作。用 @ 比起不用 @，效率差距：3 倍。</p>
<p>特别不要在循环中使用 @，在 5 次循环的测试中，即使是先用 error_reporting(0) 关掉错误，在循环完成后再打开，都比用 @ 快。</p>
<p><strong><font color="#0000ff">善用 strncmp</font></strong></p>
<p>当需要对比「前 n 个字符」是否一样的时候，用 strncmp/strncasecmp，而不是 substr/strtolower，更不是 PCRE，更千万别提 ereg。strncmp/strncasecmp 效率最高(虽然高得不多)。</p>
<p><strong><font color="#0000ff">慎用 substr_compare (PHP5 ONLY)</font></strong></p>
<p>按照上面的道理，substr_compare 应该比先 substr 再比较快咯。答案是否定的，除非：</p>
<p>- 无视大小写的比较<br />- 比较较大的字符串</p>
<p><strong><font color="#0000ff">不要用常量代替字符串</font></strong></p>
<p>为什么：</p>
<p>- 需要查询杂凑表两次<br />- 需要把常量名转换为小写(进行第二次查询的时候)<br />- 生成 E_NOTICE 警告<br />- 会建立临时字符串</p>
<p>效率差别：700%。</p>
<p><strong><font color="#0000ff">不要把 count/strlen/sizeof 放到 for 循环的条件语句中</font></strong></p>
<p>贴士：我的个人做法</p>
<p><font face="Courier New"><font color="#008080">&lt;?php<br /></font><font color="#0000ff">for (</font><font color="#008080">$i </font><font color="#0000ff">= </font><font color="#008080">0</font><font color="#0000ff">, </font><font color="#008080">$max </font><font color="#0000ff">= </font><font color="#008080">count</font><font color="#0000ff">(</font><font color="#008080">$array</font><font color="#0000ff">);</font><font color="#008080">$i </font><font color="#0000ff">&lt; </font><font color="#008080">$max</font><font color="#0000ff">; ++</font><font color="#008080">$i</font></font><font face="Courier New"><font color="#0000ff">);<br /></font><font color="#008080">?&gt;</font></font></p>
<p>效率提升相对于：</p>
<p>- count 50%<br />- strlen 75%</p>
<p><strong><font color="#ffa500">短的代码不一定快</font></strong></p>
<p><font face="Courier New" color="#008080">&lt;?php<br /></font><font face="Courier New"><font color="#808080">// longest<br /></font><font color="#0000ff">if (</font><font color="#008080">$a </font><font color="#0000ff">== </font><font color="#008080">$b</font></font><font face="Courier New"><font color="#0000ff">) {<br />&nbsp; &nbsp; </font><font color="#008080">$str </font><font color="#0000ff">.= </font><font color="#008080">$a</font></font><font face="Courier New"><font color="#0000ff">;<br />} else {<br />&nbsp; &nbsp; </font><font color="#008080">$str </font><font color="#0000ff">.= </font><font color="#008080">$b</font></font><font color="#0000ff"><font face="Courier New">;<br />}</font><br /></font><font face="Courier New"><font color="#808080">// longer<br /></font><font color="#0000ff">if (</font><font color="#008080">$a </font><font color="#0000ff">== </font><font color="#008080">$b</font></font><font face="Courier New"><font color="#0000ff">) {<br />&nbsp; &nbsp; </font><font color="#008080">$str </font><font color="#0000ff">.= </font><font color="#008080">$a</font></font><font face="Courier New"><font color="#0000ff">;<br />}<br /></font><font color="#008080">$str </font><font color="#0000ff">.= </font><font color="#008080">$b</font></font><font face="Courier New" color="#0000ff">;</p>
<p></font><font face="Courier New"><font color="#808080">// short<br /></font><font color="#008080">$str </font><font color="#0000ff">.= (</font><font color="#008080">$a </font><font color="#0000ff">== </font><font color="#008080">$b </font><font color="#0000ff">? </font><font color="#008080">$a </font><font color="#0000ff">: </font><font color="#008080">$b</font></font><font face="Courier New"><font color="#0000ff">);<br /></font><font color="#008080">?&gt;</font></font><br />你觉得哪个快？</p>
<p>效率比较：</p>
<p>- longest: 4.27<br />- longer: 4.43<br />- short: 4.76</p>
<p>不可思议？再来一个：<br /><font face="Courier New" color="#008080">&lt;?php<br /></font><font face="Courier New"><font color="#808080">// original<br /></font><font color="#008080">$d </font><font color="#0000ff">= </font><font color="#008080">dir</font><font color="#0000ff">(</font><font color="#008000">&#8216;.&#8217;</font></font><font face="Courier New"><font color="#0000ff">);<br />while ((</font><font color="#008080">$entry </font><font color="#0000ff">= </font><font color="#008080">$d</font><font color="#0000ff">-&gt;</font><font color="#008080">read</font><font color="#0000ff">()) !== </font><font color="#008080">false</font></font><font face="Courier New"><font color="#0000ff">) {<br />&nbsp; &nbsp; if (</font><font color="#008080">$entry </font><font color="#0000ff">== </font><font color="#008000">&#8216;.&#8217; </font><font color="#0000ff">|| </font><font color="#008080">$entry </font><font color="#0000ff">== </font><font color="#008000">&#8216;..&#8217;</font></font><font face="Courier New" color="#0000ff">) {<br />&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;continue;<br />&nbsp; &nbsp; }<br />}</p>
<p></font><font face="Courier New"><font color="#808080">// versus<br /></font><font color="#008080">glob</font><font color="#0000ff">(</font><font color="#008000">&#8216;./*&#8217;</font></font><font face="Courier New" color="#0000ff">);</p>
<p></font><font face="Courier New"><font color="#808080">// versus (include . and ..)<br /></font><font color="#008080">scandir</font><font color="#0000ff">(</font><font color="#008000">&#8216;.&#8217;</font></font><font face="Courier New"><font color="#0000ff">);<br /></font><font color="#008080">?&gt;</font></font><br />哪个快？</p>
<p>效率比较：</p>
<p>- original: 3.37<br />- glob: 6.28<br />- scandir: 3.42<br />- original without OO: 3.14<br />- SPL (PHP5): 3.95</p>
<p>画外音：从此也可以看出来 PHP5 的面向对象效率提高了很多，效率已经和纯函数差得不太多了。</p>
<p><strong><font color="#0000ff">提高 PHP 文件访问效率</font></strong></p>
<p>需要包含其他 PHP 文件的时候，使用完整路径，或者容易转换的相对路径。</p>
<p><font face="Courier New"><font color="#008080">&lt;?php</p>
<p></font><font color="#0000ff">include </font><font color="#008000">&#8216;file.php&#8217;</font><font color="#0000ff">; </font></font><font face="Courier New"><font color="#808080">// bad approach</p>
<p></font><font color="#008080">incldue </font><font color="#008000">&#8216;./file.php&#8217;</font><font color="#0000ff">; </font></font><font face="Courier New"><font color="#808080">// good</p>
<p></font><font color="#0000ff">include </font><font color="#008000">&#8216;/path/to/file.php&#8217;</font><font color="#0000ff">; </font></font><font face="Courier New"><font color="#808080">// ideal</p>
<p></font><font color="#008080">?&gt;</font></font></p>
<p><strong><font color="#0000ff">物尽其用</font></strong></p>
<p>PHP 有很多扩展和函数可用，在实现一个功能的之前，应该看看 PHP 是否有了这个功能？是否有更简单的实现？</p>
<p><font face="Courier New"><font color="#008080">&lt;?php<br />$filename </font><font color="#0000ff">= </font><font color="#008000">&quot;./somepic.gif&quot;</font></font><font face="Courier New"><font color="#0000ff">;<br /></font><font color="#008080">$handle </font><font color="#0000ff">= </font><font color="#008080">fopen</font><font color="#0000ff">(</font><font color="#008080">$filename</font><font color="#0000ff">, </font><font color="#008000">&quot;rb&quot;</font></font><font face="Courier New"><font color="#0000ff">);<br /></font><font color="#008080">$contents </font><font color="#0000ff">= </font><font color="#008080">fread</font><font color="#0000ff">(</font><font color="#008080">$handle</font><font color="#0000ff">, </font><font color="#008080">filesize</font><font color="#0000ff">(</font><font color="#008080">$filename</font></font><font face="Courier New"><font color="#0000ff">));<br /></font><font color="#008080">fclose</font><font color="#0000ff">(</font><font color="#008080">$handle</font></font><font face="Courier New" color="#0000ff">);</p>
<p></font><font face="Courier New"><font color="#808080">// vs. much simpler</p>
<p></font><font color="#008080">file_get_contents</font><font color="#0000ff">(</font><font color="#008000">&#8216;./somepic.gif&#8217;</font></font><font face="Courier New"><font color="#0000ff">);<br /></font><font color="#008080">?&gt;</font></font></p>
<p><strong><font color="#ffa500">关于引用的技巧</font></strong></p>
<p>引用可以：</p>
<p>- 简化对复杂结构数据的访问<br />- 优化内存使用<br /><font face="Courier New"><font color="#008080">&lt;?php<br />$a</font><font color="#0000ff">[</font><font color="#008000">'b'</font><font color="#0000ff">][</font><font color="#008000">'c'</font></font><font face="Courier New" color="#0000ff">] = array();</p>
<p></font><font face="Courier New"><font color="#808080">// slow 2 extra hash lookups per access<br /></font><font color="#0000ff">for (</font><font color="#008080">$i </font><font color="#0000ff">= </font><font color="#008080">0</font><font color="#0000ff">; </font><font color="#008080">$i </font><font color="#0000ff">&lt; </font><font color="#008080">5</font><font color="#0000ff">; ++</font><font color="#008080">$i</font></font><font face="Courier New"><font color="#0000ff">)<br />&nbsp; &nbsp; </font><font color="#008080">$a</font><font color="#0000ff">[</font><font color="#008000">'b'</font><font color="#0000ff">][</font><font color="#008000">'c'</font><font color="#0000ff">][</font><font color="#008080">$i</font><font color="#0000ff">] = </font><font color="#008080">$i</font></font><font face="Courier New" color="#0000ff">;</p>
<p></font><font face="Courier New"><font color="#808080">// much faster reference based approach<br /></font><font color="#008080">$ref </font><font color="#0000ff">=&amp; </font><font color="#008080">$a</font><font color="#0000ff">[</font><font color="#008000">'b'</font><font color="#0000ff">][</font><font color="#008000">'c'</font></font><font face="Courier New"><font color="#0000ff">];<br />for (</font><font color="#008080">$i </font><font color="#0000ff">= </font><font color="#008080">0</font><font color="#0000ff">; </font><font color="#008080">$i </font><font color="#0000ff">&lt; </font><font color="#008080">5</font><font color="#0000ff">; ++</font><font color="#008080">$i</font></font><font face="Courier New"><font color="#0000ff">)<br />&nbsp; &nbsp; </font><font color="#008080">$ref</font><font color="#0000ff">[</font><font color="#008080">$i</font><font color="#0000ff">] = </font><font color="#008080">$i</font></font><font face="Courier New"><font color="#0000ff">;<br /></font><font color="#008080">?&gt;</font></font><br /><font face="Courier New"><font color="#008080">&lt;?php<br />$a </font><font color="#0000ff">= </font><font color="#008000">&#8216;large string&#8217;</font></font><font face="Courier New" color="#0000ff">;</p>
<p></font><font face="Courier New"><font color="#808080">// memory intensive approach<br /></font><font color="#0000ff">function </font><font color="#008080">a</font><font color="#0000ff">(</font><font color="#008080">$str</font></font><font face="Courier New"><font color="#0000ff">)<br />{<br />&nbsp; &nbsp; return </font><font color="#008080">$str</font><font color="#0000ff">.</font><font color="#008000">&#8216;something&#8217;</font></font><font face="Courier New" color="#0000ff">;<br />}</p>
<p></font><font face="Courier New"><font color="#808080">// more efficient solution<br /></font><font color="#0000ff">function </font><font color="#008080">a</font><font color="#0000ff">(&amp;</font><font color="#008080">$str</font></font><font face="Courier New"><font color="#0000ff">)<br />{<br />&nbsp; &nbsp; </font><font color="#008080">$str </font><font color="#0000ff">.= </font><font color="#008000">&#8216;something&#8217;</font></font><font face="Courier New"><font color="#0000ff">;<br />}<br /></font><font color="#008080">?&gt;</font></font><br />==============================================<br />参考资料<br /><a href="http://ilia.ws/" target="_blank" snap_preview_added="spa">http://ilia.ws</a></p>
<p>Ilia 的个人网站，<a href="http://www.donevii.com/post/tag/blog" class="st_tag internal_tag" rel="tag" title="Posts tagged with blog">Blog</a>，他参与的开发以及出版的一些稿物链接等等。<br /><a href="http://ez.no/" target="_blank" snap_preview_added="spa">http://ez.no</a></p>
<p>eZ components 官方网站，eZ comp 是针对 PHP5 的开源通用库，以效率为己任，Ilia 也参与了开发。<br /><a href="http://phparch.com/" target="_blank" snap_preview_added="spa">http://phparch.com</a></p>
<p>php|architect，不错的 php 出版商/培训组织。买不起或者买不到的话，网上可以下到很多经典的盗版。</p>
<p><a href="http://talks.php.net/" target="_blank" snap_preview_added="spa">http://talks.php.net</a></p>
<p></p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/290.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mysql 优化</title>
		<link>http://www.donevii.com/post/259.html</link>
		<comments>http://www.donevii.com/post/259.html#comments</comments>
		<pubDate>Wed, 27 Dec 2006 03:41:32 +0000</pubDate>
		<dc:creator>gavinkwoe</dc:creator>
				<category><![CDATA[software & hardware]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=259</guid>
		<description><![CDATA[7.3 锁 7.3.1 锁机制 当前MySQL已经支持 ISAM, MyISAM, MEMORY (HEAP) 类型表的表级锁了，BDB 表支持页级锁，InnoDB 表支持行级锁。 很多时候，可以通过经验来猜测什么样的锁对应用程序更合适，不过通常... ]]></description>
			<content:encoded><![CDATA[<p style="TEXT-INDENT: 2em">7.3 锁 </p>
<p style="TEXT-INDENT: 2em">7.3.1 锁机制 </p>
<p style="TEXT-INDENT: 2em">当前MySQL已经支持 ISAM, MyISAM, MEMORY (HEAP) 类型表的表级锁了，BDB 表支持页级锁，InnoDB 表支持行级锁。 </p>
<p style="TEXT-INDENT: 2em">很多时候，可以通过经验来猜测什么样的锁对应用程序更合适，不过通常很难说一个锁比别的更好，这全都要依据应用程序来决定，不同的地方可能需要不同的锁。 </p>
<p style="TEXT-INDENT: 2em">想要决定是否需要采用一个支持行级锁的存储引擎，就要看看应用程序都要做什么，其中的查询、更新语句是怎么用的。例如，很多的web应用程序大量的做查询，很少删除，主要是基于索引的更新，只往特定的表中插入记录。采用基本的MySQL MyISAM 表就很合适了。 </p>
<p style="TEXT-INDENT: 2em">MySQL中对表级锁的存储引擎来说是释放死锁的。避免死锁可以这样做到：在任何查询之前先请求锁，并且按照请求的顺序锁表。 </p>
<p style="TEXT-INDENT: 2em">MySQL中用于 WRITE（写） 的表锁的实现机制如下： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">如果表没有加锁，那么就加一个写锁。 </p>
<p style="TEXT-INDENT: 2em">否则的话，将请求放到写锁队列中。 </p>
<p style="TEXT-INDENT: 2em">MySQL中用于 READ（读） 的表锁的实现机制如下： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">如果表没有加写锁，那么就加一个读锁。 </p>
<p style="TEXT-INDENT: 2em">否则的话，将请求放到读锁队列中。 </p>
<p style="TEXT-INDENT: 2em">当锁释放后，写锁队列中的线程可以用这个锁资源，然后才轮到读锁队列中的线程。 </p>
<p style="TEXT-INDENT: 2em">这就是说，如果表里有很多更新操作的话，那么 SELECT 必须等到所有的更新都完成了之后才能开始。 </p>
<p style="TEXT-INDENT: 2em">从 MySQL 3.23.33 开始，可以通过状态变量 Table_locks_waited 和 Table_locks_immediate 来分析系统中的锁表争夺情况： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">mysql&gt; SHOW STATUS LIKE &#8216;Table%&#8217;; </p>
<p style="TEXT-INDENT: 2em">+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;+ </p>
<p style="TEXT-INDENT: 2em">| Variable_name | Value | </p>
<p style="TEXT-INDENT: 2em">+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;+ </p>
<p style="TEXT-INDENT: 2em">| Table_locks_immediate | 1151552 | </p>
<p style="TEXT-INDENT: 2em">| Table_locks_waited | 15324 | </p>
<p style="TEXT-INDENT: 2em">+&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;+ </p>
<p style="TEXT-INDENT: 2em">在 MySQL 3.23.7（在Windows上是3.23.25）以后，在 MyISAM 表中只要没有冲突的 INSERT 操作，就可以无需使用锁表自由地并行执行 INSERT 和 SELECT 语句。也就是说，可以在其它客户端正在读取 MyISAM 表记录的同时时插入新记录。如果数据文件的中间没有空余的磁盘块的话，就不会发生冲突了，因为这种情况下所有的新记录都会写在数据文件的末尾（当在表的中间做删除或者更新操作时，就可能导致空洞）。当空洞被新数据填充后，并行插入特性就会自动重新被启用了。 </p>
<p style="TEXT-INDENT: 2em">如果想要在一个表上做大量的 INSERT 和 SELECT 操作，但是并行的插入却不可能时，可以将记录插入到临时表中，然后定期将临时表中的数据更新到实际的表里。可以用以下命令实现： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">mysql&gt; LOCK TABLES real_table WRITE, insert_table WRITE; </p>
<p style="TEXT-INDENT: 2em">mysql&gt; INSERT INTO real_table SELECT * FROM insert_table; </p>
<p style="TEXT-INDENT: 2em">mysql&gt; TRUNCATE TABLE insert_table; </p>
<p style="TEXT-INDENT: 2em">mysql&gt; UNLOCK TABLES; </p>
<p style="TEXT-INDENT: 2em">InnoDB 使用行级锁，BDB 使用页级锁。对于 InnoDB 和 BDB 存储引擎来说，是可能产生死锁的。这是因为 InnoDB 会自动捕获行锁，BDB 会在执行 SQL 语句时捕获页锁的，而不是在事务的开始就这么做。 </p>
<p style="TEXT-INDENT: 2em">行级锁的优点有： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">在很多线程请求不同记录时减少冲突锁。 </p>
<p style="TEXT-INDENT: 2em">事务回滚时减少改变数据。 </p>
<p style="TEXT-INDENT: 2em">使长时间对单独的一行记录加锁成为可能。 </p>
<p style="TEXT-INDENT: 2em">行级锁的缺点有： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">比页级锁和表级锁消耗更多的内存。 </p>
<p style="TEXT-INDENT: 2em">当在大量表中使用时，比页级锁和表级锁更慢，因为他需要请求更多的所资源。 </p>
<p style="TEXT-INDENT: 2em">当需要频繁对大部分数据做 GROUP BY 操作或者需要频繁扫描整个表时，就明显的比其它锁更糟糕。 </p>
<p style="TEXT-INDENT: 2em">使用更高层的锁的话，就能更方便的支持各种不同的类型应用程序，因为这种锁的开销比行级锁小多了。 </p>
<p style="TEXT-INDENT: 2em">表级锁在下列几种情况下比页级锁和行级锁更优越： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">很多操作都是读表。 </p>
<p style="TEXT-INDENT: 2em">在严格条件的索引上读取和更新，当更新或者删除可以用单独的索引来读取得到时： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">UPDATE tbl_name SET column=value WHERE unique_key_col=key_value; </p>
<p style="TEXT-INDENT: 2em">DELETE FROM tbl_name WHERE unique_key_col=key_value; </p>
<p style="TEXT-INDENT: 2em">SELECT 和 INSERT 语句并发的执行，但是只有很少的 UPDATE 和 DELETE 语句。 </p>
<p style="TEXT-INDENT: 2em">很多的扫描表和对全表的 GROUP BY 操作，但是没有任何写表。 </p>
<p style="TEXT-INDENT: 2em">表级锁和行级锁或页级锁之间的不同之处还在于： </p>
<p style="TEXT-INDENT: 2em">将同时有一个写和多个读的地方做版本（例如在MySQL中的并发插入）。也就是说，数据库/表支持根据开始访问数据时间点的不同支持各种不同的试图。其它名有：时间行程，写复制，或者是按需复制。 </p>
<p style="TEXT-INDENT: 2em">原文： Versioning (such as we use in MySQL for concurrent inserts) where you can have one writer at the same time as many readers. This means that the database/table supports different views for the data depending on when you started to access it. Other names for this are time travel, copy on write, or copy on demand. </p>
<p style="TEXT-INDENT: 2em">按需复制在很多情况下比页级锁或行级锁好多了。尽管如此，最坏情况时还是比其它正常锁使用了更多的内存。 </p>
<p style="TEXT-INDENT: 2em">可以用应用程序级锁来代替行级锁，例如MySQL中的 GET_LOCK() 和 RELEASE_LOCK()。但它们是劝告锁（原文：These are advisory locks），因此只能用于安全可信的应用程序中。 </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">7.3.2 锁表 </p>
<p style="TEXT-INDENT: 2em">为了能有快速的锁，MySQL除了 InnoDB 和 BDB 这两种存储引擎外，所有的都是用表级锁（而非页、行、列级锁）。 </p>
<p style="TEXT-INDENT: 2em">对于 InnoDB 和 BDB 表，MySQL只有在指定用 LOCK TABLES 锁表时才使用表级锁。在这两种表中，建议最好不要使用 LOCK TABLES，因为 InnoDB 自动采用行级锁，BDB 用页级锁来保证事务的隔离。 </p>
<p style="TEXT-INDENT: 2em">如果数据表很大，那么在大多数应用中表级锁会比行级锁好多了，不过这有一些陷阱。 </p>
<p style="TEXT-INDENT: 2em">表级锁让很多线程可以同时从数据表中读取数据，但是如果另一个线程想要写数据的话，就必须要先取得排他访问。正在更新数据时，必须要等到更新完成了，其他线程才能访问这个表。 </p>
<p style="TEXT-INDENT: 2em">更新操作通常认为比读取更重要，因此它的优先级更高。不过最好要先确认，数据表是否有很高的 SELECT 操作，而更新操作并非很&lsquo;急需&rsquo;。 </p>
<p style="TEXT-INDENT: 2em">表锁锁在一个线程在等待，因为磁盘空间满了，但是却需要有空余的磁盘空间，这个线程才能继续处理时就有问题了。这种情况下，所有要访问这个出问题的表的线程都会被置为等待状态，直到有剩余磁盘空间了。 </p>
<p style="TEXT-INDENT: 2em">表锁在以下设想情况中就不利了： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">一个客户端提交了一个需要长时间运行的 SELECT 操作。 </p>
<p style="TEXT-INDENT: 2em">其他客户端对同一个表提交了 UPDATE 操作，这个客户端就要等到 SELECT 完成了才能开始执行。 </p>
<p style="TEXT-INDENT: 2em">其他客户端也对同一个表提交了 SELECT 请求。由于 UPDATE 的优先级高于 SELECT，所以 SELECT 就会先等到 UPDATE 完成了之后才开始执行，它也在等待第一个 SELECT 操作。 </p>
<p style="TEXT-INDENT: 2em">下列所述可以减少表锁带来的资源争夺： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">让 SELECT 速度尽量快，这可能需要创建一些摘要表。 </p>
<p style="TEXT-INDENT: 2em">启动 mysqld 时使用参数 &#8211;low-priority-updates。这就会让更新操作的优先级低于 SELECT。这种情况下，在上面的假设中，第二个 SELECT 就会在 INSERT 之前执行了，而且也无需等待第一个SELECT 了。 </p>
<p style="TEXT-INDENT: 2em">可以执行 SET LOW_PRIORITY_UPDATES=1 命令，指定所有的更新操作都放到一个指定的链接中去完成。详情请看&ldquo;14.5.3.1 SET Syntax&rdquo;。 </p>
<p style="TEXT-INDENT: 2em">用 LOW_PRIORITY 属性来降低 INSERT，UPDATE，DELETE 的优先级。 </p>
<p style="TEXT-INDENT: 2em">用 HIGH_PRIORITY 来提高 SELECT 语句的优先级。详情请看&ldquo;14.1.7 SELECT Syntax&rdquo;。 </p>
<p style="TEXT-INDENT: 2em">从MySQL 3.23.7 开始，可以在启动 mysqld 时指定系统变量 max_write_lock_count 为一个比较低的值，它能强制临时地提高表的插入数达到一个特定值后的所有 SELECT 操作的优先级。它允许在 WRITE 锁达到一定数量后有 READ 锁。 </p>
<p style="TEXT-INDENT: 2em">当 INSERT 和 SELECT 一起使用出现问题时，可以转而采用 MyISAM 表，它支持并发的SELECT 和 INSERT 操作。 </p>
<p style="TEXT-INDENT: 2em">当在同一个表上同时有插入和删除操作时，INSERT DELAYED 可能会很有用。详情请看&ldquo;14.1.4.2 INSERT DELAYED Syntax&rdquo;。 </p>
<p style="TEXT-INDENT: 2em">当 SELECT 和 DELETE 一起使用出现问题时，DELETE 的 LIMIT 参数可能会很有用。详情请看&ldquo;14.1.1 DELETE Syntax&rdquo; </p>
<p style="TEXT-INDENT: 2em">执行 SELECT 时使用 SQL_BUFFER_RESULT 有助于减短锁表的持续时间.详情请看&ldquo;14.1.7 SELECT Syntax&rdquo;。 </p>
<p style="TEXT-INDENT: 2em">可以修改源代码 `mysys/thr_lock.<a href="http://www.donevii.com/post/tag/c" class="st_tag internal_tag" rel="tag" title="Posts tagged with c">c</a>&#8217;，只用一个所队列。这种情况下，写锁和读锁的优先级就一样了，这对一些应用可能有帮助。 </p>
<p style="TEXT-INDENT: 2em">以下是MySQL锁的一些建议： </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">只要对同一个表没有大量的更新和查询操作混在一起，目前的用户并不是问题。 </p>
<p style="TEXT-INDENT: 2em">执行 LOCK TABLES 来提高速度（很多更新操作放在一个锁之中比没有锁的很多更新快多了）。将数据拆分开到多个表中可能也有帮助。 </p>
<p style="TEXT-INDENT: 2em">当MySQL碰到由于锁表引起的速度问题时，将表类型转换成 InnoDB 或 BDB 可能有助于提高性能。详情请看&ldquo;16 The InnoDB Storage Engine&rdquo;和&ldquo;15.4 The BDB (BerkeleyDB) Storage Engine&rdquo;。</p>
<p style="TEXT-INDENT: 2em">来源:网络</p>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/259.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>设计模式之Life time controller模式</title>
		<link>http://www.donevii.com/post/229.html</link>
		<comments>http://www.donevii.com/post/229.html#comments</comments>
		<pubDate>Mon, 27 Nov 2006 08:45:42 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[c/c++/c#]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[unix]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=229</guid>
		<description><![CDATA[转至 www.Huihoo.org (作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作) 目的 对象生命周期管理者模式可以被用来控制对象的整个生命周期，从对象被首次使用前创建它们到应用程... ]]></description>
			<content:encoded><![CDATA[<p>转至 <a href="http://www.Huihoo.org">www.Huihoo.org</a></p>
<p></p>
<p> (作者:Douglas C. Schmidt ,<font color="green"><em>by </em>huihoo.org</font> CORBA课题 Thzhang 译 , Allen整理,制作) </p>
<h3>目的</h3>
<p>对象生命周期管理者模式可以被用来控制对象的整个生命周期，从对象被首次使用前创建它们到应用程序中止前完全的销毁它们。此外通过在应用启动/中止时进行对象自动的预先创建/销毁，使这个模式能够用来替代静态对象的创建/销毁。 </p>
<h3>例子</h3>
<p>单例（singleton）是一种通用的创建模式，它对唯一的类实例提供了一个全局的访问点同时能够延迟实例的创建直到它首次被访问。如果一个单例在程序 的整个生命周期中没有被需要，它将不会被创建。单例模式并没有提及在什么时候它的实例应该被销毁这个问题，但对于特定的应用或操作系统这将是个问题。 <br />为了说明为什么提及销毁语义是重要的，考虑下面的日志组件，它通过向客户提供编程API接口实现分布式的日志服务。 		 </p>
<pre><code><a href="http://www.donevii.com/post/tag/class" class="st_tag internal_tag" rel="tag" title="Posts tagged with class">class</a> Logger{public:// Global access point to Logger singleton.static Logger *instance (void) {if (instance_ == 0)instance_ = new Logger;return instance_;}// Write some information to the log.int log (const char *format, ...);protected:// Default constructor (protected to// ensure Singleton pattern usage).Logger (void);static Logger *instance_;// Contained Logger singleton instance.// . . . other resources that are held by the singleton . . .};// Initialize the instance pointer.Logger *Logger::instance_ = 0;

</code> </pre>
<p>Logger的构造函数，出于简化而忽略掉了，实现分配各种OS的endsystem资源，像SOCKET句柄，共享内存段，和/或系统范围的信号量，它们被用于实现日志服务提供的客户API。 <br />为了减少尺寸，提高记录信息的可读性，一个应用可以选择用批处理而不是分立的方式来记录某些数据，像时间统计数据。例如，下面的统计类根据每个单独的标识，批量处理时间数据：  </p>
<pre><code>class Stats{public:// Global access point to the statistics singleton.static Stats *instance (void) {if (instance_ == 0)instance_ = new Stats;return instance_;}// Record a timing data point.int record (int id,const timeval &amp;tv);// Report recorded statistics to the log.void report (int id) {Logger::instance ()-&gt;log (&quot;Avg timing %d: &quot;&quot;%ld sec %ld usec\n&quot;,id,average_i (id).tv_sec,average_i (id).tv_usec);}protected:// Default constructor.Stats (void);// Internal accessor for an average.const timeval &amp;average_i (void);// Contained Stats singleton instance.static Stats *instance_;// . . . other resources that are held by the instance . . .};// Initialize the instance pointer.Stats *Stats::instance_ = 0;}</code> </pre>
<p>在记录了各种统计数据后，程序调用report方法，它将使用Logger单例根据标识来记录平均的时间统计。 <br />Logger和Stats类对应用提供了截然不同的服务：Logger类提供了通用的日志能力，而Stats类提供了具体化的批量处理和记录时间统计功能。这些类都是使用单例模式实现的，于是在应用中每一个类都只有一个实例。 <br />下面的例子展示了一个应用可能使用Logger和Stats单例对象的一种情况： </p>
<pre><code>int main (int argc, char *argv[]){// Interval timestamps.timeval start_tv, stop_tv;// Logger, Stats singletons do not yet exist.// Logger and Stats singletons created  during the first iteration.for (int i = 0; i &lt; argc; ++i) {::gettimeofday (&amp;start_tv);// do some work between timestamps . . .::gettimeofday (&amp;stop_tv);// then record the stats . . .timeval delta_tv;delta_tv.sec = stop_tv.sec - start_tv.sec;delta_tv.usec = stop_tv.usec - start_tv.usec;Stats::instance ()-&gt;record (i, delta_tv);// . . . and log some output.Logger::instance ()-&gt;log (&quot;Arg %d [%s]\n&quot;, i, argv[i]);Stats::instance()-&gt;report (i);}// Logger and Stats singletons are not cleaned up when main returns.return 0;}</code> </pre>
<p>注意，应用没有清晰的创建或销毁Logger和Stats单例，也就是说它们的生命周期管理是与应用逻辑相分离的。这是一个通常的习惯在应用程序退出的时候不对单例对象进行销毁处理。 <br />但是，由于单例模式仅仅涉及单例对象的创建，而没有应对它的销毁，这将产生几个缺陷。值得注意的是，当上面的应用程序中止时，Logger和Stats单 例对象都没有被清理。在最好的情况下，这将导致内存泄漏的错误报告，最坏的情况下，重要的系统资源可能没有被完全的释放和销毁。 <br />例如，如果Logger和Stats单例对象持有OS的资源，像系统范围的信号量，I/O缓冲，或者其他被分配的OS资源，将产生问题。当程序关闭的时候 没有适当的释放这些资源可能导致死锁和其他同步灾难。为了减少这样的问题，在程序退出前每个单例实例的析构函数应该被调用。 <br />一种试图保证单例销毁的单例模式实现方法是，在一个文件范围内声明一个利用区域锁习惯用法实现的静态单例类实例。例如,下面的Singleton Destroyer模板提供了一个析构器用于销毁单例对象。 </p>
<pre><code>	template <class t=""> Singleton_Destroyer{public:Singleton_Destroyer (void): t_ (0) {}void register (T *) { t_ = t; }Singleton_Destroyer (void) { delete t_; }private:T *t_; // Holds the singleton instance.};</class></code> </pre>
<p>为了使用这个类，所有应该做的是通过定义一个静态的Singleton_Destroyer类实例来修改Logger和Stats类。下面的例子展示了对Logger类的修改： 
<pre><code>static Singleton_Destroyer<logger> logger_destroyer;// Global access point to the// Logger singleton.static Logger *instance (void) {if (instance_ == 0) {instance_ = new Logger;// Register the singleton so it will be// destroyed when the destructor of logger_destroyer is run.logger_destroyer.register (instance_);}return instance_;}</logger></code> </pre>
<p>注意，logger_destroyer是如何获取单例对象并在程序退出时将其销毁的。针对Stats类也有类似的修改。 </p>
<p>不幸的是，清晰的实例化一个静态的Singleton_Destroyer实例将带来一些问题。例如，在C++中，每一个 Singleton_Destroyer应该在不同的编译单元中定义（译者：内联定义。因为静态数据具有内联特性，一般不将其放在头文件中，因为每个包含 这个头文件的编译单元都将拥有一个静态数据的副本。）。在这种情况下，它们的析构函数的调用次序是没有保证的，这将导致未定义的行为。特别的，如果在不同 编译单元中的单例对象共享了一些资源，如SOCKET句柄，共享内存片段和/或系统范围的信号量，程序将不能干净的退出。在C++中单例对象的这种不确定 的销毁次序将很难保证（1）在最后一个使用资源的单例对象被完全的销毁前，而不是（2）在一个单例对象存在，并依然使用这些资源前，OS去回收这些资源。 </p>
<p>总的来说，在上面的例子中没有解决的关键问题是：(1)被单例分配的资源应该在程序退出时被最终释放。(2)不受约束的创建和销毁静态实例的次序将导致严重的程序错误。(3)将软件开发人员和管理对象生命周期的责任分离开来，将减少系统出错的可能。 </p>
<h3>语境</h3>
<p>&gt; 	一个应用和系统对其所创建的对象进行全面的生命周期控制，对于保证系统的正确性是必要的。 </p>
<h3>问题</h3>
<p>许多应用并没有适当的处理对象的整个生命周期。特别是那些使用创建模式的应用，如单例模式，经常没有考虑这些对象的销毁。同样的，那些使用静态对象来提供对象销毁的应用经常遭受不一致的初始化和销毁过程。下面是这些问题的简要说明： </p>
<p>1、单例对象析构带来的问题。单例对象的实例可能被动态创建，一个动态创建的单例实例必将会造成资源的泄漏，如果它们没有被销毁。但是，单例对象造成的泄漏经常被忽略，这是因为： <br />（1）它们对应用不会造成显著的影响， <br />（2）在大多数通用的操作系统中，如UNIX、<a href="http://www.donevii.com/post/tag/windows" class="st_tag internal_tag" rel="tag" title="Posts tagged with windows">WINDOWS</a>，在程序中止时它们会被自动清理。 不幸的是，在下面的环境中资源泄漏会导致麻烦： <br />（a）当系统需要优雅的关闭时。单例对象应该为其所获取的系统资源，如系统范围的锁、打开的网络连接、共享的内存片断负责。清晰的销毁单例对象是值得做的，这可以保证当程序中止时，所有这些资源能够在预定好的点上被销毁。 <br />（b）当一个单例对象拥有另一个单例对象的引用时。清晰的管理单例对象的销毁次序，对于避免在应用中止时由于空悬（dangling）引用产生的问题是必要的。 <br />（c）当检测内存泄漏时。内存检测工具，如NuMega BoundsCheck, ParaSoft Insure++,和Rational Purify，被用于检测像C/C++这类需要清晰的动态分配和释放内存的语言。这些工具会指示单例实例作为一处内存泄漏。 <br />（d）当从一个全局内存池中动态分配内存时。一些实时操作系统，如VxWorks和PSOS，有一个对应于所有应用的唯一的全局堆。因此，应用任务在中止时必须释放任何动态分配的内存，否则，这些内存不会被释放而被其他应用使用直到系统重新启动。 </p>
<p>2、静态对象生命周期的问题。一些对象必须先于任何使用前被创建。在C++语言中，这些实例往往被定义成静态变量，它们先于程序主函数入口点被调用前创建，在程序中止时被销毁。但是静态对象有几个严重的缺陷： <br />（a）不确定的构造/析构的次序。C++语言仅仅指定了在一个编译单元内部静态对象的构造/析构的次序。构造的次序是对象声明的次序、析构则与之相反。但 是没有指定处于不同编译单元的静态对象的构造/析构的次序。因此构造/析构的次序是实现依赖的。使用静态对象来处理初始化相关性是很难写出可以移植的C+ +代码。通常，比较简单的方式是完全避免使用静态对象，而不是解决、防止这些相关性。为了在某些平台上实现正确的程序操作，清晰的单例管理是必须的，因为 它们能够事先销毁单例对象。例如，早先JDK中的垃圾回收器，能够销毁没有任何参考指向的对象，即使这个对象是被设置成单例。虽然这个问题在后续的JDK 中被解决，但是对象生命周期管理者能够在的应用的控制下，通过维护单例的参考来解决这个问题。 <br />（b）不能被嵌入式系统很好的支持。由于历史原因，嵌入系统都是使用C语言因此，它们总是不能对OO程序语言特性提供无缝的支持。例如，在C+ +语言中的静态对象的构造/析构经常会复杂化嵌入系统的编程。嵌入式OS可能已经支持清晰的调用静态的构造/析构函数，但没有达到程序员期望的最佳状态。 一些嵌入式OS不支持一个程序有一个唯一的入口点的概念。例如VxWorks支持多个task，task类似于线程因为它们共享一个地址空间。但是对于每 一个应用并没有指定主task。因此，这些嵌入式系统可以被配置成在模块载入/卸载时分别的调用静态对象的构造器/析构器。 <br />（c）静态对象增加了应用启动的时间。静态对象可能先于任何主入口点的调用，在应用启动时被初始化。如果这些对象在一个特定的运行中没有被使 用，那么应用启动（退出）的时间被白白的增加。减少这种浪费的一种方法是，使用安需创建对象替代静态对象，如使用单例模式。使用单例替代静态对象也可以用 于延迟对象对象的构建直到被首次使用时，而且这也将减少启动时间。一些实时的应用已经发现在进入主入口点后和对象被正是需要前的一个特定时间点创建单例对 象带来的好处。一个或多个这样静态对象的缺点就足以提供将它们从程序中移走的全部动机。通常，比较好的做法是不使用它们，而是应用下面的方案替代。 </p>
<h3>解决之道</h3>
<p>定义一个对象生命周期管理者，它是一个包含了预分配对象和被管理对象集合的单例。它的职责是在应用启动和中止时分别创建和销毁预分配对象。它更深一层的职责是在程序中止时保证所有的被管理对象能够被完全的销毁。 </p>
<h3>适应性</h3>
<p>在下列情况下使用对象生命周期管理者： </p>
<p>1、在程序中止时，单例或其他动态分配的对象能够在不需要程序本身进行干预的情况下被销毁。单例和其他创建模式都没有提及它们所创建的对 象应该什么时候被销毁和由谁来销毁这个问题。与之相反，对象生命周期管理者提供了一个便利的用于删除被动态创建对象的全局对象。由创建模式产生的对象可以 向对象生命周期管理者进行注册，确保在程序中止时能够被销毁。 <br />2、静态对象必须从应用中移除。正像前面所描述的那样，静态对象是麻烦的制造者，特别是在某些语言和某些平台上。对象生命周期管理者提供了一种利用预先分配对象替代静态对象的机制。预分配对象在应用使用它们前被创建，在应用中止前被销毁。 <br />3、对于那些不支持静态对象创建和销毁的平台。一些嵌入式系统，如VXWORKS和PSOS不总是在程序启动时创建静态对象，在程序中止时销毁静态对象。通常，比较好的方式是移除静态对象。 <br />4、虽然应用需要这样，但是底层平台不能提供一个主程序的概念。在缺少对主程序概念支持的平台上，将缺少对静态对象创建和销毁的支持。对象生命周期管理者通过划分地址空间的方式可以被用来仿效一个主程序。从应用的角度来看，每一个对象生命周期管理者描绘了一个程序的范畴。 <br />5、对象被销毁的次序必须由应用来指定。动态创建的对象可以通过向对象生命周期管理者注册方式来使自身被销毁。对象生命周期管理者可以以任意希望的次序来实现对象的销毁。 <br />6、应用需要一个明确的单例对象管理机制。像上面所描述的，单例对象可能被过早的销毁。如早期的JAVA平台。对象生命周期管理者可以延迟对象的销毁直到应用中止。 </p>
<h3>结构和参与者</h3>
<p>在下面的图中，使用UML方式展示了对象生命周期管理者的结构和参与者。 对象生命周期管理者：每一个对象生命周期管理者，它是一个包含了预分配对象和被管理对象集合的单例。 </p>
<p><center> <img src="http://www.huihoo.com/ace_tao/i/lifecycle1.jpg" alt="" /> </center> </p>
<p>1、被管理对象：任何一个向对象生命周期管理者注册并由其负责销毁的对象。对象销毁发生在对象生命周期管理者本身被销毁的时候，通常都是在程序中止的时候。 <br />2、预分配对象：被对象生命周期管理者在其内部通过硬编码方式实现创建和销毁的对象。它和对象生命周期管理者具有相同的生命周期，也就是执行应用的进程的生命周期。 <br />3、应用：应用清晰或非清晰的创建和销毁对象生命周期管理者。此外，应用向本身可能包含预分配对象的对象生命周期管理者注册被管理对象。 </p>
<h3>动态特征</h3>
<p>下图中展示了对象生命周期管理者模式中的参与者之间的动态协作： <br />这个图描述了四个分离的活动： <br /><center> <img src="http://www.huihoo.com/ace_tao/i/lifecycle2.jpg" alt="" /> </center>  </p>
<p>1、对象生命周期管理者创建和初始化，它将依次创建预分配对象。 <br />2、应用创建管理对象，并向对象生命周期管理者注册。 <br />3、应用使用已经注册的管理对象和预先分配对象。 <br />4、对象生命周期管理者的销毁，这将销毁所有它控制的管理对象和预分配对象。 </p>
<h3>实现</h3>
<p>对象生命周期管理者可以用下面展示的步骤来实现。这个实现是基于ACE框架提供的对象管理者来实现的，它将引出一些将在这个单元讨论的有趣问题。下面讨论的一些步骤是语言相关的，因为ACE是用C++语言编写的。 <br />1、定义对象生命周期管理者组件。这个组件向应用提供一个接口，通过它注册那些生命周期必须被管理对象，确保在系统中止时能够完全的销毁这些对 象。此外，这个组件还定义了一个智囊团（repository）用于确保它所管理的对象能够被完全销毁。对于那些被注册，在程序中止时被销毁的预分配对象 和管理对象来说，对象生命周期管理者是一个容器。 <br />下面是用于实现对象生命周期管理者组件的子步骤： <br />（a）定义一个用于注册被管理对象的接口。一种用于向对象生命周期管理者注册被管理对象的方法是使用C语言库中的atexit函数，它在程序退出 时将调用特定的中止函数（用于实现退出时的清理操作）。但是，不是所有的平台都支持atexit函数，而且atexit函数的实现限制了最大被注册的中止 函数数目为32。因此。对象生命周期管理者必须支持下面的两个技术用于实现被管理对象的注册：使用一个容器来容纳所有的被管理对象，在程序退出时自动清除 这些被管理对象。 <br />i.定义一个cleanup函数接口。对象生命周期管理者允许应用向其注册任意类型的对象。当程序中止时，对象生命周期管理者将自动的清理这些对象。下面的C++类展示了一个在ACE中使用的特殊的CLEANUP_FUNC，用于注册能够被清除的对象或数组。 </p>
<pre><code>typedef void (*CLEANUP_FUNC)(void *object,void *param);class Object_Lifetime_Manager{public:static int at_exit (void *object,CLEANUP_FUNC cleanup_hook,void *param);};</code> </pre>
<p>这个静态的at_exit函数注册一个能够在程序退出时被清除的对象或数组对象。Cleanup_hook参数指向全局的函数或时静态的 方法，该方法在清理时被调用用于销毁对象或数组。在销毁时，对象生命周期管理者向Cleanup_hook函数传递对象和相关的参数。参数包含任何被 Cleanup_hook函数需要的额外信息，如数组中对象的个数等。 <br />ii.定义一个cleanup基类接口。这个接口允许应用向对象生命周期管理者注册销毁任何从cleanup接口继承的对象。这个 cleanup基类接口应该有一个虚的析构函数和一个虚的cleanup方法，这个方法的实现仅仅是简单的调用delete this，它将导致所有继承类的析构函数被依次调用。下面的代码段展示了在ACE中这个接口是如何实现的： </p>
<pre><code>class Cleanup{public:// Destructor.virtual ?Cleanup (void);// By default, simply deletes this.virtual void cleanup (void *param = 0);};</code> </pre>
<p>下面的代码段展示了在ACE中，对象生命周期管理者被用于注册从cleanup接口继承对象的接口。
<pre><code>class Object_Lifetime_Manager{public:static int at_exit (Cleanup *object,void *param = 0);};<code> </code></code></pre>
<p>这个静态的at_exit函数注册一个能够在程序退出时被清除的cleanup对象。在析构时，对象生命周期管理者调用cleanup对象中的cleanup方法，param参数包含了任何被cleanup函数需要的额外信息。 <br />（b）定义一个单例适配器。虽然使用上面定义的对象生命周期管理者方法是可以清晰的编码实现单例对象，但这样做是冗余的和易错的。因此，定义一个 单例的适配器模板用于封装创建单例对象和向对象生命周期管理者注册的细节是非常有用的。此外，单例适配器可以使用线程安全的双检测加锁优化模式 （double checked locking optimization pattern）来创建和访问类型指定的单例对象实例。 下面的代码片断展示了单例适配器在ACE中是如何实现的： </p>
<pre><code>template <class type="">class Singleton : public Cleanup{public:// Global access point to the	wrapped singleton.static TYPE *instance (void) {// Details of Double Checked  Locking Optimization omitted . . .if (singleton_ == 0) {singleton_ = new Singleton<type>;// Register with the Object Lifetime// Manager to control destruction.Object_Lifetime_Manager::at_exit (singleton_);}return &amp;singleton_-&gt;instance_;}protected:// Default constructor.Singleton (void);// Contained instance.TYPE instance_;// Instance of the singleton adapter.static Singleton<type> *singleton_;};</type></type></class></code> </pre>
<p>Singleton类模板从cleanup类继承，这样就允许单例实例向对象生命周期管理者注册自己 <br />（d）定义一个用于注册预分配对象的接口。预分配对象总是在应用的主进程启动前被创建。例如，在一些应用中，同步锁必须在任何使用前被创建，用于 防止竞争条件。这样一来，这些对象必须在每个对象生命周期管理者类中通过硬编码的方式实现。将这些对象的创建封装在对象生命周期管理者的初始化阶段，不给 应用代码添加任何的复杂性。 <br />对象生命周期管理者能够预先分配对象或数组。它要么能够静态的在全局数据中实现这些预先分配，要么在堆中动态的实现。一种有效的实现方式是在数 组中存储每个预分配对象。像C++这种特定的语言不支持数组容纳异类对象。因此，用指针替代对象本身来实现数组存储。实际的对象是被对象生命周期管理者在 其初始化的过程中动态创建的，在其析构的时候被销毁。 <br />下面的子步骤用于实现预分配对象： <br />1.	限制暴露。为了最小化头文件的暴露，通过宏或枚举来标识预分配对象。 
<pre><code>enum Preallocated_Object_ID{ACE_FILECACHE_LOCK,ACE_STATIC_OBJECT_LOCK,ACE_LOG_MSG_INSTANCE_LOCK,ACE_DUMP_LOCK,ACE_SIG_HANDLER_LOCK,ACE_SINGLETON_NULL_LOCK,ACE_SINGLETON_RECURSIVE_THREAD_LOCK,ACE_THREAD_EXIT_LOCK,};</code> </pre>
<p>2.	使用cleanup适配器。Cleanup适配器类模板从cleanup基类继承，包装那些没有从cleanup基类继承的类型，使它们能够被对象生命周期管理者管理起来。 </p>
<pre><code>#define PREALLOCATE_OBJECT(TYPE, ID) {\Cleanup_Adapter<type> *obj_p;\obj_p = new Cleanup_Adapter<type>;\preallocated_object[ID] = obj_p;\}#define DELETE_PREALLOCATED_OBJECT(TYPE, ID)\cleanup_destroyer (\static_cast<cleanup_adapter><type> *&gt;\(preallocated_object[ID]), 0);\preallocated_object[ID] = 0;</type></cleanup_adapter></type></type></code> </pre>
<p>3.定义预分配对象的访问接口。应用需要简便的和类型安全的预分配对象的访问接口。因为对象生命周期管理者支持不同类型的预分配对象。提供一个分 离的，包含有一个接受一个ID参数的成员函数的，在函数内部对象被预先分配，返回一个指向对象的正确的类型指针的类模板适配器是必要的。 下面的代码片断展示了在ACE中通过类模板适配器如何实现这个接口： </p>
<pre><code>template <class type="">class Preallocated_Object_Interface{public:static TYPE *get_preallocated_object(Object_Lifetime_Manager::Preallocated_Object_ID id){// Cast the return type of the object// pointer based on the type of the// function template parameter.return&amp;(static_cast<cleanup_adapter><type> *&gt;(Object_Lifetime_Manager::preallocated_object[id]))-&gt;object ();}// . . . other methods omitted.};</type></cleanup_adapter></class></code> </pre>
<p>（e）定义被注册对象的析构次序。对象生命周期管理者可以实现以任意次序销毁被注册对象。例如，可以使用优先级别标注，使销毁的次序按照优先级降 序进行。应该提供一个用于设定和改变对象优先级别的接口函数。我们已经发现，以注册顺序相反的次序销毁对象的策略对于ACE应用来说已经足够了。一个应用 可以通过控制对象的注册次序来指定对象的销毁次序。 <br />（f）定义一个中止函数接口。到目前为止所讨论的生命周期管理功能仅涉及在程序中止时销毁被管理对象。但是对象生命周期管理者能够提供一个更通 常的功能：使用其内部的相同实现机制在程序中止时能够调用一个函数。 例如，为了在程序中止时确保完全清楚打开的win32 SOCKET,WSACleanup函数必须被调用。可以使用一个区域锁习惯用法的变化实现，创建一个特殊的包装门面类，它的构造函数会调用初始化函数， 它的析构函数会调用cleantup函数。于是，应用可以向对象生命周期管理者注册这样类的实现，这样一来在对象生命周期管理者销毁时会销毁内部的被管理 对象，中止API也将会被调用。 <br />但是，这样的设计对大多数应用来说太困难了，而且它还是容易出错的，因为应用必须保证这个类被用作单例对象，因为这些API函数只能被调用一次。 作为替代，这些API中止函数将被作为对象生命周期管理者的中止方法的一部分被调用。 <br />（g）移动通用接口和实现细节到一个基础类中。分解一些内部的细节到Object Lifetime Manager Base类中将使Object Lifetime Manager的实现更加简单，健壮。定义一个Object Lifetime Manager Base类用于支持创建不同类型的Object Lifetime Manager。 </p>
<p>3、确定如何管理对象生命周期管理者自己的生命周期。对象生命周期管理者有责任初始化其他全局对象和静态对象，但是这将引发连带的问题，就是这个单例对象如何初始化和析构它自己？下面是用于初始化对象生命周期管理者实例的几种选择： <br />（a）静态初始化。如果应用对静态对象的创建和销毁没有次序限制，可以创建对象生命周期管理者为静态对象。例如，ACE中的对象生命周期管理者可以被作为静态对象创建。  <br />（b）栈初始化。当一个主程序线程明确的定义了程序的入口点和中止点时，可以在主程序线程的栈上创建对象生命周期管理者，这样可以简化创建和销毁 对象生命周期管理者的编程逻辑。这种用于初始化对象生命周期管理者的方法是假设每个程序中只有一个唯一的主线程。这个线程确定了程序本身，也就是，程序在 运行，当且仅当主线程是活动的。这个方法有一个显著的优点：对象生命周期管理者实例在每一条离开MAIN函数的路径上都将被自动的销毁。 <br />（c）清晰的初始化。这个方法是，在应用程序的控制之下，清晰的创建对象生命周期管理者实例，对象生命周期管理者类中init和fini方法允许应用在任何需要的时候创建和销毁对象生命周期管理者实例。这个选择减轻了在使用DLL带来的复杂度。 <br />（d）动态库初始化。在这个方法中，创建和销毁对象生命周期管理者分别在它的DLL被加载和卸载的时候。许多动态库工具都包含调用如下方法能力：(1)一个出世化方法当DLL被加载时（2）一个中止函数当DLL被卸载时。 </p>
<h3>结论</h3>
<p>使用它带来的好处： 	<br />1、	在程序中止时销毁单例对象和其他被管理对象。对象生命周期管理者模式允许应用&quot;干净的&quot;中止，释放被管理对象占用的内存，连同它们持有的其他资源。 <br />2、	指定销毁的次序。对象被销毁的顺序可以被指定。这个指定的销毁机制可以任意复杂任意简单。 <br />3、	从库和应用中移除了静态对象。可以使用预分配对象替代静态对象。这将防止应用依赖于静态对象的创建和销毁的次序。 <br />使用它带来的缺陷: <br />1、管理者自己的生命周期管理。应用本身必须保证尊重对象生命周期管理者的生命周期，不在其外调用对象生命周期管理者的服务。例如，应用不要先于 对象生命周期管理者完全出世化之前试图去访问预分配对象，同样的，应用不要先于最后访问预分配对象或被管理对象前销毁对象生命周期管理者。最后，如果可以 假定对象生命周期管理者仅被一个线程初始化，那么实现对象生命周期管理者是非常简单的。这将免除在其初始化函数中使用静态锁 <br />2、和共享库一起使用。在支持共享库运行时动态加载和卸载的平台上，应用程序必须小心的对待平台指定的特性对对象生命周期管理者的生命周期的影 响。例如，在windows NT平台上，对象生命周期管理者应该被应用或是包含它的DLL来初始化，这将避免潜在死锁状态，由于OS内部已经串行化装载DLL操作。 一个相关的问题是在DLL创建单例对象，被应用代码中的对象生命周期管理者管理。当DLL先于应用中止前被卸载，那么在应用中止时，对象生命周期管理者就 会试图去使用已经不在应用中的代码来销毁这个单例对象。 </p>
<h3>实现样例代码</h3>
<p>下面的代码仅仅展示了预分配对象的处理过程。（译者：而被管理对象的处理过程被忽略了，这个过程比较复杂，涉及较多的ACE其他的封装类，包括ACE_OS_Exit_Info、ACE_Cleanup_Info_Node等。后面将有比较详细的讨论。） </p>
<pre><code>class Object_Lifetime_Manager_Base{public:virtual int init (void) = 0;// Explicitly initialize. Returns 0 on success,// -1 on failure due to dynamic allocation// failure (in which case errno is set to// ENOMEM), or 1 if it had already been called.virtual int fini (void) = 0;// Explicitly destroy. Returns 0 on success,// -1 on failure because the number of <fini>// calls hasn't reached the number of <init>// calls, or 1 if it had already been called.enum Object_Lifetime_Manager_State {OBJ_MAN_UNINITIALIZED,OBJ_MAN_INITIALIZING,OBJ_MAN_INITIALIZED,OBJ_MAN_SHUTTING_DOWN,OBJ_MAN_SHUT_DOWN};protected:Object_Lifetime_Manager_Base (void) :object_manager_state_ (OBJ_MAN_UNINITIALIZED),dynamically_allocated_ (0),next_ (0) {}virtual ?Object_Lifetime_Manager_Base (void) {// Clear the flag so that fini// doesn't delete again.dynamically_allocated_ = 0;}int starting_up_i (void) {return object_manager_state_ &lt;OBJ_MAN_INITIALIZED;}// Returns 1 before Object_Lifetime_Manager_Base// has been constructed. This flag can be used// to determine if the program is constructing// static objects. If no static object spawns// any threads, the program will be// single-threaded when this flag returns 1.int shutting_down_i (void) {return object_manager_state_ &gt;OBJ_MAN_INITIALIZED;}// Returns 1 after Object_Lifetime_Manager_Base// has been destroyed.Object_Lifetime_Manager_State object_manager_state_;// State of the Object_Lifetime_Manager;u_int dynamically_allocated_;// Flag indicating whether the// Object_Lifetime_Manager instance was// dynamically allocated by the library.// (If it was dynamically allocated by the// application, then the application is// responsible for deleting it.)Object_Lifetime_Manager_Base *next_;// Link to next Object_Lifetime_Manager,// for chaining.};class Object_Lifetime_Manager :public Object_Lifetime_Manager_Base{public:virtual int init (void);virtual int fini (void);static int starting_up (void) {return instance_ ?instance_-&gt;starting_up_i () : 1;}static int shutting_down (void) {return instance_ ?instance_-&gt;shutting_down_i () : 1;}enum Preallocated_Object{# if defined (MT_SAFE) &amp;&amp; (MT_SAFE != 0)OS_MONITOR_LOCK,TSS_CLEANUP_LOCK,# else// Without MT_SAFE, There are no// preallocated objects. Make// sure that the preallocated_array// size is at least one by declaring// this dummy.EMPTY_PREALLOCATED_OBJECT,# endif /* MT_SAFE */// This enum value must be last!PREALLOCATED_OBJECTS};// Unique identifiers for Preallocated Objects.static Object_Lifetime_Manager *instance (void);// Accessor to singleton instance.public:// Application code should not use these// explicitly, so they're hidden here. They're// public so that the Object_Lifetime_Manager// can be onstructed/destructed in main, on// the stack.Object_Lifetime_Manager (void) {// Make sure that no further instances are// created via instance.if (instance_ == 0)instance_ = this;init ();}?Object_Lifetime_Manager (void) {// Don't delete this again in fini.dynamically_allocated_ = 0;fini ();}private:static Object_Lifetime_Manager *instance_;// Singleton instance pointer.static void *preallocated_object[PREALLOCATED_OBJECTS];// Array of Preallocated Objects.};Object_Lifetime_Manager *Object_Lifetime_Manager::instance_ = 0;// Singleton instance pointer.Object_Lifetime_Manager *Object_Lifetime_Manager::instance (void){// This function should be called during// construction of static instances, or// before any other threads have been created// in the process. So, it's not thread safe.if (instance_ == 0) {Object_Lifetime_Manager *instance_pointer =new Object_Lifetime_Manager;// instance_ gets set as a side effect of the// Object_Lifetime_Manager allocation, by// the default constructor. Verify that . . .assert (instance_pointer == instance_);instance_pointer-&gt;dynamically_allocated_ = 1;}return instance_;}intObject_Lifetime_Manager::init (void){if (starting_up_i ()) {// First, indicate that this// Object_Lifetime_Manager instance// is being initialized.object_manager_state_ = OBJ_MAN_INITIALIZING;if (this == instance_) {# if defined (MT_SAFE) &amp;&amp; (MT_SAFE != 0)PREALLOCATE_OBJECT (mutex_t,OS_MONITOR_LOCK)// Mutex initialization omitted.PREALLOCATE_OBJECT (recursive_mutex_t,TSS_CLEANUP_LOCK)// Recursive mutex initialization omitted.# endif /* MT_SAFE */// Open Winsock (no-op on other// platforms).socket_init (/* WINSOCK_VERSION */);// Other startup code omitted.}// Finally, indicate that the// Object_Lifetime_Manager instance// has been initialized.object_manager_state_ = OBJ_MAN_INITIALIZED;return 0;} else {// Had already initialized.return 1;}}intObject_Lifetime_Manager::fini (void){if (shutting_down_i ())// Too late. Or, maybe too early. Either// <fini> has already been called, or// <init> was never called.return object_manager_state_ ==OBJ_MAN_SHUT_DOWN ? 1 : -1;// Indicate that the Object_Lifetime_Manager// instance is being shut down.// This object manager should be the last one// to be shut down.object_manager_state_ = OBJ_MAN_SHUTTING_DOWN;// If another Object_Lifetime_Manager has// registered for termination, do it.if (next_) {next_-&gt;fini ();// Protect against recursive calls.next_ = 0;}// Only clean up Preallocated Objects when// the singleton Instance is being destroyed.if (this == instance_) {// Close down Winsock (no-op on other// platforms).socket_fini ();// Cleanup the dynamically preallocated// objects.# if defined (MT_SAFE) &amp;&amp; (MT_SAFE != 0)// Mutex destroy not shown . . .DELETE_PREALLOCATED_OBJECT (mutex_t,MONITOR_LOCK)// Recursive mutex destroy not shown . . .DELETE_PREALLOCATED_OBJECT (recursive_mutex_t,TSS_CLEANUP_LOCK)# endif /* MT_SAFE */}// Indicate that this Object_Lifetime_Manager// instance has been shut down.object_manager_state_ = OBJ_MAN_SHUT_DOWN;if (dynamically_allocated_)delete this;if (this == instance_)instance_ = 0;return 0;}</init></fini></init></fini></code> </pre>
<p>
<h3>译者补充</h3>
<p>被管理对象的处理类图如下： </p>
<p><center> <img src="http://www.huihoo.com/ace_tao/i/lifecycle3.gif" alt="" /> </center> </p>
<p>每个ACE_Object_Manager包含一个私有的ACE_OS_Exit_Info成员。当应用通过 ACE_Object_Manager提供的at_exit函数注册被管理对象时，这个函数将调用ACE_Object_Manager中的一个私有函数 at_exit_i，这个函数将调用ACE_OS_Exit_Info成员中的at_exit_i函数。在这个函数中进行如下操作： <br />ACE_Cleanup_Info new_info;  <br />new_info.object_ = object; <br />new_info.cleanup_hook_ = cleanup_hook;  <br />new_info.param_ = param; <br />registered_objects_ ＝ registered_objects_-&gt;insert <br />(new_info)；其中registered_objects是指向ACE_Cleanup_Info_Node的指针，是ACE_OS_Exit_Info的私有数据成员。 <br />ACE_Cleanup_Info_Node本身构成一个单向的链表，其包含一个ACE_Cleanup_Info实例和指向下一个ACE_Cleanup_Info_Node节点的指针。 </p>
<p>这样，应用注册的被管理对象的指针、中止函数和需要参数都将以链表的形式存放在ACE_OS_Exit_Info中。当 ACE_Object_Manager退出时，其会调用其内部的fini函数，该函数将调用ACE_OS_Exit_Info中的call_hooks函 数。这个函数按照注册相反的顺序一次遍历每个ACE_Cleanup_Info_Node，并执行其中的 <br />ACE_Cleanup_Info中的相关退出操作： </p>
<pre><code>for (ACE_Cleanup_Info_Node *iter = registered_objects_;       iter  &amp;&amp;  iter-&gt;next_ != 0;       iter = iter-&gt;next_)    {      ACE_Cleanup_Info &amp;info = iter-&gt;cleanup_info_;      // The object is an ACE_Cleanup.      ace_cleanup_destroyer (ACE_reinterpret_cast (ACE_Cleanup *,                                                     info.object_),                               info.param_);     }

</code> </pre>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/229.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>设计模式之Double Checked Locking模式</title>
		<link>http://www.donevii.com/post/228.html</link>
		<comments>http://www.donevii.com/post/228.html#comments</comments>
		<pubDate>Mon, 27 Nov 2006 07:53:38 +0000</pubDate>
		<dc:creator>dengwei</dc:creator>
				<category><![CDATA[c/c++/c#]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[优化]]></category>
		<category><![CDATA[平台]]></category>
		<category><![CDATA[开发]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[类]]></category>

		<guid isPermaLink="false">http://www.donevii.com/?p=228</guid>
		<description><![CDATA[转至 www.Huihoo.org (作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作) 意图 无论什么时候当临界区中的代码仅仅需要加锁一次，同时当其获取锁的时候必须是线程安全的，可以用Do... ]]></description>
			<content:encoded><![CDATA[<p>转至 <a href="http://www.Huihoo.org">www.Huihoo.org</a></p>
<p></p>
<p> (作者:Douglas <a href="http://www.donevii.com/post/tag/c" class="st_tag internal_tag" rel="tag" title="Posts tagged with c">C</a>. Schmidt ,<font color="green"><em>by </em>huihoo.org</font> CORBA课题 Thzhang 译 , Allen整理,制作) </p>
<h3>意图</h3>
<p>无论什么时候当临界区中的代码仅仅需要加锁一次，同时当其获取锁的时候必须是线程安全的，可以用Double Checked Locking 模式来减少竞争和加锁载荷。 </p>
<h3>动机</h3>
<p>1、标准的单例。开发正确的有效的并发应用是困难的。程序员必须学习新的技术（并发控制和防止死锁的算法）和机制（如多线程和同步API）。此外，许多熟 悉的设计模式（如单例和迭代子）在包含不使用任何并发上下文假设的顺序程序中可以工作的很好。为了说明这点，考虑一个标准的单例模式在多线程环境下的实 现。单例模式保证一个类仅有一个实例同时提供了全局唯一的访问这个实例的入口点。在c++程序中动态分配单例对象是通用的方式，这是因为c++程序没有很 好的定义静态全局对象的初始化次序，因此是不可移植的。而且，动态分配避免了单例对象在永远没有被使用情况下的初始化开销。 </p>
<pre><code><a href="http://www.donevii.com/post/tag/class" class="st_tag internal_tag" rel="tag" title="Posts tagged with class">class</a> Singleton{public:static Singleton *instance (void){if (instance_ == 0)// Critical section.instance_ = new Singleton;return instance_;}void method (void);// Other methods and members omitted.private:static Singleton *instance_;};

</code> </pre>
<p>应用代码在使用单例对象提供的操作前，通过调用静态的instance方法来获取单例对象的引用，如下所示： <br />Singleton::instance ()-&gt;method (); <br />2、问题：竞争条件。不幸的是，上面展示的标准单例模式的实现在抢先多任务和真正并行环境下无法正常工作。例如，如果在并行主机上运行的多个线程在单例对 象初始化之前同时调用Singleton::instance方法，Singleton的构造函数将被调用多次，这是因为多个线程将在上面展示的临界区中 执行new singleton操作。临界区是一个必须遵守下列定式的指令序列：当一个线程/进程在临界区中运行时，没有其他任何线程/进程会同时在临界区中运行。在 这个例子中，单例的初始化过程是一个临界区，违反临界区的原则，在最好的情况下将导致内存泄漏，最坏的情况下，如果初始化过程不是幂等的 （idempotent.），将导致严重的后果。 </p>
<p>3、	通常的陷阱和弊端。实现临界区的通常方法是在类中增加一个静态的Mutex对象。这个Mutex保证单例的分配和初始化是原子操作，如下：  </p>
<pre><code>class Singleton{public:static Singleton *instance (void){// Constructor of guard acquires lock_ automatically.Guard<mutex> guard (lock_);// Only one thread in the critical section at a time.if (instance_ == 0)instance_ = new Singleton;return instance_;// Destructor of guard releases lock_ automatically.}private:static Mutex lock_;static Singleton *instance_;};

</mutex></code> </pre>
<p>guard类使用了一个c++的习惯用法，当这个类的对象实例被创建时，它使用构造函数来自动获取一个资源，当类对象离开一个区域时，使用析构器 来自动释放这个资源。通过使用guard，每一个对Singleton::instance方法的访问将自动的获取和释放lock_。 <br />即使这个临界区只是被使用了一次，但是每个对instance方法的调用都必须获取和释放lock_。虽然现在这个实现是线程安全的，但过多的加锁负载是不能被接受的。一个明显（虽然不正确）的优化方法是将guard放在针对instance进行条件检测的内部： </p>
<pre><code>static Singleton *instance (void){if (instance_ == 0) {Guard<mutex> guard (lock_);// Only come here if instance_ hasn't been initialized yet.instance_ = new Singleton;}return instance_;}

</mutex></code> </pre>
<p>这将减少加锁负载，但是不能提供线程安全的初始化。在多线程的应用中，仍然存在竞争条件，将导致多次初始化instance_。例如，考虑两 个线程同时检测 instance_ == 0，都将会成功，一个将通过guard获取lock_另一个将被阻塞。当第一线程初始化Singleton后释放lock_，被阻塞的线程将获取 lock_，错误的再次初始化Singleton。 <br />4、解决之道，Double Checked Locking优化。解决这个问题更好的方法是使用Double Checked Locking。它是一种用于清除不必要加锁过程的优化模式。具有讽刺意味的是，它的实现几乎和前面的方法一样。通过在另一个条件检测中包装对new的调 用来避免不必要的加锁： </p>
<pre><code>class Singleton{public:static Singleton *instance (void){// First checkif (instance_ == 0){// Ensure serialization (guard constructor acquires lock_).Guard<mutex> guard (lock_);// Double check.if (instance_ == 0)instance_ = new Singleton;}return instance_;// guard destructor releases lock_.}private:static Mutex lock_;static Singleton *instance_;};

</mutex></code> </pre>
<p>第一个获取lock_的线程将构建Singleton，并将指针分配给instance_，后续调用instance方法的线程将发现 instance_ != 0，于是将跳过初始化过程。如果多个线程试图并发初始化Singleton，第二个检测件阻止竞争条件的发生。在上面的代码中，这些线程将在lock_上 排队，当排队的线程最终获取lock_时，他们将发现instance_ != 0于是将跳过初始化过程。 </p>
<p>上面Singleton::instance的实现仅仅在Singleton首次被初始化时，如果有多个线程同时进入instance方法将导致加锁负 载。在后续对Singleton::instance的调用因为instance_ != 0而不会有加锁和解锁的负载。 通过增加一个mutex和一个二次条件检测，标准的单例实现可以是线程安全的，同时不会产生过多的初始化加锁负载。 </p>
<h3>适应性</h3>
<p>&gt; 当一个应用具有下列特征时，可以使用Double Checked Locking优化模式： <br />1、应用包含一个或多个需要顺序执行的临界区代码。 <br />2、多个线程可能潜在的试图并发执行临界区。 <br />3、临界区仅仅需要被执行一次。 <br />4、在每一个对临界区的访问进行加锁操作将导致过多加锁负载。 <br />5、在一个锁的范围内增加一个轻量的，可靠的条件检测是可行的。 </p>
<h3>结构和参与者</h3>
<p>通过使用伪代码能够最好地展示Double Checked Locking模式的结构和参与者，图1展示了在Double Checked Locking模式有下列参与者： <br /><center> <img src="http://www.huihoo.com/ace_tao/i/dcl1.jpg" alt="" /> </center>  </p>
<p>1、仅有一次临界区（Just Once Critical Section,）。临界区所包含的代码仅仅被执行一次。例如，单例对象仅仅被初始化一次。这样，执行对new Singleton的调用（只有一次）相对于Singleton::instance方法的访问将非常稀少。 <br />2、mutex。锁被用来序列化对临界区中代码的访问。 <br />3、标记。标记被用来指示临界区的代码是否已经被执行过。在上面的例子中单例指针instance_被用来作为标记。 <br />4、	应用线程。试图执行临界区代码的线程。 </p>
<h3>协作</h3>
<p>图2展示了Double Checked Locking模式的参与者之间的互动。作为一种普通的优化用例，应用线程首先检测flag是否已经被设置。如果没有被设置，mutex将被获取。在持有 这个锁之后，应用线程将再次检测flag是否被设置，实现Just Once Critical Section，设定flag为真。最后应用线程释放锁。 </p>
<p><center> <img src="http://www.huihoo.com/ace_tao/i/dcl2.jpg" alt="" /> </center> </p>
<h3>结论</h3>
<p>使用Double Checked Locking模式带来的几点好处： <br />1、最小化加锁。通过实现两个flag检测，Double Checked  Locking模式实现通常用例的优化。一旦flag被设置，第一个检测将保证后续的访问不要加锁操作。 <br />2、防止竞争条件。对flag的第二个检测将保证临界区中的事件仅实现一次。 <br />使用Double Checked Locking模式也将带来一个缺点：产生微妙的移植bug的潜能。这个微妙的移植问题能够导致致命的bug，如果使用Double Checked Locking模式的软件被移植到没有原子性的指针和正数赋值语义的硬件平台上。例如，如果一个instance_指针被用来作为Singleton实现 的flag，instance_指针中的所有位（bit）必须在一次操作中完成读和写。如果将new的结果写入内存不是一个原子操作，其他的线程可能会试 图读取一个不健全的指针，这将导致非法的内存访问。 <br />在一些允许内存地址跨越对齐边界的系统上这种现象是可能的，因此每次访问需要从内存中取两次。在这种情况下，系统可能使用分离的字对齐合成flag，来表示instance_指针。 <br />如果一个过于激进（aggressive）编译器通过某种缓冲手段来优化flag，或是移除了第二个flag==0检测，将带来另外的相关问题。后面会介绍如何使用volatile关键字来解决这个问题。 </p>
<h3>实现和例子代码</h3>
<p>ACE在多个库组件中使用Double Checked Locking模式。例如，为了减少代码的重复，ACE使用了一个可重用的适配器ACE Singleton来将普通的类转换成具有单例行为的类。下面的代码展示了如何用Double Checked Locking模式来实现ACE Singleton。 
<pre><code>// A Singleton Adapter: uses the Adapter// pattern to turn ordinary classes into// Singletons optimized with the// Double-Checked Locking pattern.template <class lock="" class="" type="">class ACE_Singleton{public:static TYPE *instance (void);protected:static TYPE *instance_;static LOCK lock_;};template <class lock="" class="" type=""> TYPE *ACE_Singleton</class></class></code> </pre>
<p>ACE Singleton类被TYPE和LOCK来参数化。因此一个给定TYEP的类将被转换成使用LOCK类型的互斥量的具有单例行为的类。 <br />ACE中的Token Manager.是使用ACE Singleton的一个例子。Token Manager实现在多线程应用中对本地和远端的token（一种递归锁）死锁检测。为了减少资源的使用，Token Manager被按需创建。为了创建一个单例的Token Manager对象，只是需要实现下面的typedef： </p>
<p>typedef ACE_Singleton
<pre><code>// Acquire the mutex.int Mutex_Token::acquire (void){

// If the token is already held, we must block.if (mutex_in_use ()) {// Use the Token_Mgr Singleton to check// for a deadlock situation *before* blocking.if (Token_Mgr::instance ()-&gt;testdeadlock ()){	errno = EDEADLK;	return -1;}else{	// Sleep waiting for the lock...	// Acquire lock...}</code>}

} </pre>
<h3>变化</h3>
<p>一种变化的Double Checked Locking模式实现可能是需要的，如果一个编译器通过某种缓冲方式优化了flag。在这种情况下，缓冲的粘着性（coherency）将变成问题，如 果拷贝flag到多个线程的寄存器中，会产生不一致现象。如果一个线程更改flag的值将不能反映在其他线程的对应拷贝中。 </p>
<p>另一个相关的问题是编译器移除了第二个flag==0检测，因为它对于持有高度优化特性的编译器来说是多余的。例如，下面的代码在激进的编译器下将被跳过对flag的读取，而是假定instance_还是为0，因为它没有被声明为volatile的。
<pre><code>Singleton *Singleton::instance (void){if (Singleton::instance_ == 0){// Only lock if instance_ isn't 0.Guard<mutex> guard (lock_);// Dead code elimination may remove the next line.// Perform the Double-Check.if (Singleton::instance_ == 0)// ...

</mutex></code> </pre>
<p>解决这两个问题的一个方法是生命flag为Singleton的volatile成员变量，如下： <br />private:  <br />static volatile long Flag_; // Flag is volatile. <br />使用volatile将保证编译器不会将flag缓冲到编译器，同时也不会优化掉第二次读操作。使用volatile关键字的言下之意是所有对flag的访问是通过内存，而不是通过寄存器。 </p>
<h3>相关模式</h3>
<p>Double Checked Locking模式是First-Time-In习惯用法的一个变化。First-Time-In习惯用法经常使用在类似c这种缺少构造器的程序语言中，下面的代码展示了这个模式： 	
<pre><code>static const int STACK_SIZE = 1000;static T *stack_;static int top_;void push (T *item){// First-time-in flagif (stack_ == 0) {stack_ = malloc (STACK_SIZE * sizeof *stack);assert (stack_ != 0);top_ = 0;}stack_[top_++] = item;// ...}

	第一次push被调用时，stack_是0，这将导致触发malloc来初始化它自己。</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.donevii.com/post/228.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

