0

管理者,请你远离产品开发/设计部门[转]


先说一个身边真正发生过的故事,可能大家会对故事情节感觉非常熟悉。

一家小型电子商务公司,管理者就是公司的创始人,他有些技术开发背景。因为产品面向海外市场,他也比较关注产品设计与可用性。由于他对自己的技术、设计能力的自信,总是对开发部与设计部成员的能力持怀疑态度,经常插手他们的工作。

一次,开发人员就新产品需求、设计文档的要求,提出开发方案A。这位管理者看后,以其经验判断认为不理想,并给出方案B。开发人员觉得方案B有些问题,但一时又没有找到有说服力的例证,无奈只好以此方案进行开发。

果然,在开发过程中,由于管理者仓促间所提出方案,在设计上确实存在不合理的疏漏,导致项目出现了一些问题。当开发人员想方设法将问题一一解决后,突然发现,这个DEBUG后的开发方案B,已经变得与他们最初提出的方案A并无二致。

而此时,该项目已经严重超期,那位管理者还为此非常不满,认为是开发人员能力的问题导致的。此后,管理者对开发工作的介入变得更多,而开发人员也开始变
化,一部分对领导的这种超权行为越来越不满意,矛盾变得更加激化,后面工作中的沟通变得更加困难;另一部分则变得越来越没有积极性,领导要求怎么做就怎么
做。

刚好看到UCDChina.com中的一篇文章《管理者不应直接参与产品的开发与设计》,感觉说得太有道理了。上面这个故事,可以用来充实文章的论据。

其实团队管理者关注团队产品的成败,是非常正常的心态。他们插手设计、开发工作,无非是希望把产品更得更好,动机是无庸质疑的。尽管如此,还是不建议管理
者过多的介入具体的开发与设计工作,即使是领域专家型的管理者,过多的插手具体设计、开发工作,也得不到预期的结果,可能对于项目来说,往往事与愿违,由
此产生的恶性干扰多于良性的指导,这是得不偿失的。

为什么会这样?原因很多:

1、因为管理者与团队成员的权重不同,有管理者参与的项目,往往不能非常公平地对待其它项目涉众。尽管,管理者本意是以其对项目的理解和自身的经验,为设
计、开发团队提供一些指导和建议,至少也会增加一种思路。但往往事与愿违,管理者介入后,一般会导致所有项目涉众的思维模式,会被管理者所左右,就因为他
是团队的管理者。

大家想想,是否经常遇到这样的情况,设计、开发人员深思熟虑后形成的方案,经常因为管理者的一点儿质疑,而被否决;而管理者的灵光一闪,都可能形成一个决
策,如果其它团队成员想到推翻它,必须经过周密、详实的举证与论述,这本就不公平,对项目也绝对没有好处,这会影响到团队成员的思路,甚至导致他们逐渐推
动思考的动力。

2、因为技术背景与对项目的关注点不同,管理者与设计、开发人员会处在不同层面,看问题角度肯定是不同的。其实,能从多个不同角度看待产品,这本来是好事,可以把问题考虑得更全面一些。但如果以管理者的角度,去干扰设计、开发中的问题,那就是另外一码儿事了。

《论语•泰伯》:“子曰:不在其位,不谋其政。”

这句话说得非常好,这不是推卸责任意思,而恰恰是出于更负责的角度考虑。为了便于理解,这里把“位”理解为“职位”。

每个职位有不同的职责,职责会影响人的立场,不同立场、知识背景的人理解、处理同一个问题,其结果肯定是不一样的。也许管理者认为自己可以做到“换位思考
”,即便如此也是搁靴搔痒的状态,其结果绝对差强人意。比如,管理者明白不能让UI设计师去考虑代码优化的问题,那又为什么自己去犯同样的错误呢?

正所谓术业有专攻,专业的工作还是让专业的人去做,那才可能达到令人满意的结果。

3、有人说,在IT行业,许多管理者都是优秀程序员或策划、设计师出身,这种领域专家型的管理者插手做具体设计、开发工作,应该有对项目、对团队成员有帮助。

其实不然,前面提到的两点问题,对于这样的管理者也依然存在。而且,文章开头提到的那位管理者也曾经是开发人员,本身也是位不错的产品设计师。为什么团队成员在他的指导下,项目进展依然不顺利呢?

从事过产品设计、程序开发工作的朋友应该很清楚,这样的工作需要投入大量的时间与精力,这是一个反复尝试与迭代的过程。好的设计与优秀的编码,绝对不是通
过纸上谈兵或灵光一闪就可以得到的(当然,这个世界是有天才存在的,也许他们拍拍脑袋就可以把产品设计得非常完美,程序编码也可以没有任何BUG,但毕竟
这种天才少得没有说服力)。有的时候,即使写好伪代码,也不能百之百的确保业务逻辑没问题。

而团队的管理者,往往事务缠身,他们没有精力(也不应该)深入于其中任何一项具体工作,那样就会犯下过于关注细节的管理大忌。而浅尝辄止的心态,恰恰又是做好设计与开发工作的大忌。这样看来,管理者兼顾设计、开发工作,并取得好的结果,根本是一项不可能完成的任务。

当然,领域专家型的管理者是可遇而不可求的,他丰富的经验与思路都是对团队成员有帮助的,但仅此而已,具体的设计、开发工作还是应该让设计师和程序员来做。

基于上面分析,如果管理者仍执意要插手设计与开发工作,就必须保证自己的观点(或决策)是100%准确与全面的,这样才不会对项目有负面影响。

当然,这样的要求是不合理的,也没有人敢做此承诺,所以,管理者们还去做好本职工作吧,那里才是发挥你们特长的地方。好的管理者,要懂得放权,要对团队成员应该有足够的信任,相信他们的工作能力,也相信他们的工作态度。正所谓:用人不疑,疑人不用。

如果管理者实在想参与设计、开发工作,那就干脆转行吧。

转自:http://.sina.com.cn/s/_564cabe30100bxmb.

0

加快Flex应用启动速度的5种方式


Jun Heider在O’Reilly的InsideRIA站点上发表了一篇精彩的文章,该文章就如何加快Flex应用的启动速度提出了很多建议,以帮助用户减少看见讨厌的“Loading”对话框的出现时间。他深入探讨了问题的不同方面,并对每种技术的优势和劣势进行了评判。

从外部加载媒体(Media)
Heider提到了一个常用的Flex最佳实践——限制嵌入到应用/SWF文件中的媒体的数量,如图像、影片及mp3等资源都可以从外部的SWF文件加载。 Flex框架可以直接将图片、mp3及字体等资源编译到SWF中。当你想让最终用户获得全部资源时,这种方式确实能派上用场,但是这会导致你的应用长时间停留在“Loading”阶段。中国最大的RIA分享社区-与中国闪客一起成长和发展!

在嵌入式字体中限制字符集
Heider建议在嵌入式字体中限制字符集以降低SWF文件的总下载时间: 当你在Flex中嵌入一种字体时,你就会获得该字体的全部字符的支持。尽管这可能是你想要的,但你确信你需要全部字符么?例如,在一个只面向英文的应用中,你确信你真的想花时间下载中文字符数据么?
缓存框架

Heider回顾了Flex 3 support for runtime-shared-libraries (RSL)这篇文章:从Flex 3开始,你可以将Adobe签名的框架——RSLs缓存到Flash Player的cache中。这有两个好处。首先,缓存在Flash Player cache中的签名的框架RSLs可由所有配置好的Flex应用共享。换句话说,如果某人的应用已经下载了500k的签名的框架RSL,并且该RSL仍旧 在Flash Player cache中,那么你的应用就可以使用缓存下来的RSL。其次,即使某人清空了其浏览器缓存,对Flash Player cache也没有任何影响。

考虑模块化
Heider谈到了将Flex应用划分成模块的好处:减少字体加载时间的另一种方式就是将你的Flex应用划分成模块。使用模块的一个好处在于当加载和卸载模块时你能完全操控它。
之所以要划分成模块的最后一个原因是他们更快,而且我能即时加载它们。换句话说,在启动时唯一需要加载的模块就是 Step1.swf模块。因此,在使用模块的情况下,最终用户节省了启动时间,但是当他从一个模块切换到另一个模块时却需要花更多时间,因为每个模块都需 要以JIT形式加载。在我的应用中,只有当用户首次在steps 1-5之间切换时需要花更多时间。

推迟实例化
Heider围绕着Flex组件的“creationPolicy”属性及何时实例化应用的不同部分给出了很多建议。如果你想减少从数据下载到用户真正可以使用的总时间,当务之急就是推迟实例化。这项技术背后的理念就是直到应用真正使用的时候才在内存中创建对象。尽管推迟实例化技术会在应用的整个使用过程中导致少许——通常不那么明显——的延迟,但与长时间的启动延迟相比,它还是可接受的。推迟实例化的另一个好处在于内存使用的优化。 Heider还谈到了一个“实验性”的条款——“使用流”,这是他在讨论Dirk Eismann的帖子(Building monolithic Flex SWFs that still startup quickly.”)时谈及的。Eismann提出一项技术以利用Flash Player中的多个frames以在部分应用中达到流的目的。查看所有的帖子以更多地了解该技术及关于加快Flex启动速度的建议。中国最大的RIA分享社区-与中国闪客一起成长和发展!)

原文出处:http://www.infoq.com/cn/news/2008/05/flex-startup-time

0

google背后的分布式架构


  Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用 (比如那个大堂里的地球模型),更在于其有别常规的IT策略……

  加利福尼亚州山景城(Mountain View)Google公司(Google,下称Google)总部有一个43号大楼,该建筑的中央大屏幕上显示着一个与Google地球(Google Earth)相仿的世界地图,一个转动的地球上不停地闪动着五颜六色的光点,恍如罗马宫廷的千万烛灯,每一次闪动标志着地球的这个角落一名Google用 户发起了一次新的搜索。

  这同时意味着Google又一次满足了人们对未知信息的好奇与渴望。

  Google是与众不同的。它的独特不仅仅表现于革新的思维和充满创意的应用 (比如那个大堂里的地球模型),更在于其有别常规的IT策略。从人们的常理来看,简单的硬件商品和免费软件是无法构建出一个帝国的,但是Google做到 了。在性能调整后,Google把它们变成一个无可比拟的分布式计算平台,该平台能够支持大规模的搜索和不断涌现的新兴应用。我们原本认为这些应用都是个 人消费级别的,但是Google改变了这一切。现在商业世界也在使用它们,这就令这家搜索公司显得那么与众不同。

  GoogleWeb 服务背后的IT架构对无数使用搜索引擎的用户来说也许并不是非常重要,但它是Google几百位致力于把全球信息组织起来,实现“随处可达,随时可用”目 标的工程师们的最核心工作。这就需要一个在覆盖范围和野心上都与Google的商业愿景完全相符的IT蓝图作为支撑。

  Google 的经理们一直对公司的IT策略话题保持沉默,他们厌恶谈及特定的厂商或者产品,当被问到他们的服务器和数据中心时,他们总是闭口不谈。但与几位 Google的IT领导一起呆了一天后,我们最终得以揭示该公司的IT是如何运作的,那可不仅仅是一个运行在无数服务器集群上的、表面看来非常简单的搜索 引擎。在其简单的外表下,蕴涵着许多内部研发软件、定制硬件、人工智能,以及对性能的执着追求和打破常规的人力管理模式。

  IT理念方面,Google对同行有一条建议:尽量避免那些人人都在使用的系统和软件,以自己的方式做事会更有独特的竞争优势。

  “企业文化决定了你的做事方式。”道格拉斯”美林(Douglas Merrill),这位Google工程副总裁和事实上的首席信息官(CIO) 指出,“到了我们这样的发展阶段,企业观念和文化非常与众不同,这也反过来鞭策我们必须要采用与众不同的方式来运行那些他人看来很常规的系统。”
  Google 最大的IT优势在于它能建造出既富于性价比(并非廉价)又能承受极高负载的高性能系统。因此IT顾问史蒂芬”阿诺德(Stephen Arnold)指出,Google与竞争对手,如亚马逊网站(Amazon)、电子港湾公司(eBay)、微软公司(Microsoft,下称微软)和雅 虎公司 (Yahoo,下称雅虎)等公司相比,具有更大的成本优势。Google程序员的效率比其他Web公司同行们高出50%~100%,原因是Google已 经开发出了一整套专用于支持大规模并行系统编程的定制软件库。据他估算,其他竞争公司可能要花上四倍的时间才能获得同等的效果。

  打造服务器

  Google 究竟是怎样做到这点的呢?其中一个手段,美林认为,“是因为我们自己动手打造硬件。”Google并不制造计算机系统,但它根据自己的参数定制硬件,然后 像MTV的节目“靓车打造”(Pimp My Ride)那样自己安装和调整硬件系统。开源程序经理克里斯”迪博纳(Chris DiBona)评论道:“我们很善于购买商业服务器,并且改造他们为我们所用,最后把性能压榨和发挥到极致,以致有时候他们热得像要融化了似的。”

  这种亲手打造的方式,来源于Google从车库诞生时与生俱来的节俭风格,更与Google那超大型的系统规模息息相关,良好的习惯一直延续至 今。据说 Google在65个数据中心拥有20万~45万台服务器—这个数目会有偏差(取决于你如何定义服务器和由谁来做这项统计)。但是,不变的是持续上升的趋势。

  Google不会去讨论这些资产,因为它认为保密也是一种竞争优势。事实上,Google之所以喜欢开源软件也是因为它的私密性。“如果我们购 买了软件许可或代码许可,人们只要对号入座,就可以猜出Google的IT基础架构。”迪博纳分析说, “使用开源软件,就使我们多了一条把握自己命运的途径。”

  Google喜欢规模化的服务器运行方式。当有成百上千台机器时,定制服务器的优势也会成倍增加,效果也会更趋明显。Google正在俄勒冈州 哥伦比亚河边的达勒斯市建造一个占地30亩的数据中心,在那儿它可以获得运算和降温需要的低价水力电力能源(参见边栏《Google数据中心自有一套》)。

  Google以“单元”(Cell)的形式组织这些运行 Linux操作系统的服务器,迪博纳把这种形式比喻成互联网服务的“磁盘驱动器”(但别和一直谣传的Google存储服务Gdrive混淆了,“并没有 Gdrive这回事。”一位Google女发言人明确表示。),公司的软件程序都驻扎在这些并不昂贵的电脑机箱里,由程序员决定它们的冗余工作量。这种由 很多单元组成的文件系统代替了商业存储设备;迪博纳表示Google这些单元设备更易于建造和维护,他还暗示他们能处理更大规模的数据。

  Google 不会漏过对任何技术细节的关注。多年来,公司的工程师就在研究微处理器的内部工作机制,随着Google规模的持续壮大,必然会用到特别定制和调节过的芯 片。知名工程师路易斯”巴罗索(Luiz Barroso)去年在一篇发表在工业杂志上的论文中证实,近年来Google的主要负荷都由单核设计的系统承担着。但许多服务器端的应用,如 Google搜索索引服务,所需的并行计算在单核芯片的指令级别上执行得并不好。

  曾在数据设备公司(Digital Equipment)和康柏公司(Compaq)当过芯片设计师的巴罗索认为,随着AMD公司、英特尔公司(Intel)、太阳计算机系统公司(Sun)开始制造多核芯片,必将会出现越来越多芯片级别的并行计算。

  Google 也曾考虑过自己制造计算机芯片,但从业界潮流来看,这个冒险的举动似乎不是很必要。“微处理器的设计非常复杂而且成本昂贵,”运营高级副总裁乌尔斯”霍尔 茨勒(Urs Holzle)表示。Google宁愿与芯片制造商合作,让他们去理解自己的应用并设计适合的芯片。这是一种客户建议式的设计,其关注点在于总体吞吐量、 效能,以及耗电比,而不是看单线程的峰值性能。霍尔茨勒表示,“这也是最近多核CPU的设计潮流与未来方向。”
裁缝般地定制软件

  为了能尽量压榨硬件性能,Google开发了相当数量的定制软件。创新产品主要包括用于简化处理和创建大规模数据集的编程模型 MapReduce;用于存储和管理大规模数据的系统BigTable;分析分布式运算环境中大规模数据集的解释编程语言Sawzall;用于数据密集型 应用的分布式文件系统的 “Google文件系统”(Google File System);还有为处理分布式系统队列分组和任务调度的“Google工作队列”(Google Workqueue)。

  正是从Sawzall这些工具里体现出Google对计算效率的执著关注。并不是每家公司都能从底层去解决效率问题,但是对Google来说, 为常规关系型数据库无法容纳的大规模数据集专门设计一种编程语言是完全合理的。即使其他编程工具可以解决问题,Google的工程师们仍然会为了追求效率 而另外开发一套定制方案。Google工程师认为,Sawzall能与C++中的MapReduce相媲美,而且它更容易编写一些。

  Google 对效率的关注使它不可能对标准Linux内核感到满意;Google会根据自己的需要运行修改过的内核版本。通过调整Linux的底层性能,Google 工程师们在提高了整体系统可靠性的基础上,还一并解决了数据损坏和数据瓶颈等一系列棘手问题。对内核的修改也使Google的计算机集群系统因为通信效率 的提高而运行得更快。

  当然,Google偶尔也会出现系统故障,情况一旦发生,无数的用户就会受到影响了。三年前一次持续30分钟的系统故障使20%的搜索流量受到影响。

  Google 开发了自己的网站服务器却没有使用开源的Apache服务器,尽管它在网站服务器的市场占有率超过60%。迪博纳认为,Google的网站服务器可以运行 在更多数量的主机上,对Google站点上内容庞大又彼此互相依赖的应用程序来说,这种服务器的负载均衡能力远比Apache的能力更高。同时,在用标准 公共网关接口(CGI)访问数据库动态网页方面,Google服务器的编程难度要比 Apache更高,但是最终运行速度却更快。“如果我们能够压榨出10%~20%的性能,我们就可以节省出更多系统资源、电量和人力了。”迪博纳在总结中指出。

  Google还设计了自己的客户关系管理(CRM)系统用于支持自己基于竞价和点击的互联网广告收费业务。但对是否需要设计自己的工具,Google的态度也不是一成不变的。比如在财会软件上,它就使用了甲骨文公司(Oracle)的Financials软件。

  美林拿着一只叉子举例说明现成的产品也可以带来价值。但在有些场合现成的软件产品就不一定适用了。“我们的文化在各个层面对我们的运作都有深远影响,”他表示,“所以我们不想让购买所得的工具改变我们的工作方式和文化层面。”
Google’s BigTable 原理 (翻译)

题记:google 的成功除了一个个出色的创意外,还因为有 Jeff Dean 这样的软件架构天才。
—— 编者

官方的 Google Reader 中有对BigTable 的解释。这是Google 内部开发的一个用来处理大数据量的系统。这种系统适合处理半结构化的数据比如 RSS 数据源。 以下发言 是 Andrew Hitchcock 在 2005 年10月18号 基于: Google 的工程师 Jeff Dean 在华盛顿大学的一次谈话 (Creative Commons License).

首先,BigTable 从 2004 年初就开始研发了,到现在为止已经用了将近8个月。(2005年2月)目前大概有100个左右的服务使用BigTable,比如: Print,Search History,Maps和 Orkut。根据Google的一贯做法,内部开发的BigTable是为跑在廉价的PC机上设计的。BigTable 让Google在提供新服务时的运行成本降低,最大限度地利用了计算能力。

BigTable 是建立在 GFS ,Scheduler ,Lock Service 和 MapReduce 之上的。

每个Table都是一个多维的稀疏图 sparse map。Table 由行和列组成,并且每个存储单元 cell 都有一个时间戳。在不同的时间对同一个存储单元cell有多份拷贝,这样就可以记录数据的变动情况。在他的例子中,行是URLs ,列可以定义一个名字,比如:contents。Contents 字段就可以存储文件的数据。或者列名是:”language”,可以存储一个“EN”的语言代码字符串。

为了管理巨大的Table,把Table根据行分割,这些分割后的数据统称为:Tablets。每 个Tablets大概有 100-200 MB,每个机器存储100个左右的 Tablets。底层的架构是:GFS。由于GFS是一种分布式的文件系统,采用Tablets的机制后,可以获得很好的负载均衡。比如:可以把经常响应 的表移动到其他空闲机器上,然后快速重建。

Tablets在系统中的存储方式是不可修改的 immutable 的SSTables,一台机器一个日志文件。当系统的内存满后,系统会压缩一些Tablets。由于Jeff在论述这点的时候说的很快,所以我没有时间把听到的都记录下来,因此下面是一个大概的说明:

压缩分为:主要和次要的两部分。次要的压缩仅仅包括几个Tablets,而主要的压缩时关于整个系统的压缩。主压缩有回收硬盘空间的功能。Tablets的位置实际上是存储在几个特殊的BigTable的存储单元cell中。看起来这是一个三层的系统。
客户端有一个指向METAO的Tablets的指针。如果METAO的Tablets被频繁使用,那个这台机器就会放弃其他的tablets专门支持 METAO这个Tablets。METAO tablets 保持着所有的META1的tablets的记录。这些tablets中包含着查找tablets的实际位置。(老实说翻译到这里,我也不太明白。)在这个系统中不存在大的瓶颈,因为被频繁调用的数据已经被提前获得并进行了缓存。

现在我们返回到对列的说明:列是类似下面的形式: family:optional_qualifier。在他的例子中,行:www.search-analysis.com 也许有列:”contents:其中包含html页面的代码。 “ anchor:cnn.com/news” 中包含着 相对应的url,”anchor:www.search-analysis.com/” 包含着链接的文字部分。列中包含着类型信息。
(翻译到这里我要插一句,以前我看过一个关于万能数据库的文章,当时很激动,就联系了作者,现在回想起来,或许google的 bigtable 才是更好的方案,切不说分布式的特性,就是这种建华的表结构就很有用处。)

注意这里说的是列信息,而不是列类型。列的信息是如下信息,一般是:属性/规则。 比如:保存n份数据的拷贝或者保存数据n天长等等。当 tablets 重新建立的时候,就运用上面的规则,剔出不符合条件的记录。由于设计上的原因,列本身的创建是很容易的,但是跟列相关的功能确实非常复杂的,比如上文提到 的 类型和规则信息等。为了优化读取速度,列的功能被分割然后以组的方式存储在所建索引的机器上。这些被分割后的组作用于 列 ,然后被分割成不同的 SSTables。这种方式可以提高系统的性能,因为小的,频繁读取的列可以被单独存储,和那些大的不经常访问的列隔离开来。

在一台机器上的所有的 tablets 共享一个log,在一个包含1亿的tablets的集群中,这将会导致非常多的文件被打开和写操作。新的log块经常被创建,一般是64M大小,这个GFS的块大小相等。当一个机器down掉后,控制机器就会重新发布他的log块到其他机器上继续进行处理。这台机器重建tablets然后询问控制机器处理结构的存储位置,然后直接对重建后的数据进行处理。这个系统中有很多冗余数据,因此在系统中大量使用了压缩技术。

Dean 对压缩的部分说的很快,我没有完全记下来,所以我还是说个大概吧:压缩前先寻找相似的 \行,列,和时间数据。

他们使用不同版本的: BMDiff 和 Zippy

BMDiff 提供给他们非常快的写速度: 100MB/s – 1000MB/s 。Zippy 是和 LZW 类似的。Zippy 并不像 LZW 或者 gzip 那样压缩比高,但是他处理速度非常快。

Dean 还给了一个关于压缩 蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面,行按照以下的方式命名:“com.cnn.www/index.:http”.在未压缩前的web page 页面大小是:45.1 TB ,压缩后的大小是:4.2 TB , 只是原来的 9.2%。Links 数据压缩到原来的 13.9% , 链接文本数据压缩到原来的 12.7%。
Continue Reading

0

[转] C语言的setjmp: 异常处理与构建协作式多任务系统


转至 http://www.upsdn.net/html/2004-11/47.html

在C标准库中有一对非常有趣的函数setjmp()函数与longjmp()函数,用来实现代替goto实现一些非常重要的功能,如异常处理。C语言中,标准库函数setjmp和longjmp形成了结构化异常工具的基础。简单的说即setjmp实例化异常处理程序,而longjmp产生异常。

先介绍setjmp
int setjmp(jmp_buf envbuf)
宏函数setjmp()在缓冲区envbuf中保存系统堆栈里的内容,供longjmp()以后使用,setjmp()必须使用头文件setjmp.h。
调用setjmp()宏时,返回值为0,然而longjmp()把一个变原传递给setjmp(),该值(恒不为0)就是调用longjmp()后出现的setjmp()的值。
setjmp函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用longjmp函数来恢复先前被保存的程序堆栈环境。当setjmp和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”(”non-local ”)的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块之中;或者程序中不采用正常的返回(return)语句,或函数的正常调用等方法,而使程序能被恢复到先前的一个调用例程(也即函数)中。
对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境, 并且当前的程序控制流,会因此而返回到先前调用setjmp时的程序执行点。此时,在接下来的控制流的例程中,所能访问的所有的变量(除寄存器类型的变量 以外),包含了longjmp函数调用时,所拥有的变量。
void (jmp_buf envbuf,int status);
     函数longjmp()使程序在最近一次调用setjmp()出重新执行。setjmp()和longjmp()提供了一种在函数间调转的手段,必须使用头部文件setjmp.h。
     函数longjmp()通过把堆栈复位成envbuf中描述的状态进行操作,envbuf的设置是由预先调用setjmp()生成的。这样使程序的执行在 setjmp()调用后的下一个语句从新开始,使计算机认为从未离开调用setjmp()的函数。从效果上看,longjmp()函数似乎“绕”过了时间 和空间(内存)回到程序的原点,不必执行正常的函数返回过程。
     缓冲区envbuf具有<setjmp.h>中定义的buf_jmp类型,它必须调用longjmp()前通过调用setjmp()来设置好。
    值status变成setjmp()的返回值,由此确定长调转的来处。不允许的唯一值是0,0是程序直接调用函数setjmp()时由该函数返回的,不是间接通过执行函数longjmp()返回的。
     longjmp()函数最常用于在一个错误发生时,从一组深层嵌套的实用程序中返回。
longjmp函数用于恢复先前程序中调用的setjmp函数时所保存的堆栈环境。setjmp和longjmp组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”(”non-local goto”)的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块,或者不采用正常的返回(return)语句,或函数的正常调用等方法,使程序能被恢复到先前的一个调用例程(也即函数)中。

   对setjmp函数的调用时,会保存程序当前的堆栈环境到env参数中;接下来调用longjmp时,会根据这个曾经保存的变量来恢复先前的环境,并且 因此当前的程序控制流,会返回到先前调用setjmp时的执行点。此时,value参数值会被setjmp函数所返回,程序继续得以执行。并且,在接下来 的控制流的例程中,它所能够访问到的所有的变量(除寄存器类型的变量以外),包含了longjmp函数调用时,所拥有的变量;而寄存器类型的变量将不可预 料。setjmp函数返回的值必须是非零值,如果longjmp传送的value参数值为0,那么实际上被setjmp返回的值是1。

  在调用setjmp的函数返回之前,调用longjmp,否则结果不可预料。

在使用longjmp时,请遵守以下规则或限制:
  · 不要假象寄存器类型的变量将总会保持不变。在调用longjmp之后,通过setjmp所返回的控制流中,例程中寄存器类型的变量将不会被恢复。
  · 不要使用longjmp函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用 _fpreset函数,来首先初始化浮点数包后,它是可以通过longjmp来实现从中断处理例程中返回。
如何实现异常处理
首先设置一个跳转点(setjmp() 函数可以实现这一功能),然后在其后的代码中任意地方调用 longjmp() 跳转回这个跳转点上,以此来实现当发生异常时,转到处理异常的程序上,在其后的介绍中将介绍如何实现。 setjmp() 为跳转返回保存现场并为异常提供处理程序,longjmp() 则进行跳转(抛出异常),setjmp() 与 longjmp() 可以在函数间进行跳转,这就像一个全局的 goto 语句,可以跨函数跳转。

jmp_buf 异常结构

        使用 setjmp() 及 longjmp() 函数前,需要先认识一下 jmp_buf 异常结构。jmp_buf 将使用在 setjmp() 函数中,用于保存当前程序现场(保存当前需要用到的寄存器的值),jmp_buf 结构在 setjmp.h 文件内声明:

        typedef struct
        {
                unsigned j_sp;  // 堆栈指针寄存器
                unsigned j_ss;  // 堆栈段
                unsigned j_flag;  // 标志寄存器
                unsigned j_cs;  // 代码段
                unsigned j_ip;  // 指令指针寄存器
                unsigned j_bp; // 基址指针
                unsigned j_di;  // 目的指针
                unsigned j_es; // 附加段
                unsigned j_si;  // 源变址
                unsigned j_ds; // 数据段
        } jmp_buf;

        jmp_buf 结构存放了程序当前寄存器的值,以确保使用 longjmp() 后可以跳回到该执行点上继续执行。

setjmp() 与 longjmp() 函数都使用了 jmp_buf 结构作为形参,它们的调用关系是这样的:
        首先调用 setjmp() 函数来初始化 jmp_buf 结构变量 jmpb,将当前CPU中的大部分影响到程序执行的积存器存入 jmpb,为 longjmp() 函数提供跳转,setjmp() 函数是一个有趣的函数,它能返回两次,它应该是所有库函数中唯一一个能返回两次的函数,第一次是初始化时,返回零,第二次遇到 longjmp() 函数调用后,longjmp() 函数使 setjmp() 函数发生第二次返回,返回值由 longjmp() 的第二个参数给出(整型,这时不应该再返回零)。
        在使用 setjmp() 初始化 jmpb 后,可以其后的程序中任意地方使用 longjmp() 函数跳转会 setjmp() 函数的位置,longjmp() 的第一个参数便是 setjmp() 初始化的 jmpb,若想跳转回刚才设置的 setjmp() 处,则 longjmp() 函数的第一个参数是 setjmp() 所初始化的 jmpb 这个异常,这也说明一件事,即 jmpb 这个异常,一般需要定义为全局变量,否则,若是局部变量,当跨函数调用时就几乎无法使用(除非每次遇到函数调用都将 jmpb 以参数传递,然而明显地,是不值得这样做的);longjmp() 函数的第二个参数是传给 setjmp() 的第二次返回值,这在介绍 setjmp() 函数时已经介绍过。
        下面是 setjmp() 函数与 longjmp() 函数的一个示意图:

通过示意图可以看出 setjmp() 函数与 longjmp() 函数的关系,如何跳转。

呵呵!现在是否对程序的执行流程一目了然,其中最关键的就是setjjmp和longjmp函数的调用处理。我们分别来分析之。
  当程序运行到第②步时,调用setjmp函数,这个函数会保存程序当前运行的一些状态信息,主要是一些系统寄存器的值,如ss,cs,eip, eax,ebx,ecx,edx,eflags等寄存器,其中尤其重要的是eip的值,因为它相当于保存了一个程序运行的执行点。这些信息被保存到 mark变量中,这是一个C标准库中所定义的特殊结构体类型的变量。
  调用setjmp函数保存程序状态之后,该函数返回0值,于是接下来程序执行到第③步和第④步中。在第④步中语句执行时,如果变量n2为0值,于是便 引发了一个浮点数计算异常,,导致控制流转入fphandler函数中,也即进入到第⑤步。
  然后运行到第⑥步,调用longjmp函数,这个函数内部会从先前的setjmp所保存的程序状态,也即mark变量中,来恢复到以前的系统寄存器的 值。于是便进入到了第⑦步,注意,这非常有点意思,实际上,通过longjmp函数的调用后,程序控制流(尤其是eip的值)再次戏剧性地进入到了 setjmp函数的处理内部中,但是这一次setjmp返回的值是longjmp函数调用时,所传入的第2个参数,也即-1,因此程序接下来进入到了第⑧ 步的执行之中。
因为 jmp_buf 全局只能存放一个 jmpb 结构,使得只有最后一组宏可以响应异常;    这是无法嵌套异常的原因,要实现多重嵌套可以建立一个全局堆栈来维护一组 jmpb 结构
实现机理
setjmp()保存其调用返回点的ebx, esi, edi, ebp, esp, eip,对于sigsetjmp(), 还保存当前的信号屏蔽字, longjmp恢复这些寄存器及信号屏蔽字,同时传递一个返回值。
ongjmp只是使CPU的状态和setjmp时的状态一致,相同的CPU初始状态执行相同的代码并不意味着产生相同的结果,setjmp, longjmp并不关心内存变量的变化,而只关心CPU的状态,
内存变量的确定性应该根据上下文来灵活处理.
如果将函数之间的调用关系看成一种树型结构, 将可执行文件的入口函数看成它的最内层节点(根节点), 那么setjmp提供了标记节点的方法, longjmp提供了从外层节点直接返回到更内层节点的方法,
在同一层节点之间或者从内层节点到外层节点的longjmp是没有意义的, 因为目标节点在此时根本不存在.
摘要:讨论一个利用标准C语言setjmp库函烽实现查询式协作多任务系统,给出完整的内核和样例程序并对源代码进行说明。该系统具有简单易用的特点,只需要编写存取堆栈指针的宏就可方便地移植到新的平台上。文章详述了系统的优缺点,讨论一些性能扩展的方法。该内核适用
于中小规模的嵌入式软件。

关键词:协作式多任务 C语言 setjmp

引言

本文介绍的是利用标准C语言setjmp库函数实现的具备此特点的协作式多任务系统。从本 质上讲,实时多任务操作系统应该具备按照优先级抢占调度的内核。然而,在实际应用中,抢中式的多任务某种程序上带来了用户程序设计时数据保护的困难,并 且,具备抢占功能的多任务内核设计时困难也比较多,这会增加操作系统自身的代码,也使它在小资源单片机系统中应用较少;而协作多任务系统的调度只在用户指 定的时机发生,这会大大简化内核和用户系统的设计,尤其本文实现的系统通过条件查询来放弃CPU,既符合传统单片机程序设计的思维,又带来了多任务、模块 化、可重入的编程便利。

Setjmp是标准C语言库函数的组成部分,它可以实现程序执行中的远程转操作。具体来 说,它可以在一个函数中使用setjmp来初始化一个全局标号,然后只要该函数未曾返回,那么在其它任何地方都可以通过longjmp调用来跳转到 setjmp的下一条语句执行。实际上,setjmp函数将发生调用处的局部环境保存在一个jmp_buf的结构当中,只要主调函数中对应的内存未曾释放 (函数返回时局部内存就失效了),那么在调用longjmp的时候就可以根据已保存的jmp_buf参数恢复到setjmp的地方执行。我们的系统中就是 分析了setjmp标准库函数的特点,以简单的方式实现了协作式多任务。

1 演示程序

为了便于理解,首先给出多任务演示程序的源代码。这个程序演示了协作式多任务切换、任务的 动态生成、多任务共用代码等功能,一共使用了init_coos初始化根任务(也就是C语言main函数)、creat_task创建新任务和 WAITFOR查询条件这3个基本的系统调用。由于面向嵌入式系统,因而程序不会中止并且运行中也没有进行任何输出,需要借助适合的调试工具来理解多任务 系统的运行。

example.c文件清单:

#include<stdlib.h>

#include“co-os.h”

void tskfunc1(int argc,void *argv);

void tskfunc2(int argc,void *argv);

void subfunc(void);

volatile int cnt,test;

int main(void){

int i;

init_coos(400);

creat_tsk(tskfunc1,12,NULL,400);

creat_tsk(tskfunc2,0,NULL,400);

i=0;

while(1){

WAITFOR(cnt= =8);

while(i++<cnt)test=i;

cnt++;

}

}

void tskfunc1(int argc,void *argv){

int i;

static int creat=0;

if(!creat){

creat_tsk(tskfunc1,9,NULL,400);

creat=1;

}

i=0;

while(1){

WAITFOR(cnt>argc);

test=0×55;

/*使用函数调用在子程序中测试WAITFOR*/

subfunc();

while(i++<cnt)test=i^0xAA;

}

}

void tskfunc2(int argc,void *argv){

while(1){

WAITFOR(++cnt>15);

cnt=0;

}

}

void subfunc(void){

int i;

WAITFOR(cnt<5);

for(i=0;i<++)test=0×10*i;

}

2 内核构成

内核包括一个供外部用户程序包含的头文件(co-os.h)和具体实现的源文件(co-os.),它们提供了演示程序中用到的3个系统调用。

内核的实现代码假定了CPU堆栈是向下增长的,并且通过宏来直接操作堆栈指针。以下代码在 Microsoft VC6 for x86、Borland C++ Builder 5.5、SDS CrossCode7.0 for 68K和GCC3.2 for AVR四种平台中测试过,只需在co-os.h头文件中定义相应的平台类型即可顺利编译。

(1)co-os.h文件清单

#include<setjmp.h>

/*选择X86_VC6,X86_BC5,AVR_GCC或M68H_SDS.*/

#define X86_VC6

#define MAX_TSK 10

typedef struct {

void (*entry)(int argc,void *argv);

jmp_buf env;

int argc;

void *argv;

}TVB;

extern TCB tcb[MAX_TSK];

extern int task_num,tskid;

void init_coos(int mainstk);

int creat_tsk(void(*entry)(int argc,void *argv),int argc,void *argv,int stksize);

#define WAITFOR(condition)do{

setjmp(tcb[tskid].env);

if(!(condition)){

tskid++;

if(tskid>=task_num)tskid=0;

longijmp(tcb[tskid].env,1);

}

}while(0)

(2)co-os.c文件清单

#include “co-os.h”

#if defined(X86_VC6)||defined(X86_BC5)

#define SAVE_SP(p) _asm mov p,esp

#define RESTORE_SP(p) _asm mov esp,p

#elif defined(AVR_GCC)

#include<io.h>

#define SAVE_SP(p) p=(int*)SP

#define RESTORE_SP(p) SP=(int)p

#elif defined(M68K_SDS)

#define SAVE_SP(p) asm(“MOVE.L A7,{“#p”}”)

#define RESTORE_SP(p) asm(“MOVE.L {“#p”},A7″)

#endif

TCB tcb[MAX_TSK];

Int task_num=1;

Int tskid;

Static int stktop,oldsp;

Void init_coos(int mainstk){

SAVE_SP(stktop);

stktop=stktop+sizeof(void(*)(void))/sizeof(int)

-(mainstk+sizeof(int)-1)/sizeof(int);

}

int creat_tsk(void(*entry)(int argc,void *argv),

int argc,void *argv,int stksize){

if(task_num>=MAX_TSK)terurn-1;

SAVE_SP(oldsp);

RESTORE_SP(stktop);

If(!setjmp(tcb[task_num].env)){

RESTORE_SP(oldsp);

tcb[task_num].entry=entry;

tcb[task_num].argc=argc;

tcb[task_num].argv=argv;

task_num++;

stktop-=(stksize+sizeof(int)-1)/sizeof(int);

}

else tcb[tskid].entry(tcb[tskid].argc,tcb[tskid].argv);

return 0;

}

3 代码说明

任务代码通过执行setjmp设置本任务下次查询时的返回点,然后在等待条件放弃掉CPU 跳转到下一任务的返回点处执行。如此周而复始,让各任务都获得轮转运行的机会,也要求各任务都需要主动通过等待条件的方式放弃掉CPU。系统中除了中断服 务程序之外,所有任务都是平等的,都应该遵循同样的规则和其它任务一起协作运行。基本系统中没有设计杀死任务的调用,这要求各任务都应当设计成某种形式的 无限循环。

任务中等待的条件可以是任务复杂的表达式或都函数调用,也可以是中断服务程序设置的全局变 量(注意加volatile定义)。一般在任务执行时会让下次等待的条件不再满足,避免某个任务一直霸占CPU将系统饿死。在嵌入式软件中还经常会遇到任 务定时启动和超时等待在I/O操作上,在我们的系统中可以维护一个时间计数器,只需在适当的地方记录时刻,然后在任务查询条件中判断当前计数器和记录时刻 之间的差值就可以了。

内核实现的代码则相当简法。由于主要的保护和恢复任务现场的工作都由C语言标准库 setjmp实现了,我们就只需要操纵一下堆栈指针防止不同的任务使用了重叠的局部环境,这个工作在初始化和创建任务的时候通过预定义的两个宏来实现。在 init_coos函数中,记录了主任务(main函数)保留mainstk字节堆栈后的新栈顶位置(stktop),然后在每次creat_task时 都根据要求为每个任务保留stksize字节的堆栈并重新计算下一个stktop。在creat_task函数中利用了setjmp函数的返回值(直接返 回时为0,longjmp跳转返回时非0),使得一方面creat_task能正常回到调用者,又让下次轮转到新任务时能够找到创建时的入口。

co-os.h中定义的WAITFOR使用了一个do{…while(0)实现多语句宏的 C语言小技巧,这样能保证在任何情况下WAITFOR都可以如单条语句一样在源程序中使用,需要担心多了或者少了大括弧破坏if/else匹配之类的问 题,并且,所有的编译器都会优化掉这个假循环。

为了尽量使程序简单并说明问题,以上代码中没有考虑中断相关的数据保护问题。实际运行的系 统中,如果首先写堆栈指针不能一步完成(如AVR这样的8位机),那么,在写操作正在进行的时候绝对不能允许中断;另外,在任务中查询的条件如果和中断有 关,那么也必须考虑数据的完整性。

4 性能分析

所有的协作式多任务系统中任务切换时间都和用户代码(是否长期占用CPU)、就绪时刻有 关,比标准系统略差的是,我们的简单系统中不支持任务优先级,并且完成一轮查询调度的时间还和任务数目有关。这也是为了达到简单和可移植性目标而不得已作 出的牺牲。在各任务间切换查询条件通过C语言标准库函数longjmp实现,一般来讲longjmp函数要从内存中恢复大部分CPU寄存器,执行它也需要 若干条指令的时间。

为了面向嵌入式系统应用,任务控制块(TCB)采用静态数组来实现,这样要求预先确定系统 的最大任务数(co-os.h中的MAX_TSK)。如果需要,也可以通过环波链表来动态管理任务控制块(TCB),这时可以简单实现任务的动态创建和删 除,并且通过指针来访问TCB也要比通过下标(tskid)略快一点。

在某些情况下,如果在中断返回后需要执行某关键任务,可以考虑通过设置“高级中断”的方法 来实现。具体地讲,就是在中断返回前改变返回地址到某函数入口(“高级中断服务程序”),同时保留原返回地址到堆栈中,这样在“高级中断服务程序”完成后 执行return就又回到了正常的多任务查询流程。使用“高级中断”时要注意现场保护的衔接,并且这种技巧显然和CPU和体系结构有关。

5 结论

setjmp是标准C语言中用于远程跳转的库函数,利用它可方便实现一个简单易移植的协作式多任务系统。该系统功能完备、编程简单、易于学习,适合一些中小规模的嵌入式软件使用;并且,以此为基础,还可以用一些与平台相关的编程技巧提高其实时性和灵活性。

部分资料来源于希赛,单片机与嵌入式系统应用

0

[转] MTK平台芯片概说


转至 http://blog.sina.com.cn/mobliephone

目前联发科技已开发出MT6205、MT6217、MT6218、MT6219、MT6226、MT6227、MT6228等系列平台,其中MT6205、MT6217、MT6218、MT6219、MT6226、MT6227、MT6228、MT6229、MT6225、MT6223、MT6230均为基带芯片,所有芯片均采用ARM7的核。
MT6305为电源管理芯片,有MT6305、MT6305N、MT6305BN;

MT6129、MT6139是射频芯片。MT6129为早期的射频RF芯片,一般与MT6205的CPU一起使用。现在用的多的是MT6129、MT6129N、MT6129D,其中MT6129、MT6129N一般用在MT6217、MT6218、MT6219的CPU的机器上,MT6129D一般用在MT6226、MT6227的CPU的机器上。RF3146(7×7mm)、RF3146D(双频)、RF3166(6×6mm)为RFMD的PA。

MT6205为最早的方案,只有GSM的基本功能,不支持GPRS、WAP、MP3等功能。(2003年MP);
MT6218为在MT6205基础上增加GPRS、WAP、MP3功能。MT6217为MT6218的cost down方案,与MT6128 PIN TO PIN,只是软件不同而已,另外MT6217支持16bit数据。(2004年MP)
MT6219为MT6218上增加内置AIT的1.3M camera处理IC,增加MP4功能。8bit数据。(2005年MP)
MT6226为MT6219 cost 升级产品,内置0.3M 摄相处理IC,支持GPRS、WAP、MP3、MP4等,内部配置比MT6219优化及改善,比如配蓝牙是可用很便宜的芯片CSR的BC03模块USD3即可支持数据传输(如听立体声MP3等)功能。
MT6226M为MT6226高配置设计,内置的是1.3M摄像处理IC。(2006年MP)
MT6227与MT6226功能基本一样,PIN TO PIN,只是内置的是2.0M 摄像处理IC。(2006年MP)
MT6228比MT6227增加TV OUT功能,内置3.0M 摄像处理IC,支持支持GPRS、WAP、MP3、MP4。(2006年MP)
从MT6226后软件均可支持网络摄像头功能,也就是说你的机子可以用于QQ视频。

MT6229平台支持EDGE功能,其他功能和6228基本一致。

MT6225是6217的代替产品,可以接cam但是没有isp,也就是没有特效,变焦,但是其主频很高和6228/6229一样达到了104mhz,可以接wifi,并且给设计公司提出了更高的要求——如何利用104m的资源去实现mp4的编解码,如何用104m的资源跑更多的应用,这些都是设计公司做的,对设计公司的要求也非常得高。

MT6223是6205的替代,支持语音,短信,MP3,不支持T_F卡,USB盘,没有集成ISP,PMIC内签。

目前市面上出的双卡双待手机,一般是采取的方案分为以下几种:MT6226+6205,MT6225+6205和MT6225+6223

0

做网站的一些定律


1.250定律

拉德认为:每一位顾客身后,大体有250名亲朋好友。如果您赢得了一位顾客的好感,就意味着赢得了250个人的好感;反之,如果你得罪了一名顾客,也就意味着得罪了250 名顾客。   在你的网站访客中,一个访客可能可以带来一群访客,任何网站都有起步和发展的过程,这个过程中此定律尤其重要。

2.达维多定律

达维多认为,一个企业要想在市场上总是占据主导地位,那么就要做到第一个开发出新产品,又第一个淘汰自己的老产品。   国内网站跟风太严重,比如前段时间的格子网,乞讨网,博客网,一个成功了,大家一拥而上。但实际效果是,第一个出名的往往最成功,所以在网站的定位上,要动自己的脑筋,不是去捡人家剩下的客户。同理,买人家出售的数据来建站效果是很糟糕的。

3.木桶定律

水桶定律是指,一只水桶能装多少水,完全取决于它最短的那块木板。这就是说任何一个组织都可能面临的一个共同问题,即构成组织的各个部分往往决定了整个组织的水平。   注意审视自己的网站,是速度最糟糕?美工最糟糕?宣传最糟糕?你首先要做的,不是改进你最强的,而应该是你最薄弱的。

4.马太效应

《新约》中有这样一个故事,一个国王远行前,交给三个仆人每人一锭银子,吩咐他们:“你们去做生意,等我回来时,再来见我。”国王回来时,第一个仆人说: “主人,你交给我们的一锭银子,我已赚了10锭。”于是国王奖励他10座城邑。第二个仆人报告说:“主人,你给我的一锭银子,我已赚了5锭。” 于是国王例奖励了他5座城邑。第三个仆人报告说:“主人,你给我的一锭银子,我一直包在手巾里存着,我怕丢失,一直没有拿出来。”于是国王命令将第三个仆人的一锭银子也赏给第一个仆人,并且说:“凡是少的,就连他所有的也要夺过来。凡是多的,还要给他,叫他多多益善。”这就是马太效应。   在同类网站中,马太效应是很明显的。一个出名的社区,比一个新建的社区,更容易吸引到新客户。启示是,如果你无法把网站做大,那么你要做专。作专之后再做大就更容易。

5.手表定理

手表定理是指一个人有一只表时,可以知道现在是几点钟,而当他同时拥有两只表时却无法确定。
一个网站,你只需要关注你特定的用户群需求。不要在意不相干人的看法。

6.不值得定律

不值得定律:不值得做的事情,就不值得做好   不要过度seo,如果你不是想只做垃圾站。不要把时间浪费在美化再美化页面,优化再优化程序,在你网站能盈利后,这些事情可以交给技术人员完成。

7.彼得原理

劳伦斯.彼得认为:在各种组织中,由于习惯于对在某个等级上称职的人员进行晋升提拔,因而雇员总是趋向于晋升到其不称职的地位。
不要轻易改变自己网站的定位。如博客网想变门户,盛大想做娱乐,大家拭目以待吧。

8.零和游戏原理

当你看到两位对弈者时,你就可以说他们正在玩“零和游戏”。因为在大多数情况下, 总会有一个赢,一个输,如果我们把获胜计算为得1分,而输棋为-1分,那么,这两人得分之和就是:1+(-1)=0   不要把目光一直盯在你的竞争网站上,不要花太多时间抢它的访客。我们把这些时间用来寻找互补的合作网站,挖掘新访客。

9.华盛顿合作规律

华盛顿合作规律说的是: 一个人敷衍了事,两个人互相推诿, 三个人则永无成事之日。
如果你看准一个方向,你自己干,缺人手就招。不要轻易找同伴一起搞网站,否则你会发现,日子似乎越过越快了,事情越做越慢了。

10.邦尼人力定律

一个人一分钟可以挖一个洞,六十个人一秒种却挖不了一个洞。合作是一个问题,如何合作也是一个问题。你需要有计划。

11.牛蛙效应

把一只牛蛙放在开水锅里,牛蛙会很快跳出来;但当你把它放在冷水里,它不会跳出来,然后慢慢加热,起初牛蛙出于懒惰,不会有什么动作,当水温高到它无法忍受的时候,想出来,但已经没有了力气。   如果你是soho,注意关注你的财务。不要等到没钱了再想怎么挣,你会发现那时候挣钱更难。

12.蘑菇管理

蘑菇管理是许多组织对待初出茅庐者的一种管理方法,初学者被置于阴暗的角落(不受重视的部门,或打杂跑腿的工作),浇上一头大粪(无端的批评、指责、代人受过),任其自生自灭(得不到必要的指导和提携)。
  做网站毕竟要遭遇这样的阶段,搜索引擎不理你,友情链接找不到,访客不上门。这是磨练。

13.奥卡姆剃刀定律

如无必要,勿增实体。
把网站做得简单,再简单,简单到非常实用,而不是花俏。google的首页为什么比雅虎好?

14.巴莱多定律(Paredo 也叫二八定律)

你所完成的工作里80%的成果,来自于你20%的付出;而80%的付出,只换来20%的成果。
随时衡量你所做的工作,哪些是最有效果的。

1.马蝇效应

林肯少年时和他的兄弟在肯塔基老家的一个农场里犁玉米地,林肯吆马,他兄弟扶犁,而那匹马很懒,慢慢腾腾,走走停停。可是有一段时间马走得飞快。 林肯感到奇怪,到了地头,他发现有一只很大的马蝇叮在马身上,他就把马蝇打落了。看到马蝇被打落了,他兄弟就抱怨说:”哎呀,你为什么要打掉它,正是那家伙使马跑起来的嘛!”   在你心满意足的时候,去寻找你的马蝇。没有firefox,不会有ie7,firefox就是微软的马蝇之一。马蝇不可怕,怕的是会一口吃掉你的东西,像 ie当初对网景干的那样。

2.最高气温效应

每天最热总是下午2 时左右,我们总认为这个时候太阳最厉害,其实这时的太阳早已偏西,不再是供给最大热量的时候了。此时气温之所以最高,不过是源于此前的热量积累。
你今天的网站流量,是你一个星期或更长时间前所做的事带来的。

3.超限效应(溢出效应)

刺激过多、过强和作用时间过久而引起心理极不耐烦或反抗的心理现象,称之为“超限效应”。 别到别人论坛里发太多广告。别在自己网站上放太多广告。别在自己的论坛里太多地太明显地诱导话题。

4.懒蚂蚁效应

生物学家研究发现,成群的蚂蚁中,大部分蚂蚁很勤劳,寻找、搬运食物争先恐后,少数蚂蚁却东张西望不干活。当食物来源断绝或蚁窝被破坏时,那些勤快的蚂蚁一筹莫展。“懒蚂蚁”则“挺身而出”,带领众伙伴向它早已侦察到的新的食物源转移。   不要把注意力仅仅放在一个网站上,即使这个网站现在为你带来一切。你要给自己一些时间寻找新的可行的方向,以备万一。

5.长尾理论

ChrisAnderson认为,只要存储和流通的渠道足够大,需求不旺或销量不佳的产品共同占据的市场份额就可以和那些数量不多的热卖品所占据的市场份额相匹敌甚至更大。   对于搜索引擎,未必你需要一个热门词排在第一位,如果有一千个冷门词排在第一位,效果不但一样,还会更稳定更长远。

6.破窗理论

栋建筑上的一块玻璃,又没有及时修好,别人就可能受到某些暗示性的纵容,去打碎更多的玻璃。   管理论坛时,如果你发现第一个垃圾贴,赶紧删掉他吧。想想:落伍现在为什么那么多××贴?现在控制比最初控制难多了。

7.“羊群效应”,又称复制原则(Copy Strategy)

一个羊群(集体)是一个很散乱的组织,平时大家在一起盲目地左冲右撞。如果一头羊发现了一片肥沃的绿草地,并在那里吃到了新鲜的青草,后来的羊群就会一哄而上,争抢那里的青草,全然不顾旁边虎视眈眈的狼,或者看不到其它地方还有更好的青草。
不要轻易跟风,保持自己思考的能力。

8.墨菲定律

如果坏事情有可能发生,不管这种可能性多么小,它总会发生,并引起最大可能的损失。
除非垃圾站,否则不要作弊,对搜索引擎不要,对广告也不要。

9.光环效应

人们对人的某种品质或特点有清晰的知觉,印象比较深刻、突出, 这种强烈的知觉, 就像月晕形式的光环一样,向周围弥漫、扩散,掩盖了对这个人的其他品质或特点的认识。
不要轻易崇拜一个人或者公司、一个概念、一种做法。

10.蝴蝶效应

一只亚马逊河流域热带雨林中的蝴蝶,偶尔扇动几下翅膀,两周后,可能在美国德克萨斯州引起一场龙卷风。
不管你做什么,网站或者其他,你都应该关注新闻。机遇或者灾难可能就在那。

11.阿尔巴德定理

一个企业经营成功与否,全靠对顾客的要求了解到什么程度。   我赞同别人的点评:看到了别人的需要,你就成功了一半;满足了别人的需求,你就成功了全部。尤其是做网站。

12.史密斯原则

如果你不能战胜他们,你就加入到他们之中去。  
不要试图做孤胆英雄。如果潮流挡不住,至少,你要去思考为什么。

0

PHP5 效率优化


静态调用的成员一定要定义成 static  (5 ONLY)

PHP 5 引入了静态成员的概念,作用和 PHP 4 的函数内部静态变量一致,但前者是作为类的成员来使用。静态变量和 的类变量( variable)差不多,所有类的实例共享同一个静态变量。

<?php
class foo
{
    function
bar
() {
        echo
‘foobar’
;
    }
}

$foo = new foo;

// instance way

$foo->bar();

// static way

foo::bar();
?>

静态地调用非 static 成员,效率会比静态地调用 static 成员慢 50-60%。主要是因为前者会产生 E_STRICT 警告,内部也需要做转换。

使用类常量 (PHP5 ONLY)

PHP 5 新功能,类似于 ++ 的 const。

使用类常量的好处是:

- 编译时解析,没有额外开销
- 杂凑表更小,所以内部查找更快
- 类常量仅存在于特定「命名空间」,所以杂凑名更短
- 代码更干净,使除错更方便

(暂时)不要使用 require/include_once

require/include_once 每次被调用的时候都会打开目标文件!

- 如果用绝对路径的话,PHP 5.2/6.0 不存在这个问题
- 新版的 APC 缓存系统已经解决这个问题

文件 I/O 增加 => 效率降低

如果需要,可以自行检查文件是否已被 require/include。

不要调用毫无意义的函数

有对应的常量的时候,不要使用函数。

<?php
php_uname
(‘s’) == PHP_OS
;
php_version() == PHP_VERSION
;
php_sapi_name() == PHP_SAPI
;
?>
虽然使用不多,但是效率提升大概在 3500% 左右。

最快的 Win32 检查

<?php
$is_win
= DIRECTORY_SEPARATOR == ‘\\’
;
?>

- 不用函数
- Win98/NT/2000/XP/Vista/Longhorn/Shorthorn/Whistler…通用
- 一直可用

时间问题 (PHP>5.1.0 ONLY)

你如何在你的软件中得知现在的时间?简单,「time() time() again, you ask me…」。

不过总归会调用函数,慢。

现在好了,用 $_SERVER['REQUEST_TIME'],不用调用函数,又省了。

加速 PCRE

 对于不用保存的结果,不用 (),一律用 (?:)

这样 PHP 不用为符合的内容分配内存,省。效率提升 15% 左右。

- 能不用正则,就不用正则,在分析的时候仔细阅读手册「字符串函数」部分。有没有你漏掉的好用的函数?

加速 strtr

如果需要转换的全是单个字符的时候,用字符串而不是数组来做 strtr:

<?php
$addr
= strtr($addr, "abcd", "efgh");
// good
$addr = strtr($addr, array(‘a’ => ‘e’
,
                           
// …
                           
));
// bad
?>

效率提升:10 倍。

不要做无谓的替换

即使没有替换,str_replace 也会为其参数分配内存。很慢!解决办法:

- 用 strpos 先查找(非常快),看是否需要替换,如果需要,再替换

效率:

- 如果需要替换:效率几乎相等,差别在 0.1% 左右。
- 如果不需要替换:用 strpos 快 200%。

邪恶的 @ 操作符

不要滥用 @ 操作符。虽然 @ 看上去很简单,但是实际上后台有很多操作。用 @ 比起不用 @,效率差距:3 倍。

特别不要在循环中使用 @,在 5 次循环的测试中,即使是先用 error_reporting(0) 关掉错误,在循环完成后再打开,都比用 @ 快。

善用 strncmp

当需要对比「前 n 个字符」是否一样的时候,用 strncmp/strncasecmp,而不是 substr/strtolower,更不是 PCRE,更千万别提 ereg。strncmp/strncasecmp 效率最高(虽然高得不多)。

慎用 substr_compare (PHP5 ONLY)

按照上面的道理,substr_compare 应该比先 substr 再比较快咯。答案是否定的,除非:

- 无视大小写的比较
- 比较较大的字符串

不要用常量代替字符串

为什么:

- 需要查询杂凑表两次
- 需要把常量名转换为小写(进行第二次查询的时候)
- 生成 E_NOTICE 警告
- 会建立临时字符串

效率差别:700%。

不要把 count/strlen/sizeof 放到 for 循环的条件语句中

贴士:我的个人做法

<?php
for ($i = 0, $max = count($array);$i < $max; ++$i
);
?>

效率提升相对于:

- count 50%
- strlen 75%

短的代码不一定快

<?php
// longest
if ($a == $b
) {
   
$str .= $a
;
} else {
   
$str .= $b
;
}

// longer
if ($a == $b
) {
   
$str .= $a
;
}
$str .= $b
;

// short
$str .= ($a == $b ? $a : $b
);
?>

你觉得哪个快?

效率比较:

- longest: 4.27
- longer: 4.43
- short: 4.76

不可思议?再来一个:
<?php
// original
$d = dir(‘.’
);
while ((
$entry = $d->read()) !== false
) {
    if (
$entry == ‘.’ || $entry == ‘..’
) {
        continue;
    }
}

// versus
glob(‘./*’
);

// versus (include . and ..)
scandir(‘.’
);
?>

哪个快?

效率比较:

- original: 3.37
- glob: 6.28
- scandir: 3.42
- original without OO: 3.14
- SPL (PHP5): 3.95

画外音:从此也可以看出来 PHP5 的面向对象效率提高了很多,效率已经和纯函数差得不太多了。

提高 PHP 文件访问效率

需要包含其他 PHP 文件的时候,使用完整路径,或者容易转换的相对路径。

<?php

include ‘file.php’; // bad approach

incldue ‘./file.php’; // good

include ‘/path/to/file.php’; // ideal

?>

物尽其用

PHP 有很多扩展和函数可用,在实现一个功能的之前,应该看看 PHP 是否有了这个功能?是否有更简单的实现?

<?php
$filename
= "./somepic.gif"
;
$handle = fopen($filename, "rb"
);
$contents = fread($handle, filesize($filename
));
fclose($handle
);

// vs. much simpler

file_get_contents(‘./somepic.gif’);
?>

关于引用的技巧

引用可以:

- 简化对复杂结构数据的访问
- 优化内存使用
<?php
$a
['b']['c'
] = array();

// slow 2 extra hash lookups per access
for ($i = 0; $i < 5; ++$i
)
   
$a['b']['c'][$i] = $i
;

// much faster reference based approach
$ref =& $a['b']['c'
];
for (
$i = 0; $i < 5; ++$i
)
   
$ref[$i] = $i
;
?>

<?php
$a
= ‘large string’
;

// memory intensive approach
function a($str
)
{
    return
$str.‘something’
;
}

// more efficient solution
function a(&$str
)
{
   
$str .= ‘something’
;
}
?>

==============================================
参考资料
http://ilia.ws

Ilia 的个人网站,,他参与的开发以及出版的一些稿物链接等等。
http://ez.no

eZ components 官方网站,eZ comp 是针对 PHP5 的开源通用库,以效率为己任,Ilia 也参与了开发。
http://phparch.com

php|architect,不错的 php 出版商/培训组织。买不起或者买不到的话,网上可以下到很多经典的盗版。

http://talks.php.net

0

Mysql 优化


7.3 锁

7.3.1 锁机制

当前MySQL已经支持 ISAM, MyISAM, MEMORY (HEAP) 类型表的表级锁了,BDB 表支持页级锁,InnoDB 表支持行级锁。

很多时候,可以通过经验来猜测什么样的锁对应用程序更合适,不过通常很难说一个锁比别的更好,这全都要依据应用程序来决定,不同的地方可能需要不同的锁。

想要决定是否需要采用一个支持行级锁的存储引擎,就要看看应用程序都要做什么,其中的查询、更新语句是怎么用的。例如,很多的web应用程序大量的做查询,很少删除,主要是基于索引的更新,只往特定的表中插入记录。采用基本的MySQL MyISAM 表就很合适了。

MySQL中对表级锁的存储引擎来说是释放死锁的。避免死锁可以这样做到:在任何查询之前先请求锁,并且按照请求的顺序锁表。

MySQL中用于 WRITE(写) 的表锁的实现机制如下:

 

如果表没有加锁,那么就加一个写锁。

否则的话,将请求放到写锁队列中。

MySQL中用于 READ(读) 的表锁的实现机制如下:

 

如果表没有加写锁,那么就加一个读锁。

否则的话,将请求放到读锁队列中。

当锁释放后,写锁队列中的线程可以用这个锁资源,然后才轮到读锁队列中的线程。

这就是说,如果表里有很多更新操作的话,那么 SELECT 必须等到所有的更新都完成了之后才能开始。

从 MySQL 3.23.33 开始,可以通过状态变量 Table_locks_waited 和 Table_locks_immediate 来分析系统中的锁表争夺情况:

 

mysql> SHOW STATUS LIKE ‘Table%’;

+———————–+———+

| Variable_name | Value |

+———————–+———+

| Table_locks_immediate | 1151552 |

| Table_locks_waited | 15324 |

+———————–+———+

在 MySQL 3.23.7(在Windows上是3.23.25)以后,在 MyISAM 表中只要没有冲突的 INSERT 操作,就可以无需使用锁表自由地并行执行 INSERT 和 SELECT 语句。也就是说,可以在其它客户端正在读取 MyISAM 表记录的同时时插入新记录。如果数据文件的中间没有空余的磁盘块的话,就不会发生冲突了,因为这种情况下所有的新记录都会写在数据文件的末尾(当在表的中间做删除或者更新操作时,就可能导致空洞)。当空洞被新数据填充后,并行插入特性就会自动重新被启用了。

如果想要在一个表上做大量的 INSERT 和 SELECT 操作,但是并行的插入却不可能时,可以将记录插入到临时表中,然后定期将临时表中的数据更新到实际的表里。可以用以下命令实现:

 

mysql> LOCK TABLES real_table WRITE, insert_table WRITE;

mysql> INSERT INTO real_table SELECT * FROM insert_table;

mysql> TRUNCATE TABLE insert_table;

mysql> UNLOCK TABLES;

InnoDB 使用行级锁,BDB 使用页级锁。对于 InnoDB 和 BDB 存储引擎来说,是可能产生死锁的。这是因为 InnoDB 会自动捕获行锁,BDB 会在执行 SQL 语句时捕获页锁的,而不是在事务的开始就这么做。

行级锁的优点有:

 

在很多线程请求不同记录时减少冲突锁。

事务回滚时减少改变数据。

使长时间对单独的一行记录加锁成为可能。

行级锁的缺点有:

 

比页级锁和表级锁消耗更多的内存。

当在大量表中使用时,比页级锁和表级锁更慢,因为他需要请求更多的所资源。

当需要频繁对大部分数据做 GROUP BY 操作或者需要频繁扫描整个表时,就明显的比其它锁更糟糕。

使用更高层的锁的话,就能更方便的支持各种不同的类型应用程序,因为这种锁的开销比行级锁小多了。

表级锁在下列几种情况下比页级锁和行级锁更优越:

 

很多操作都是读表。

在严格条件的索引上读取和更新,当更新或者删除可以用单独的索引来读取得到时:

 

UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;

DELETE FROM tbl_name WHERE unique_key_col=key_value;

SELECT 和 INSERT 语句并发的执行,但是只有很少的 UPDATE 和 DELETE 语句。

很多的扫描表和对全表的 GROUP BY 操作,但是没有任何写表。

表级锁和行级锁或页级锁之间的不同之处还在于:

将同时有一个写和多个读的地方做版本(例如在MySQL中的并发插入)。也就是说,数据库/表支持根据开始访问数据时间点的不同支持各种不同的试图。其它名有:时间行程,写复制,或者是按需复制。

原文: 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.

按需复制在很多情况下比页级锁或行级锁好多了。尽管如此,最坏情况时还是比其它正常锁使用了更多的内存。

可以用应用程序级锁来代替行级锁,例如MySQL中的 GET_LOCK() 和 RELEASE_LOCK()。但它们是劝告锁(原文:These are advisory locks),因此只能用于安全可信的应用程序中。

 

7.3.2 锁表

为了能有快速的锁,MySQL除了 InnoDB 和 BDB 这两种存储引擎外,所有的都是用表级锁(而非页、行、列级锁)。

对于 InnoDB 和 BDB 表,MySQL只有在指定用 LOCK TABLES 锁表时才使用表级锁。在这两种表中,建议最好不要使用 LOCK TABLES,因为 InnoDB 自动采用行级锁,BDB 用页级锁来保证事务的隔离。

如果数据表很大,那么在大多数应用中表级锁会比行级锁好多了,不过这有一些陷阱。

表级锁让很多线程可以同时从数据表中读取数据,但是如果另一个线程想要写数据的话,就必须要先取得排他访问。正在更新数据时,必须要等到更新完成了,其他线程才能访问这个表。

更新操作通常认为比读取更重要,因此它的优先级更高。不过最好要先确认,数据表是否有很高的 SELECT 操作,而更新操作并非很‘急需’。

表锁锁在一个线程在等待,因为磁盘空间满了,但是却需要有空余的磁盘空间,这个线程才能继续处理时就有问题了。这种情况下,所有要访问这个出问题的表的线程都会被置为等待状态,直到有剩余磁盘空间了。

表锁在以下设想情况中就不利了:

 

一个客户端提交了一个需要长时间运行的 SELECT 操作。

其他客户端对同一个表提交了 UPDATE 操作,这个客户端就要等到 SELECT 完成了才能开始执行。

其他客户端也对同一个表提交了 SELECT 请求。由于 UPDATE 的优先级高于 SELECT,所以 SELECT 就会先等到 UPDATE 完成了之后才开始执行,它也在等待第一个 SELECT 操作。

下列所述可以减少表锁带来的资源争夺:

 

让 SELECT 速度尽量快,这可能需要创建一些摘要表。

启动 mysqld 时使用参数 –low-priority-updates。这就会让更新操作的优先级低于 SELECT。这种情况下,在上面的假设中,第二个 SELECT 就会在 INSERT 之前执行了,而且也无需等待第一个SELECT 了。

可以执行 SET LOW_PRIORITY_UPDATES=1 命令,指定所有的更新操作都放到一个指定的链接中去完成。详情请看“14.5.3.1 SET Syntax”。

用 LOW_PRIORITY 属性来降低 INSERT,UPDATE,DELETE 的优先级。

用 HIGH_PRIORITY 来提高 SELECT 语句的优先级。详情请看“14.1.7 SELECT Syntax”。

从MySQL 3.23.7 开始,可以在启动 mysqld 时指定系统变量 max_write_lock_count 为一个比较低的值,它能强制临时地提高表的插入数达到一个特定值后的所有 SELECT 操作的优先级。它允许在 WRITE 锁达到一定数量后有 READ 锁。

当 INSERT 和 SELECT 一起使用出现问题时,可以转而采用 MyISAM 表,它支持并发的SELECT 和 INSERT 操作。

当在同一个表上同时有插入和删除操作时,INSERT DELAYED 可能会很有用。详情请看“14.1.4.2 INSERT DELAYED Syntax”。

当 SELECT 和 DELETE 一起使用出现问题时,DELETE 的 LIMIT 参数可能会很有用。详情请看“14.1.1 DELETE Syntax”

执行 SELECT 时使用 SQL_BUFFER_RESULT 有助于减短锁表的持续时间.详情请看“14.1.7 SELECT Syntax”。

可以修改源代码 `mysys/thr_lock.’,只用一个所队列。这种情况下,写锁和读锁的优先级就一样了,这对一些应用可能有帮助。

以下是MySQL锁的一些建议:

 

只要对同一个表没有大量的更新和查询操作混在一起,目前的用户并不是问题。

执行 LOCK TABLES 来提高速度(很多更新操作放在一个锁之中比没有锁的很多更新快多了)。将数据拆分开到多个表中可能也有帮助。

当MySQL碰到由于锁表引起的速度问题时,将表类型转换成 InnoDB 或 BDB 可能有助于提高性能。详情请看“16 The InnoDB Storage Engine”和“15.4 The BDB (BerkeleyDB) Storage Engine”。

来源:网络

0

设计模式之Life time controller模式


转至 www.Huihoo.org

(作者:Douglas . Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作)

目的

对象生命周期管理者模式可以被用来控制对象的整个生命周期,从对象被首次使用前创建它们到应用程序中止前完全的销毁它们。此外通过在应用启动/中止时进行对象自动的预先创建/销毁,使这个模式能够用来替代静态对象的创建/销毁。

例子

单例(singleton)是一种通用的创建模式,它对唯一的类实例提供了一个全局的访问点同时能够延迟实例的创建直到它首次被访问。如果一个单例在程序 的整个生命周期中没有被需要,它将不会被创建。单例模式并没有提及在什么时候它的实例应该被销毁这个问题,但对于特定的应用或操作系统这将是个问题。
为了说明为什么提及销毁语义是重要的,考虑下面的日志组件,它通过向客户提供编程API接口实现分布式的日志服务。

 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;

 

Logger的构造函数,出于简化而忽略掉了,实现分配各种OS的endsystem资源,像SOCKET句柄,共享内存段,和/或系统范围的信号量,它们被用于实现日志服务提供的客户API。
为了减少尺寸,提高记录信息的可读性,一个应用可以选择用批处理而不是分立的方式来记录某些数据,像时间统计数据。例如,下面的统计类根据每个单独的标识,批量处理时间数据:

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 &tv);// Report recorded statistics to the log.void report (int id) {Logger::instance ()->log ("Avg timing %d: ""%ld sec %ld usec\n",id,average_i (id).tv_sec,average_i (id).tv_usec);}protected:// Default constructor.Stats (void);// Internal accessor for an average.const timeval &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;} 

在记录了各种统计数据后,程序调用report方法,它将使用Logger单例根据标识来记录平均的时间统计。
Logger和Stats类对应用提供了截然不同的服务:Logger类提供了通用的日志能力,而Stats类提供了具体化的批量处理和记录时间统计功能。这些类都是使用单例模式实现的,于是在应用中每一个类都只有一个实例。
下面的例子展示了一个应用可能使用Logger和Stats单例对象的一种情况:

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 < argc; ++i) {::gettimeofday (&start_tv);// do some work between timestamps . . .::gettimeofday (&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 ()->record (i, delta_tv);// . . . and log some output.Logger::instance ()->log ("Arg %d [%s]\n", i, argv[i]);Stats::instance()->report (i);}// Logger and Stats singletons are not cleaned up when main returns.return 0;} 

注意,应用没有清晰的创建或销毁Logger和Stats单例,也就是说它们的生命周期管理是与应用逻辑相分离的。这是一个通常的习惯在应用程序退出的时候不对单例对象进行销毁处理。
但是,由于单例模式仅仅涉及单例对象的创建,而没有应对它的销毁,这将产生几个缺陷。值得注意的是,当上面的应用程序中止时,Logger和Stats单 例对象都没有被清理。在最好的情况下,这将导致内存泄漏的错误报告,最坏的情况下,重要的系统资源可能没有被完全的释放和销毁。
例如,如果Logger和Stats单例对象持有OS的资源,像系统范围的信号量,I/O缓冲,或者其他被分配的OS资源,将产生问题。当程序关闭的时候 没有适当的释放这些资源可能导致死锁和其他同步灾难。为了减少这样的问题,在程序退出前每个单例实例的析构函数应该被调用。
一种试图保证单例销毁的单例模式实现方法是,在一个文件范围内声明一个利用区域锁习惯用法实现的静态单例类实例。例如,下面的Singleton Destroyer模板提供了一个析构器用于销毁单例对象。

	template  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.}; 

为了使用这个类,所有应该做的是通过定义一个静态的Singleton_Destroyer类实例来修改Logger和Stats类。下面的例子展示了对Logger类的修改:

static Singleton_Destroyer 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_destroyer是如何获取单例对象并在程序退出时将其销毁的。针对Stats类也有类似的修改。

不幸的是,清晰的实例化一个静态的Singleton_Destroyer实例将带来一些问题。例如,在C++中,每一个 Singleton_Destroyer应该在不同的编译单元中定义(译者:内联定义。因为静态数据具有内联特性,一般不将其放在头文件中,因为每个包含 这个头文件的编译单元都将拥有一个静态数据的副本。)。在这种情况下,它们的析构函数的调用次序是没有保证的,这将导致未定义的行为。特别的,如果在不同 编译单元中的单例对象共享了一些资源,如SOCKET句柄,共享内存片段和/或系统范围的信号量,程序将不能干净的退出。在C++中单例对象的这种不确定 的销毁次序将很难保证(1)在最后一个使用资源的单例对象被完全的销毁前,而不是(2)在一个单例对象存在,并依然使用这些资源前,OS去回收这些资源。

总的来说,在上面的例子中没有解决的关键问题是:(1)被单例分配的资源应该在程序退出时被最终释放。(2)不受约束的创建和销毁静态实例的次序将导致严重的程序错误。(3)将软件开发人员和管理对象生命周期的责任分离开来,将减少系统出错的可能。

语境

> 一个应用和系统对其所创建的对象进行全面的生命周期控制,对于保证系统的正确性是必要的。

问题

许多应用并没有适当的处理对象的整个生命周期。特别是那些使用创建模式的应用,如单例模式,经常没有考虑这些对象的销毁。同样的,那些使用静态对象来提供对象销毁的应用经常遭受不一致的初始化和销毁过程。下面是这些问题的简要说明:

1、单例对象析构带来的问题。单例对象的实例可能被动态创建,一个动态创建的单例实例必将会造成资源的泄漏,如果它们没有被销毁。但是,单例对象造成的泄漏经常被忽略,这是因为:
(1)它们对应用不会造成显著的影响,
(2)在大多数通用的操作系统中,如UNIX、,在程序中止时它们会被自动清理。 不幸的是,在下面的环境中资源泄漏会导致麻烦:
(a)当系统需要优雅的关闭时。单例对象应该为其所获取的系统资源,如系统范围的锁、打开的网络连接、共享的内存片断负责。清晰的销毁单例对象是值得做的,这可以保证当程序中止时,所有这些资源能够在预定好的点上被销毁。
(b)当一个单例对象拥有另一个单例对象的引用时。清晰的管理单例对象的销毁次序,对于避免在应用中止时由于空悬(dangling)引用产生的问题是必要的。
(c)当检测内存泄漏时。内存检测工具,如NuMega BoundsCheck, ParaSoft Insure++,和Rational Purify,被用于检测像C/C++这类需要清晰的动态分配和释放内存的语言。这些工具会指示单例实例作为一处内存泄漏。
(d)当从一个全局内存池中动态分配内存时。一些实时操作系统,如VxWorks和PSOS,有一个对应于所有应用的唯一的全局堆。因此,应用任务在中止时必须释放任何动态分配的内存,否则,这些内存不会被释放而被其他应用使用直到系统重新启动。

2、静态对象生命周期的问题。一些对象必须先于任何使用前被创建。在C++语言中,这些实例往往被定义成静态变量,它们先于程序主函数入口点被调用前创建,在程序中止时被销毁。但是静态对象有几个严重的缺陷:
(a)不确定的构造/析构的次序。C++语言仅仅指定了在一个编译单元内部静态对象的构造/析构的次序。构造的次序是对象声明的次序、析构则与之相反。但 是没有指定处于不同编译单元的静态对象的构造/析构的次序。因此构造/析构的次序是实现依赖的。使用静态对象来处理初始化相关性是很难写出可以移植的C+ +代码。通常,比较简单的方式是完全避免使用静态对象,而不是解决、防止这些相关性。为了在某些平台上实现正确的程序操作,清晰的单例管理是必须的,因为 它们能够事先销毁单例对象。例如,早先JDK中的垃圾回收器,能够销毁没有任何参考指向的对象,即使这个对象是被设置成单例。虽然这个问题在后续的JDK 中被解决,但是对象生命周期管理者能够在的应用的控制下,通过维护单例的参考来解决这个问题。
(b)不能被嵌入式系统很好的支持。由于历史原因,嵌入系统都是使用C语言因此,它们总是不能对OO程序语言特性提供无缝的支持。例如,在C+ +语言中的静态对象的构造/析构经常会复杂化嵌入系统的编程。嵌入式OS可能已经支持清晰的调用静态的构造/析构函数,但没有达到程序员期望的最佳状态。 一些嵌入式OS不支持一个程序有一个唯一的入口点的概念。例如VxWorks支持多个task,task类似于线程因为它们共享一个地址空间。但是对于每 一个应用并没有指定主task。因此,这些嵌入式系统可以被配置成在模块载入/卸载时分别的调用静态对象的构造器/析构器。
(c)静态对象增加了应用启动的时间。静态对象可能先于任何主入口点的调用,在应用启动时被初始化。如果这些对象在一个特定的运行中没有被使 用,那么应用启动(退出)的时间被白白的增加。减少这种浪费的一种方法是,使用安需创建对象替代静态对象,如使用单例模式。使用单例替代静态对象也可以用 于延迟对象对象的构建直到被首次使用时,而且这也将减少启动时间。一些实时的应用已经发现在进入主入口点后和对象被正是需要前的一个特定时间点创建单例对 象带来的好处。一个或多个这样静态对象的缺点就足以提供将它们从程序中移走的全部动机。通常,比较好的做法是不使用它们,而是应用下面的方案替代。

解决之道

定义一个对象生命周期管理者,它是一个包含了预分配对象和被管理对象集合的单例。它的职责是在应用启动和中止时分别创建和销毁预分配对象。它更深一层的职责是在程序中止时保证所有的被管理对象能够被完全的销毁。

适应性

在下列情况下使用对象生命周期管理者:

1、在程序中止时,单例或其他动态分配的对象能够在不需要程序本身进行干预的情况下被销毁。单例和其他创建模式都没有提及它们所创建的对 象应该什么时候被销毁和由谁来销毁这个问题。与之相反,对象生命周期管理者提供了一个便利的用于删除被动态创建对象的全局对象。由创建模式产生的对象可以 向对象生命周期管理者进行注册,确保在程序中止时能够被销毁。
2、静态对象必须从应用中移除。正像前面所描述的那样,静态对象是麻烦的制造者,特别是在某些语言和某些平台上。对象生命周期管理者提供了一种利用预先分配对象替代静态对象的机制。预分配对象在应用使用它们前被创建,在应用中止前被销毁。
3、对于那些不支持静态对象创建和销毁的平台。一些嵌入式系统,如VXWORKS和PSOS不总是在程序启动时创建静态对象,在程序中止时销毁静态对象。通常,比较好的方式是移除静态对象。
4、虽然应用需要这样,但是底层平台不能提供一个主程序的概念。在缺少对主程序概念支持的平台上,将缺少对静态对象创建和销毁的支持。对象生命周期管理者通过划分地址空间的方式可以被用来仿效一个主程序。从应用的角度来看,每一个对象生命周期管理者描绘了一个程序的范畴。
5、对象被销毁的次序必须由应用来指定。动态创建的对象可以通过向对象生命周期管理者注册方式来使自身被销毁。对象生命周期管理者可以以任意希望的次序来实现对象的销毁。
6、应用需要一个明确的单例对象管理机制。像上面所描述的,单例对象可能被过早的销毁。如早期的JAVA平台。对象生命周期管理者可以延迟对象的销毁直到应用中止。

结构和参与者

在下面的图中,使用UML方式展示了对象生命周期管理者的结构和参与者。 对象生命周期管理者:每一个对象生命周期管理者,它是一个包含了预分配对象和被管理对象集合的单例。

1、被管理对象:任何一个向对象生命周期管理者注册并由其负责销毁的对象。对象销毁发生在对象生命周期管理者本身被销毁的时候,通常都是在程序中止的时候。
2、预分配对象:被对象生命周期管理者在其内部通过硬编码方式实现创建和销毁的对象。它和对象生命周期管理者具有相同的生命周期,也就是执行应用的进程的生命周期。
3、应用:应用清晰或非清晰的创建和销毁对象生命周期管理者。此外,应用向本身可能包含预分配对象的对象生命周期管理者注册被管理对象。

动态特征

下图中展示了对象生命周期管理者模式中的参与者之间的动态协作:
这个图描述了四个分离的活动:

1、对象生命周期管理者创建和初始化,它将依次创建预分配对象。
2、应用创建管理对象,并向对象生命周期管理者注册。
3、应用使用已经注册的管理对象和预先分配对象。
4、对象生命周期管理者的销毁,这将销毁所有它控制的管理对象和预分配对象。

实现

对象生命周期管理者可以用下面展示的步骤来实现。这个实现是基于ACE框架提供的对象管理者来实现的,它将引出一些将在这个单元讨论的有趣问题。下面讨论的一些步骤是语言相关的,因为ACE是用C++语言编写的。
1、定义对象生命周期管理者组件。这个组件向应用提供一个接口,通过它注册那些生命周期必须被管理对象,确保在系统中止时能够完全的销毁这些对 象。此外,这个组件还定义了一个智囊团(repository)用于确保它所管理的对象能够被完全销毁。对于那些被注册,在程序中止时被销毁的预分配对象 和管理对象来说,对象生命周期管理者是一个容器。
下面是用于实现对象生命周期管理者组件的子步骤:
(a)定义一个用于注册被管理对象的接口。一种用于向对象生命周期管理者注册被管理对象的方法是使用C语言库中的atexit函数,它在程序退出 时将调用特定的中止函数(用于实现退出时的清理操作)。但是,不是所有的平台都支持atexit函数,而且atexit函数的实现限制了最大被注册的中止 函数数目为32。因此。对象生命周期管理者必须支持下面的两个技术用于实现被管理对象的注册:使用一个容器来容纳所有的被管理对象,在程序退出时自动清除 这些被管理对象。
i.定义一个cleanup函数接口。对象生命周期管理者允许应用向其注册任意类型的对象。当程序中止时,对象生命周期管理者将自动的清理这些对象。下面的C++类展示了一个在ACE中使用的特殊的CLEANUP_FUNC,用于注册能够被清除的对象或数组。

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);}; 

这个静态的at_exit函数注册一个能够在程序退出时被清除的对象或数组对象。Cleanup_hook参数指向全局的函数或时静态的 方法,该方法在清理时被调用用于销毁对象或数组。在销毁时,对象生命周期管理者向Cleanup_hook函数传递对象和相关的参数。参数包含任何被 Cleanup_hook函数需要的额外信息,如数组中对象的个数等。
ii.定义一个cleanup基类接口。这个接口允许应用向对象生命周期管理者注册销毁任何从cleanup接口继承的对象。这个 cleanup基类接口应该有一个虚的析构函数和一个虚的cleanup方法,这个方法的实现仅仅是简单的调用delete this,它将导致所有继承类的析构函数被依次调用。下面的代码段展示了在ACE中这个接口是如何实现的:

class Cleanup{public:// Destructor.virtual ?Cleanup (void);// By default, simply deletes this.virtual void cleanup (void *param = 0);}; 

下面的代码段展示了在ACE中,对象生命周期管理者被用于注册从cleanup接口继承对象的接口。

class Object_Lifetime_Manager{public:static int at_exit (Cleanup *object,void *param = 0);}; 

这个静态的at_exit函数注册一个能够在程序退出时被清除的cleanup对象。在析构时,对象生命周期管理者调用cleanup对象中的cleanup方法,param参数包含了任何被cleanup函数需要的额外信息。
(b)定义一个单例适配器。虽然使用上面定义的对象生命周期管理者方法是可以清晰的编码实现单例对象,但这样做是冗余的和易错的。因此,定义一个 单例的适配器模板用于封装创建单例对象和向对象生命周期管理者注册的细节是非常有用的。此外,单例适配器可以使用线程安全的双检测加锁优化模式 (double checked locking optimization pattern)来创建和访问类型指定的单例对象实例。 下面的代码片断展示了单例适配器在ACE中是如何实现的:

template 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;// Register with the Object Lifetime// Manager to control destruction.Object_Lifetime_Manager::at_exit (singleton_);}return &singleton_->instance_;}protected:// Default constructor.Singleton (void);// Contained instance.TYPE instance_;// Instance of the singleton adapter.static Singleton *singleton_;}; 

Singleton类模板从cleanup类继承,这样就允许单例实例向对象生命周期管理者注册自己
(d)定义一个用于注册预分配对象的接口。预分配对象总是在应用的主进程启动前被创建。例如,在一些应用中,同步锁必须在任何使用前被创建,用于 防止竞争条件。这样一来,这些对象必须在每个对象生命周期管理者类中通过硬编码的方式实现。将这些对象的创建封装在对象生命周期管理者的初始化阶段,不给 应用代码添加任何的复杂性。
对象生命周期管理者能够预先分配对象或数组。它要么能够静态的在全局数据中实现这些预先分配,要么在堆中动态的实现。一种有效的实现方式是在数 组中存储每个预分配对象。像C++这种特定的语言不支持数组容纳异类对象。因此,用指针替代对象本身来实现数组存储。实际的对象是被对象生命周期管理者在 其初始化的过程中动态创建的,在其析构的时候被销毁。
下面的子步骤用于实现预分配对象:
1. 限制暴露。为了最小化头文件的暴露,通过宏或枚举来标识预分配对象。

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,}; 

2. 使用cleanup适配器。Cleanup适配器类模板从cleanup基类继承,包装那些没有从cleanup基类继承的类型,使它们能够被对象生命周期管理者管理起来。

#define PREALLOCATE_OBJECT(TYPE, ID) {\Cleanup_Adapter *obj_p;\obj_p = new Cleanup_Adapter;\preallocated_object[ID] = obj_p;\}#define DELETE_PREALLOCATED_OBJECT(TYPE, ID)\cleanup_destroyer (\static_cast *>\(preallocated_object[ID]), 0);\preallocated_object[ID] = 0; 

3.定义预分配对象的访问接口。应用需要简便的和类型安全的预分配对象的访问接口。因为对象生命周期管理者支持不同类型的预分配对象。提供一个分 离的,包含有一个接受一个ID参数的成员函数的,在函数内部对象被预先分配,返回一个指向对象的正确的类型指针的类模板适配器是必要的。 下面的代码片断展示了在ACE中通过类模板适配器如何实现这个接口:

template 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&(static_cast *>(Object_Lifetime_Manager::preallocated_object[id]))->object ();}// . . . other methods omitted.}; 

(e)定义被注册对象的析构次序。对象生命周期管理者可以实现以任意次序销毁被注册对象。例如,可以使用优先级别标注,使销毁的次序按照优先级降 序进行。应该提供一个用于设定和改变对象优先级别的接口函数。我们已经发现,以注册顺序相反的次序销毁对象的策略对于ACE应用来说已经足够了。一个应用 可以通过控制对象的注册次序来指定对象的销毁次序。
(f)定义一个中止函数接口。到目前为止所讨论的生命周期管理功能仅涉及在程序中止时销毁被管理对象。但是对象生命周期管理者能够提供一个更通 常的功能:使用其内部的相同实现机制在程序中止时能够调用一个函数。 例如,为了在程序中止时确保完全清楚打开的win32 SOCKET,WSACleanup函数必须被调用。可以使用一个区域锁习惯用法的变化实现,创建一个特殊的包装门面类,它的构造函数会调用初始化函数, 它的析构函数会调用cleantup函数。于是,应用可以向对象生命周期管理者注册这样类的实现,这样一来在对象生命周期管理者销毁时会销毁内部的被管理 对象,中止API也将会被调用。
但是,这样的设计对大多数应用来说太困难了,而且它还是容易出错的,因为应用必须保证这个类被用作单例对象,因为这些API函数只能被调用一次。 作为替代,这些API中止函数将被作为对象生命周期管理者的中止方法的一部分被调用。
(g)移动通用接口和实现细节到一个基础类中。分解一些内部的细节到Object Lifetime Manager Base类中将使Object Lifetime Manager的实现更加简单,健壮。定义一个Object Lifetime Manager Base类用于支持创建不同类型的Object Lifetime Manager。

3、确定如何管理对象生命周期管理者自己的生命周期。对象生命周期管理者有责任初始化其他全局对象和静态对象,但是这将引发连带的问题,就是这个单例对象如何初始化和析构它自己?下面是用于初始化对象生命周期管理者实例的几种选择:
(a)静态初始化。如果应用对静态对象的创建和销毁没有次序限制,可以创建对象生命周期管理者为静态对象。例如,ACE中的对象生命周期管理者可以被作为静态对象创建。
(b)栈初始化。当一个主程序线程明确的定义了程序的入口点和中止点时,可以在主程序线程的栈上创建对象生命周期管理者,这样可以简化创建和销毁 对象生命周期管理者的编程逻辑。这种用于初始化对象生命周期管理者的方法是假设每个程序中只有一个唯一的主线程。这个线程确定了程序本身,也就是,程序在 运行,当且仅当主线程是活动的。这个方法有一个显著的优点:对象生命周期管理者实例在每一条离开MAIN函数的路径上都将被自动的销毁。
(c)清晰的初始化。这个方法是,在应用程序的控制之下,清晰的创建对象生命周期管理者实例,对象生命周期管理者类中init和fini方法允许应用在任何需要的时候创建和销毁对象生命周期管理者实例。这个选择减轻了在使用DLL带来的复杂度。
(d)动态库初始化。在这个方法中,创建和销毁对象生命周期管理者分别在它的DLL被加载和卸载的时候。许多动态库工具都包含调用如下方法能力:(1)一个出世化方法当DLL被加载时(2)一个中止函数当DLL被卸载时。

结论

使用它带来的好处:
1、 在程序中止时销毁单例对象和其他被管理对象。对象生命周期管理者模式允许应用"干净的"中止,释放被管理对象占用的内存,连同它们持有的其他资源。
2、 指定销毁的次序。对象被销毁的顺序可以被指定。这个指定的销毁机制可以任意复杂任意简单。
3、 从库和应用中移除了静态对象。可以使用预分配对象替代静态对象。这将防止应用依赖于静态对象的创建和销毁的次序。
使用它带来的缺陷:
1、管理者自己的生命周期管理。应用本身必须保证尊重对象生命周期管理者的生命周期,不在其外调用对象生命周期管理者的服务。例如,应用不要先于 对象生命周期管理者完全出世化之前试图去访问预分配对象,同样的,应用不要先于最后访问预分配对象或被管理对象前销毁对象生命周期管理者。最后,如果可以 假定对象生命周期管理者仅被一个线程初始化,那么实现对象生命周期管理者是非常简单的。这将免除在其初始化函数中使用静态锁
2、和共享库一起使用。在支持共享库运行时动态加载和卸载的平台上,应用程序必须小心的对待平台指定的特性对对象生命周期管理者的生命周期的影 响。例如,在windows NT平台上,对象生命周期管理者应该被应用或是包含它的DLL来初始化,这将避免潜在死锁状态,由于OS内部已经串行化装载DLL操作。 一个相关的问题是在DLL创建单例对象,被应用代码中的对象生命周期管理者管理。当DLL先于应用中止前被卸载,那么在应用中止时,对象生命周期管理者就 会试图去使用已经不在应用中的代码来销毁这个单例对象。

实现样例代码

下面的代码仅仅展示了预分配对象的处理过程。(译者:而被管理对象的处理过程被忽略了,这个过程比较复杂,涉及较多的ACE其他的封装类,包括ACE_OS_Exit_Info、ACE_Cleanup_Info_Node等。后面将有比较详细的讨论。)

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 // calls hasn't reached the number of // 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_ <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_ >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_->starting_up_i () : 1;}static int shutting_down (void) {return instance_ ?instance_->shutting_down_i () : 1;}enum Preallocated_Object{# if defined (MT_SAFE) && (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->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) && (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//  has already been called, or//  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_->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) && (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;} 

译者补充

被管理对象的处理类图如下:

每个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函数。在这个函数中进行如下操作:
ACE_Cleanup_Info new_info;
new_info.object_ = object;
new_info.cleanup_hook_ = cleanup_hook;
new_info.param_ = param;
registered_objects_ = registered_objects_->insert
(new_info);其中registered_objects是指向ACE_Cleanup_Info_Node的指针,是ACE_OS_Exit_Info的私有数据成员。
ACE_Cleanup_Info_Node本身构成一个单向的链表,其包含一个ACE_Cleanup_Info实例和指向下一个ACE_Cleanup_Info_Node节点的指针。

这样,应用注册的被管理对象的指针、中止函数和需要参数都将以链表的形式存放在ACE_OS_Exit_Info中。当 ACE_Object_Manager退出时,其会调用其内部的fini函数,该函数将调用ACE_OS_Exit_Info中的call_hooks函 数。这个函数按照注册相反的顺序一次遍历每个ACE_Cleanup_Info_Node,并执行其中的
ACE_Cleanup_Info中的相关退出操作:

for (ACE_Cleanup_Info_Node *iter = registered_objects_;       iter  &&  iter->next_ != 0;       iter = iter->next_)    {      ACE_Cleanup_Info &info = iter->cleanup_info_;      // The object is an ACE_Cleanup.      ace_cleanup_destroyer (ACE_reinterpret_cast (ACE_Cleanup *,                                                     info.object_),                               info.param_);     }

 

0

设计模式之Double Checked Locking模式


转至 www.Huihoo.org

(作者:Douglas . Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作)

意图

无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,可以用Double Checked Locking 模式来减少竞争和加锁载荷。

动机

1、标准的单例。开发正确的有效的并发应用是困难的。程序员必须学习新的技术(并发控制和防止死锁的算法)和机制(如多线程和同步API)。此外,许多熟 悉的设计模式(如单例和迭代子)在包含不使用任何并发上下文假设的顺序程序中可以工作的很好。为了说明这点,考虑一个标准的单例模式在多线程环境下的实 现。单例模式保证一个类仅有一个实例同时提供了全局唯一的访问这个实例的入口点。在c++程序中动态分配单例对象是通用的方式,这是因为c++程序没有很 好的定义静态全局对象的初始化次序,因此是不可移植的。而且,动态分配避免了单例对象在永远没有被使用情况下的初始化开销。

 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_;};

 

应用代码在使用单例对象提供的操作前,通过调用静态的instance方法来获取单例对象的引用,如下所示:
Singleton::instance ()->method ();
2、问题:竞争条件。不幸的是,上面展示的标准单例模式的实现在抢先多任务和真正并行环境下无法正常工作。例如,如果在并行主机上运行的多个线程在单例对 象初始化之前同时调用Singleton::instance方法,Singleton的构造函数将被调用多次,这是因为多个线程将在上面展示的临界区中 执行new singleton操作。临界区是一个必须遵守下列定式的指令序列:当一个线程/进程在临界区中运行时,没有其他任何线程/进程会同时在临界区中运行。在 这个例子中,单例的初始化过程是一个临界区,违反临界区的原则,在最好的情况下将导致内存泄漏,最坏的情况下,如果初始化过程不是幂等的 (idempotent.),将导致严重的后果。

3、 通常的陷阱和弊端。实现临界区的通常方法是在类中增加一个静态的Mutex对象。这个Mutex保证单例的分配和初始化是原子操作,如下:

class Singleton{public:static Singleton *instance (void){// Constructor of guard acquires lock_ automatically.Guard 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_;};

 

guard类使用了一个c++的习惯用法,当这个类的对象实例被创建时,它使用构造函数来自动获取一个资源,当类对象离开一个区域时,使用析构器 来自动释放这个资源。通过使用guard,每一个对Singleton::instance方法的访问将自动的获取和释放lock_。
即使这个临界区只是被使用了一次,但是每个对instance方法的调用都必须获取和释放lock_。虽然现在这个实现是线程安全的,但过多的加锁负载是不能被接受的。一个明显(虽然不正确)的优化方法是将guard放在针对instance进行条件检测的内部:

static Singleton *instance (void){if (instance_ == 0) {Guard guard (lock_);// Only come here if instance_ hasn't been initialized yet.instance_ = new Singleton;}return instance_;}

 

这将减少加锁负载,但是不能提供线程安全的初始化。在多线程的应用中,仍然存在竞争条件,将导致多次初始化instance_。例如,考虑两 个线程同时检测 instance_ == 0,都将会成功,一个将通过guard获取lock_另一个将被阻塞。当第一线程初始化Singleton后释放lock_,被阻塞的线程将获取 lock_,错误的再次初始化Singleton。
4、解决之道,Double Checked Locking优化。解决这个问题更好的方法是使用Double Checked Locking。它是一种用于清除不必要加锁过程的优化模式。具有讽刺意味的是,它的实现几乎和前面的方法一样。通过在另一个条件检测中包装对new的调 用来避免不必要的加锁:

class Singleton{public:static Singleton *instance (void){// First checkif (instance_ == 0){// Ensure serialization (guard constructor acquires lock_).Guard guard (lock_);// Double check.if (instance_ == 0)instance_ = new Singleton;}return instance_;// guard destructor releases lock_.}private:static Mutex lock_;static Singleton *instance_;};

 

第一个获取lock_的线程将构建Singleton,并将指针分配给instance_,后续调用instance方法的线程将发现 instance_ != 0,于是将跳过初始化过程。如果多个线程试图并发初始化Singleton,第二个检测件阻止竞争条件的发生。在上面的代码中,这些线程将在lock_上 排队,当排队的线程最终获取lock_时,他们将发现instance_ != 0于是将跳过初始化过程。

上面Singleton::instance的实现仅仅在Singleton首次被初始化时,如果有多个线程同时进入instance方法将导致加锁负 载。在后续对Singleton::instance的调用因为instance_ != 0而不会有加锁和解锁的负载。 通过增加一个mutex和一个二次条件检测,标准的单例实现可以是线程安全的,同时不会产生过多的初始化加锁负载。

适应性

> 当一个应用具有下列特征时,可以使用Double Checked Locking优化模式:
1、应用包含一个或多个需要顺序执行的临界区代码。
2、多个线程可能潜在的试图并发执行临界区。
3、临界区仅仅需要被执行一次。
4、在每一个对临界区的访问进行加锁操作将导致过多加锁负载。
5、在一个锁的范围内增加一个轻量的,可靠的条件检测是可行的。

结构和参与者

通过使用伪代码能够最好地展示Double Checked Locking模式的结构和参与者,图1展示了在Double Checked Locking模式有下列参与者:

1、仅有一次临界区(Just Once Critical Section,)。临界区所包含的代码仅仅被执行一次。例如,单例对象仅仅被初始化一次。这样,执行对new Singleton的调用(只有一次)相对于Singleton::instance方法的访问将非常稀少。
2、mutex。锁被用来序列化对临界区中代码的访问。
3、标记。标记被用来指示临界区的代码是否已经被执行过。在上面的例子中单例指针instance_被用来作为标记。
4、 应用线程。试图执行临界区代码的线程。

协作

图2展示了Double Checked Locking模式的参与者之间的互动。作为一种普通的优化用例,应用线程首先检测flag是否已经被设置。如果没有被设置,mutex将被获取。在持有 这个锁之后,应用线程将再次检测flag是否被设置,实现Just Once Critical Section,设定flag为真。最后应用线程释放锁。

结论

使用Double Checked Locking模式带来的几点好处:
1、最小化加锁。通过实现两个flag检测,Double Checked Locking模式实现通常用例的优化。一旦flag被设置,第一个检测将保证后续的访问不要加锁操作。
2、防止竞争条件。对flag的第二个检测将保证临界区中的事件仅实现一次。
使用Double Checked Locking模式也将带来一个缺点:产生微妙的移植bug的潜能。这个微妙的移植问题能够导致致命的bug,如果使用Double Checked Locking模式的软件被移植到没有原子性的指针和正数赋值语义的硬件平台上。例如,如果一个instance_指针被用来作为Singleton实现 的flag,instance_指针中的所有位(bit)必须在一次操作中完成读和写。如果将new的结果写入内存不是一个原子操作,其他的线程可能会试 图读取一个不健全的指针,这将导致非法的内存访问。
在一些允许内存地址跨越对齐边界的系统上这种现象是可能的,因此每次访问需要从内存中取两次。在这种情况下,系统可能使用分离的字对齐合成flag,来表示instance_指针。
如果一个过于激进(aggressive)编译器通过某种缓冲手段来优化flag,或是移除了第二个flag==0检测,将带来另外的相关问题。后面会介绍如何使用volatile关键字来解决这个问题。

实现和例子代码

ACE在多个库组件中使用Double Checked Locking模式。例如,为了减少代码的重复,ACE使用了一个可重用的适配器ACE Singleton来将普通的类转换成具有单例行为的类。下面的代码展示了如何用Double Checked Locking模式来实现ACE Singleton。

// A Singleton Adapter: uses the Adapter// pattern to turn ordinary classes into// Singletons optimized with the// Double-Checked Locking pattern.template class ACE_Singleton{public:static TYPE *instance (void);protected:static TYPE *instance_;static LOCK lock_;};template  TYPE *ACE_Singleton 

ACE Singleton类被TYPE和LOCK来参数化。因此一个给定TYEP的类将被转换成使用LOCK类型的互斥量的具有单例行为的类。
ACE中的Token Manager.是使用ACE Singleton的一个例子。Token Manager实现在多线程应用中对本地和远端的token(一种递归锁)死锁检测。为了减少资源的使用,Token Manager被按需创建。为了创建一个单例的Token Manager对象,只是需要实现下面的typedef:

typedef ACE_Singleton

// 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 ()->testdeadlock ()){	errno = EDEADLK;	return -1;}else{	// Sleep waiting for the lock...	// Acquire lock...}}

} 

变化

一种变化的Double Checked Locking模式实现可能是需要的,如果一个编译器通过某种缓冲方式优化了flag。在这种情况下,缓冲的粘着性(coherency)将变成问题,如 果拷贝flag到多个线程的寄存器中,会产生不一致现象。如果一个线程更改flag的值将不能反映在其他线程的对应拷贝中。

另一个相关的问题是编译器移除了第二个flag==0检测,因为它对于持有高度优化特性的编译器来说是多余的。例如,下面的代码在激进的编译器下将被跳过对flag的读取,而是假定instance_还是为0,因为它没有被声明为volatile的。

Singleton *Singleton::instance (void){if (Singleton::instance_ == 0){// Only lock if instance_ isn't 0.Guard guard (lock_);// Dead code elimination may remove the next line.// Perform the Double-Check.if (Singleton::instance_ == 0)// ...

 

解决这两个问题的一个方法是生命flag为Singleton的volatile成员变量,如下:
private:
static volatile long Flag_; // Flag is volatile.
使用volatile将保证编译器不会将flag缓冲到编译器,同时也不会优化掉第二次读操作。使用volatile关键字的言下之意是所有对flag的访问是通过内存,而不是通过寄存器。

相关模式

Double Checked Locking模式是First-Time-In习惯用法的一个变化。First-Time-In习惯用法经常使用在类似c这种缺少构造器的程序语言中,下面的代码展示了这个模式:

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来初始化它自己。

Previous Page

Random Posts Recent Comments

  • 女友糖尿病害我蛀牙 Says:

    汗一个…...

  • Htj06 Says:

    zhenyouchuangyi...

  • 电商圈 Says:

    试图该怎么建立啊,,怎在程序中是吸纳...

  • edward Says:

    看得人心旷神怡,好文,情不自禁的顶一下...

  • Daniel Says:

    我也在处理这个问题,没有找到好的方法。我用了楼上兄弟的方法,还是可以的。不知道您找到好的方法了吗、我暂时楼上兄弟的方法。...

  • 卡,卡 Says:

    弱弱问一句:博主,你博客的模板这样设计pv高吗?...

  • 站长工具 Says:

    博主,兔年快乐!...

  • health Says:

    great post!!I hope I can read more in your website....

  • pdu Says:

    好博文,支持分享...

  • 站长工具 Says:

    博主的文章很不错,我是站长工具-站长精灵的作者,一款专业的SEO工具软件(可以帮您提高博客的流量),想跟您交换个链接,不知可否...

Tag Cloud

arm audio blog brew cache class debug flash google html j2me java javascript Joke linux lua mobile mtk php python ror ruby server shell stream unix web windows 优化 动态加载 女人 女生 平台 开发 手机 技术 流媒体 测试 漫画 生活 男人 男生 缓存 芯片