google背后的分布式架构
Posted by dengwei
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 blog 中有对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 还给了一个关于压缩 web 蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面,行按照以下的方式命名:“com.cnn.www/index.html:http”.在未压缩前的web page 页面大小是:45.1 TB ,压缩后的大小是:4.2 TB , 只是原来的 9.2%。Links 数据压缩到原来的 13.9% , 链接文本数据压缩到原来的 12.7%。
Read the rest of this entry »
[转] 一步一步学习Vim 全图解释
Posted by gavinkwoe

Whole 中文版 感谢 fdl 的翻译工作

以下是学习这些基本的VIM命令的基本步骤,在学习完之后,可以依照上图进一步的学习和使用!!
步骤1
注释1:
1) h j k l为光标键,分别左下上右
2) i 插入键进入编辑模式,Esc键退出到一般模式
3) x 删除当前光标所在字符,X删除当前光标前的一个字符
4) A 在一行的末尾添加text,a在当前光标后添加text
5) u 执行Undo操作,Ctrl+R表示Redo
6) 0 跳到行首,$ 跳到行尾 ^跳到行首(类似正则式$和^的意义)
7) w b e移动一个单词word(全部是字符或者符号)
8) W B E移动一个单词WORD(以空格隔开)
9) R 进入插入编辑模式,并且对被编辑位置进行覆盖
10) :w 保存 :q 退出 :q! 强制退出

步骤2
注释2:
1) f 移动光标从当前位置到下一个f后跟的字符的右边,包含此指定字符,F 方向相反为前一个,需要和操作(operator)配合操作
2) t 和 T类似f 和 F,只是它们一直到指定的字符左才停,即不包含指定的字符。
3) d 删除操作 与上面 w f t 等配合:例如”dw”删除下一word,”df-”从当前位置一直往前删除到字符”-”,不保留”-”,”dt-”删除直到”-”但是保留”-”
4) c 删除且进入编辑模式(类似d和i的结合),同样可以与f t T w 等motion结合
5) j k 可以和c和d结合,删除所有末尾或开头的所有行
6) . 可以重复最后一次编辑的所有操作,注意是在一般模式下重复最后一次编辑操作
7) 操作和motion可以和数字组合,”d2w” 删除后2个单词word “d2t,”删除知道这一行的第2个”,”
8) cc 和 dd 删除当前行
9) v 进入可视模式

步骤3
注释3:
1) y 后面跟任意的motion执行copy操作
2) p (paster)粘贴,如果复制的是字符形式,则粘贴到右边,行形式粘贴到下面
3) P 同上p操作,但是粘贴方向相反,在左边或上面
4) yy 复制当前行
5) y 也可以在可视模式(visual mode)下工作,同时,d,c,x..等text删除操作也将被删除内容进行复制(这一点要注意)
6) “和a-z字符如果在复制/删除/粘贴命令前表示选择一个寄存器暂存
7) 在复制/删除前的A-Z 寄存器意味进行叠加复制,就是多个复制操作的内容将被加起来
8) “*操作或者”+操作选择系统的剪贴板
9) o 在当前行下新其一行进入插入模式,O 操作类似o,只是在当前行的上新起一行

步骤4
注释4:
1) / 是基本的查找motion,可以和operation结合执行操作,也可以单独使用,直接查找匹配后面的正则式
2) ? 与/相同,但是方向相反backward,/ 方向forward
3) n 重复最后一次查找方向, N 重复最后一次查找的相反方向
4) * 和 # 查找当前光标所在的实体相同的实体,前者方向向前,后者方向向后,两者仅在vim下有效(vi not support)

步骤5
注释5:
1) m 操作后跟a-z字符用来设置一个标记mark
2) ` 后面跟a-z字符可以去这个字符表示的标记处
3) ‘ 和一个字符可以到所在行的第一个非空处
4) A-Z 标记为全局标记 a-z仅在每个buffer内可见
5) `. 表示到最后一个修改的地方
6) q 后跟字符a-z用来记录宏
7) @ 后跟字符用了重放宏,@@ 重放最后一个宏

步骤6
注释6:
1) % 在配对的( 和 ) [ 和 ]等 之间跳动
2) H M L 直接跳到整个屏幕的最上面,中间和最下面
3) G 跳到文件的末尾,G 前面跟行号表明跳到指定的行
4) - 或者 + 直接跳到前一行 或 后一行
5) K 跳到帮助
6) ( 和 )跳到当前句子的最前和最后
7) { 和 }跳到前一个空行 或 后一个空行
8) [[ 跳到前一个第0列是{的位置(必须{为第0列)
9) ]] 跳到下一个第0列是}的位置

步骤7
注释7:
1) J 连接当前行和下一行在一般模式,或所有行在可是模式
2) r 后跟任意字符替换当前字符
3) C 为c$的简写,表示删除当前位置到行结束并进入插入模式
4) D 为d$的简写,表示删除当前到行结束
5) Y 为yy的简写,复制整个行
6) s 删除当前光标所在字符并进入插入模式
7) S 清除当前行,并进入插入模式
8) > 和一个motion 表示缩进一行或多行
9) < 和一个motion 表示不缩进
10) = 和一个motion 表示重新格式化text
11) > < 和 = 工作在可视模式下,且均可重复例如>>表示缩进整个当前行

为了父母, 你需要知道患了中风该怎么做?
Posted by dengwei
不怕一万,就怕万一! 这样的小常识,还是挺不错的, 大家看看吧!
了解知识,预防意外,有备无患!
为了父母,你需要知道患了中风,脑部的微血管,
会慢慢的破裂,遇到这种情形, 千万别慌,
患者无论在什么地方(不管是浴室、卧房或客厅),千万不可搬动他。
因为,如果移动,会加速微血管的破裂。
所以要先原地把患者扶起坐稳, 以防止再摔倒,这时才开始(放血)。
家中如有专为注射用的针,当然最好。
如果没有 ,就拿缝衣用的针,就在患者的十个手指头尖儿(没有固定穴道,大约距离手指甲一分之处刺上去,要刺出血来(万一血不出来,可用手挤),
等十个手指头都流出血来( 每指一 滴) ,大约几分钟之后,患者就会自然清醒。
如果嘴也歪了,就拉他的耳朵,把耳朵拉红,在两耳的耳垂儿的部位各刺两针,也各流两滴血,几分钟以后,嘴就恢复原状了。
等患者一切恢复正常感觉没有异状时再送医,就一定可以转危为安,否则,若急着抬上救护车送医,经一路的颠跛震动恐怕还没到医院,他脑部微血管,差不多已经都破裂了。万一能够吉人天相,保全老命,能象孙院长,容得勉强行动,那得要靠祖上积德了。
放血救命法, 是住在新竹的中医师夏伯挺先生说的。且是经自己亲身实验,敢说百分之百有效。
大概是民国六十八年一位在台中逢甲学院任教的教师,有天上午正在上课,一位老师 跑到他的教室上气不接下气的说∶刘老师快来 ,主任中风了;他立刻跑到三楼, 看到陈幅添主任,气色不正, 语意模糊,嘴也歪了,很明显的是中风了。立即请工读生到校门外的西药房,买来一支注射用的针头,就在陈主任十个手指头上直刺。等十个手指尖儿都见血了(豆粒似的一滴),大约几分钟以后,陈主任的气色就变过来了,两眼也有神了,只有嘴还歪着,他就拉搓陈主任的耳朵,使之充血,等把耳朵拉红,就在左右耳垂之处,各刺两 针,待两耳垂都流出两滴血来,奇迹就出现了,大约不到三五分钟,他的嘴形,恢复正常了,说话也清清楚楚了。让陈主任静坐一阵子,喝了一 杯热茶,才扶他下楼,开车送到惠华医院,打一罐点滴,休息了一夜就出院回学校上课了。一切照常工作,毫无后遗症。
反观一般脑中风患者,都是送医院治疗时,经过一路震荡血管急速破裂,以致多数患者一病不起,所以脑中 风,在死因排行榜上高居第二位,其最幸运者,也仅能保住老命,而落得终身残废。这是一个多么可怕的病症。如果大家都能记住这(放血救命) 的方法,立刻施救,在短短时间它能起死回生,而且保证百分之百的正常。
这个急救法,希望大家告诉大家。那脑中风,在死因排行榜上,就可以除名。
●阅后传知他人,功德无量●
辨识中风~很短
有个朋友瑛格莉,在一次烤肉聚会当中绊倒了,摔了一跤,旁边的朋友建议找医护人员,但她很确定自己没事,只是穿了新鞋被砖块绊了一下罢了。
瑛格莉还有点危危颤颤站立不稳的时候,朋友们帮她清洗干净,又为她承了一盘新的食物,然后她就跟着大家一起享受接下来的时光了。
瑛格莉的先生后来打电话通知大家,他的太太被送到医院,傍晚六点,瑛格莉就过世了,原因是她在烤肉聚餐的时候中风。
如果他们懂得辨识中风的症兆,瑛格莉现在也许还跟我们在一起。
有些人不会死,但结局是处于无助无望的景况中。
只需要花一分钟的时间读完这篇文章,神经科医师说,如果他能在三小时之内接触到中风患者,他就可以将中风的后果完全扭转过来。
诀窍就是辨识诊断出中风的问题,并让病患在三小时之内接受医疗,而这是很难的。
辨识中风
感谢上帝让我们记住STR三步骤,请阅读并学习 !
有时候中风的症兆很难辨认,不幸的是,缺乏警觉就会带来灾难。
身边的人辨认不出中风的征兆,中风患者就会造成严重的脑伤。
医生说,旁边的人只要问三个简单的问题,就可以辨识中风:
S :(smile) 要求患者笑一下
T :(talk)要求患者说一句简单的句子(要有条理,有连惯性)例如:今天天气晴朗。
R :(raise)要求患者举起双手
※注意:另外一项中风症兆是:要求患者伸出舌头,如果舌头是「弯曲」的,如果舌 头偏向一边,那也是中风的症兆。
*上面四个动作,患者如果有任何一个动作做不来,就要立刻打120!!!
并且把症状描述给接线员听。
心脏科医师说,收到这封电函的人,若能将它转寄给10 个人,就至少可以救一条命。
转自:http://blog.chinaunix.net/u/524/showart_317750.html
redhad下的openssl(安装和卸载)
Posted by dengwei
转至: http://blog.csdn.net/baitianhai/archive/2004/10/27/155461.aspx
最近在鼓捣redhat linux,想自己以源代码方式安装软件,不想用rpm方式安装。
首先从httpd开始,先卸载在安装倒是比较容易,不过后来像添加ssl功能,发现编译的时候需要用openssl的安装目录,本人比较愚笨,一顿好找也没有找到,于是就想把openssl也以源代码方式安装。先卸载,此时出现问题,系统好多东西依赖于openssl的库,我查了好多资料也没找到什么办法,于是我最后一狠心,用rpm -e –nodeps给卸载了,然后手动安装了openssl,然后重新启动,这下坏了,好多服务都起不来了,smb,ssh等等,图形模式也起不来了,我欲哭无泪。
因为我是在虚拟机上安装的,smb起不来了,我只能重新安装系统了。这次安装我大多数东西都没选择,一路安装完毕,结果在文本方式发现vi编辑没有颜色了,哎,也不知道是少装了那个东西弄得(各位谁知道麻烦告诉告诉我一下),只能按照猜测重新安装了又添加了一些东西。不过幸运的vi高亮显示功能又有了,遗憾的是具体是那个软件我还是不清楚。有了上次的教训我不敢轻易卸掉系统原来的openssl了,我从网上搜索到了一篇安装openssl的英文文章,地址在 http://www.devside.net/web/server/linux/openssl 我按照上面说的安装了zlib,openssl。步骤简介如下(怕以后忘了)
安装zlib
Home : http://www.gzip.org/zlib/
Package(linux source) : http://www.gzip.org/zlib/
Our Configuration
Install to : /usr/local
Module types : dynamically and staticly loaded modules, *.so and *.a
Build Instructions
zlib library files are placed into /usr/local/lib and zlib header files are placed into /usr/local/include, by default.
Build static libraries
…/zlib-1.2.1]# ./configure
…/zlib-1.2.1]# make test
…/zlib-1.2.1]# make install
Build shared libraries
…/zlib-1.2.1]# make clean
…/zlib-1.2.1]# ./configure –shared
…/zlib-1.2.1]# make test
…/zlib-1.2.1]# make install
…/zlib-1.2.1]# cp zutil.h /usr/local/include
…/zlib-1.2.1]# cp zutil.c /usr/local/include
/usr/local/lib should now contain…
libz.a
libz.so -> libz.so.1.2.1
libz.so.1 -> libz.so.1.2.1
libz.so.1.2.1
/usr/local/include should now contain…
zconf.h
zlib.h
zutil.h
[Optional] Instructions for non-standard placement of zlib
Create the directory that will contain zlib
…/zlib-1.2.1]# mkdir /usr/local/zlib
Follow the given procedure above, except
…/zlib-1.2.1]# ./configure –prefix=/usr/local/zlib
Update the Run-Time Linker
/etc/ld.so.cache will need to be updated with the new zlib shared lib: libz.so.1.2.1
For standard zlib installation…
Add /usr/local/lib to /etc/ld.so.conf, if specified path is not present
/etc]# ldconfig
If zlib was installed with a prefix…
Add /usr/local/zlib/lib to /etc/ld.so.conf
/etc]# ldconfig
安装openssl
Download
Home : http://www.openssl.org/
Package(source) : openssl-0.9.7d.tar.gz
Our Configuration
install to : /usr/local/ssl
module types : dynamically and staticly loaded modules, *.so *.a
Build Instructions
…/openssl-0.9.7d]# ./config
–prefix=/usr/local/ssl
[default location]
shared
[in addition to the usual static libraries, create shared libraries]
zlib-dynamic
[like "zlib", but has OpenSSL load the zlib library dynamically when needed]
…/openssl-0.9.7d]# ./config -t
[display guess on system made by ./config]
…/openssl-0.9.7d]# make
…/openssl-0.9.7d]# make test
…/openssl-0.9.7d]# make install
Update the Run-time Linker
ld.so.cache will need to be updated with the location of the new OpenSSL shared libs: libcrypto.so.0.9.7 and libssl.so.0.9.7
Sometimes it is sufficient to just add these two files to /lib, but we recommend you follow these instructions instead.
Edit /etc/ld.so.conf
Add /usr/local/ssl/lib to the bottom.
…]# ldconfig
Update the PATH
Edit /root/.bash_profile
Add /usr/local/ssl/bin to the PATH variable.
Re-login
Testing
…]# openssl version
Should display OpenSSL 0.9.7d 17 Mar 2004
If an older version is shown, your system contains a previously installed OpenSSL.
Repeate the steps in Update the PATH, except place the specified location at the start of the PATH variable.
[the older openssl, on most systems, is located under /usr/bin]
[the command 'which openssl' should display the path of the openssl that your system is using]
/usr/local/ssl/bin]# ./openssl version should display the correct version.
但是我最后没有得到想要的结果,系统原来的openssl还是没能卸载掉,我该怎么做那?我继续搜索资料,哈,幸运的我找了,在一个国内论坛上是这么说的
cd /usr/local/ssl/lib
ln -s libcrypto.so.0.9.7 libcrypto.so.2
ln -s libssl.so.0.9.7 libssl.so.2
//最后要刷新系统的动态连接库配置
echo /usr/local/ssl/lib >> /etc/ld.so.conf
ldconfig -v
这下子我豁然开朗,原来依赖的那2个文件是个软链接啊,我把它修改为我现在真正的openssl库文件不是就行了吗?于是一顿忙碌后,我终于执行了 rpm -e -nodeps ,然后重新启动系统,一路运行下去,全是绿灯。一时间感觉自己好幸福啊
为了这个问题我查了国内的几个比较大的unix/linux网站都没找到资料,不过从这里http://bbs.netbuddy.org/unix/737.html还是找到了(国外的E文大概意思能看懂,但是查找起来还是没找到,也不知道这方面好点的网站),
目前主流开发技术的分析和总结
Posted by dengwei
转至: www.csdn.net
主流的程序设计语言:C++、Delphi(ObjectPascal)、Java、C#
桌面应用程序框架:MFC、VCL、QT、JavaAWT\SWING、.Net
企业应用程序框架:WindowsDNA(ASP、COM、COM+)、J2EE、.NetFramework
开发工具:VisualBasic、Delphi、VisualC++、C++Builder、VisualC#
*程序设计语言:C++\Delphi(本来应该是ObjectPascal,但为了简单,我就语言和工具混为一谈吧)\Java\C#(虽然他刚刚推出,但因为微软为之倾注了大量心血,一定会成为一种重要的开发语言)
*桌面应用程序框架:MFC\VCL
*企业应用程序框架:WindowsDNA\J2EE\.Net
*COM技术:我单独提出这项技术,是因为它无法简单的被视为语言、桌面应用程序框架或企业应用程序框架,它与这些都有关系。
2.1 程序设计语言
2.1.1 C++语言的演进
最初要从二进制代码和汇编说起,但那太遥远了。我们就从面向过程的语言说起吧(包括Basic\C\Fortran\Pascal)。这种面向过程的高 级语言终于把计算机带入了寻常的应用领域。其中的C语言因为它的简单和灵活造就了Unix和Windows这样的伟大的软件。
面向对象 的语言是计算机语言的一个合乎逻辑的进化,因为在没有过多的影响效率、简单性的前提下提供了一种更好的组织数据的方法,可使程序更容易理解,更容易管理 ——这一点可能会引出不同意见,但事实胜于雄辩,C++终于让C语言的领地越来越小,当今还活着的计算机语言或多或少的都具备面向对象的特征,所以这一点 并不会引起太多困惑。C++的成功很大程度要归因于C,C++成为它今天的样子是合乎逻辑的产物。因为在面向过程的时代,C几乎已经统一天下了。今天著名 的语言象Java\C#都从C借鉴了很多东西,C#本来的意思就是C++++。其实C++曾经很有理由统一面向对象程序设计语言的天下来着,但可惜的是, C++太复杂了。即使是一个熟练的程序员,要你很清楚的解释一些问题你也会很头痛。举几个还不是那么复杂的例子来说:
对=的重载\成员转换函数\拷贝构造函数\转化构造函数之间有什么区别和联系呢?
定义一个类成员函数private:virtualvoidMemFun()=0;是什么意义呢?
int(*(*x(int))[4])(double);是什么意思?
还有其他的特征,比如说可以用来制造一种新语言的typedef和宏(虽然宏不是C++的一部分,但它与C++的关系实在太密切了),让你一不小心就摔 跤的内存问题(只要new和delete就可以了吗?有没有考虑一个对象存放在容器中的情况?)……诸如此类,C++是如此的复杂以至于要学会它就需要很 长的时间,而且你会发现即使你用C++已经好几年了,你还会发现经常有新东西可学。你想解决一个应用领域的问题——比如说从数据库里面查询数据、更改数据 那样的问题,可是你却需要首先为C++头痛一阵子才可以,是的,你精通C++,你可以很容易的回答我的问题,可是你有没有想过你付出了多大的代价呢?我不 是想过分的谴责C++,我本人喜欢C++,我甚至建议一个认真的开发普通的应用系统的程序员也去学习一下C++,C++中的一些特性,比如说指针运算\模 板\STL几乎让人爱不释手,宏可以用几个字符代替很多代码,对系统级的程序员来说,C++的地位是不可替代的,Java的虚拟机就是C++写的。C++ 还将继续存在而且有旺盛的生命力。
2.1.2 Java和C#
Java和C#相对于C++的不同最大的有两点:第一点是他们运 行在一个虚拟环境之中,第二点是语法简单。对于开发人员而言,在语法和语言机制的角度可以把Java和C#视为同一种语言。C#更多的是个政治的产物而不 是技术产物。如果不是Sun为难微软的话,我想微软不会费尽心力推出一个和Java差不多的C++++,记得Visual J++吗,记得WFC吗?看看那些东西就会知道微软为Java曾经倾注了多少心血。而且从更广泛的角度来说,两者也是非常相似的——C#和Java面对的 是同样的问题,面向应用领域的问题:事务处理、远程访问、Webservice、Web页面发布、图形界面。那么在这一段中,我暂且用Java这个名字指 代Java和C#两种语言——尽管两者在细节上确实有区别。Java是适合解决应用领域的问题的语言。最大的原因Java对于使用者来说非常简单。想想你 学会并且能够使用Java需要多长时间,学会并且能够使用C++要多长时间。由于Java很大程度上屏蔽了内存管理问题,而且没有那么多为了微小的性能提 升定义的特殊的内容(比如说,在Java里面没有virtual这个关键字,Java也不允许你直接在栈上创建对象,Java明确的区分bool和整型变 量),他让你尽量一致的方式操作所有的东西,除了基本数据类型,所有的东西都是对象,你必须通过引用来操作他们;除了这些之外,Java还提供了丰富的类 库帮助你解决应用问题——因为它是面向应用的语言,它为你提供了多线程标准、JDBC标准、GUI标准,而这些标准在C++中是不存在的,因为C++并不 是直接面向解决应用问题的用户,有人试图在C++中加入这些内容,但并不成功,因为C++本身太复杂了,用这种复杂的语言来实现这种复杂的应用程序框架本 身就是一件艰难的事情,稍后我们会提到这种尝试——COM技术。渐渐的,人们不会再用C++开发应用领域的软件,象MFC\QT\COM这一类的东西最终 也将退出历史舞台。
2.1.3 Delphi
Delphi是从用C++开发应用系统转向用Java开发应用系统的一 个中间产物。它比C++简单,简单的几乎象Java一样,因为它的简单,定义和使用丰富的类库成为可能,而且Delphi也这么做了,结果就是VCL和其 他的组件库。而另一方面,它又比运行于虚拟环境的Java效率要高一些,这样在简单性和效率的平衡之中,Delphi找到了自己的生存空间。而且预计在未 来的一段时间之内,这个生存空间将仍然是存在的。可以明显的看出,微软放弃了这个领域,他专注于两方面:系统语言C++和未来的Java(其实是. Net)。也许这对于Borland来说,是一件很幸运的事情。如果我能够给Borland提一些建议的话,那就是不要把Delphi弄得越来越复杂,如 果那样,就是把自己的用户赶到了C++或Java的领地。在虚拟机没有最终占领所有的应用程序开发领域之前,Delphi和Delphi的用户仍然会生存 得很好。
2.2桌面应用程序框架
目前真正成功的桌面应用程序框架只有两个,一个是MFC,一个是VCL,还有一些其他的,但事实上并未进入应用领域。遗憾的是我对两个桌面应用程序框架都不精通。但这不妨碍我对他做出正确的评价。
2.2.1MFC
MFC(还有曾经的OWL)是SDK编程的正常演化的结果,就象是C++是C的演化结果一样。MFC本身是一件了不起但不那么成功的作品,而且它过时 了。这就是我的结论。MFC凝聚了很多天才的智慧——当然,OWL和VCL也一样,侯捷的《深入浅出MFC》把这些智慧摆在了我们的面前。但是这件东西用 起来估计不会有人觉得很舒服,如果你一直在用Java、VB或者Delphi,再回过头来用MFC,不舒服的感觉会更加强烈。我不能够解释MFC为什么没 有能够最终发展成和VCL一样简单好用的桌面程序框架,也许是微软没精力或者没动力,总之MFC就是那个样子了,而且也不会再有发展,它已经被抛弃了。我 有时候想,也许基于C++这种复杂的语言开发MFC这样的东西本身就是错误的——可以开发这样的一个框架,但不应当要求使用它的人熟悉了整个框架之后才能 够使用这个系统,但很显然,如果你不了解MFC的内部机制,是不太可能把它用好的,我不能解释清楚为什么会出现这种现象。
2.2.2VCL
相比之下VCL要成功的得多。我相信很多使用VCL的人可能没有像MFC的用户研究MFC那样费劲的研究过VCL的内部机制。但这不妨碍他们开发出好用 好看的应用程序,这就足够了,还有什么好说的呢?VCL给你提供了一种简单一致的机制,让你可以写出复杂的应用程序。在李维的Borland故事那篇文章 中曾经说过,在Borland C++ 3.1推出之后Borland就有人提出开发类似C++ Builder一类的软件,后来竟未成行。是啊,如果C++ Builder是在那个时候出现的,今天的软件开发领域将会是怎么样的世界呢?真的不能想象。也许再过一段时间,这些都将不再重要。因为新生的语言如 Java和C#都提供了类似于VCL的桌面应用程序框架。那个时候,加上Java和C#本身的简单性,如果他们的速度有足够块,连Delphi这种语言也 要消失了,还有什么好争论的呢?只是对于今天的桌面程序开发人员来说,VCL确实是最好的选择。
2.3 企业应用程序框架
2.3.1 Windows DNA
Windows DNA的起源无从探究了。随着.Net的推出,事实上Windows DNA将成为历史的陈迹。Windows DNA虽然是几乎所有的企业应用程序开发人员都知道的一个名词,但我相信Windows DNA事实上应用的最广泛的是ASP而不是COM+。真正的COM开发有多少人真正的掌握了呢,更不要提COM+(我有必要解释一下:COM+是COM的 执行环境,它提供了一系列如事务处理、安全等基础服务,让应用程序开发人员尽量少在基础架构设计上花精力)——当然我这里指的COM开发不是指VB下的 COM开发,所以要这么说,是因为我觉得如果不能理解用C++进行COM开发,也就不能真正的理解COM技术。如果以一种技术没有被广泛理解和应用作为失 败的标志,那么Windows DNA实际上是失败了,但这不是它本身的错,而是基于C++的COM技术的失败造成的。多层应用、系统开发角色分离的概念依然没有过时。
2.3.2 J2EE
J2EE是第一套成功的企业应用程序开发框架。因为它把事务处理、远程访问、安全这些概念带入了寻常百姓家。这种成功我认为要归因于Java的简单性。 Java的简单,对于J2EE容器供应商来说一样重要。开发一个基于Java的应用服务器要比基于C++的更容易。而且由于Java的简单性,应用系统开 发者出错的机会也会少一些,不会像C++的开发者那样受到那么多挫折。开发基于Java的企业应用系统的周期会更短,这恐怕是不容争辩的事实。不论如何, 是J2EE让事务处理、远程访问、安全这些原来几乎都是用在金融系统中的概念也被一般的企业用户使用并从中获得利益。
2.3.3 .NET
.Net有什么好说的呢?其实,它不过是微软的J2EE。事务处理、安全、远程访问,这些概念在.Net中都找得到。更有力的说明是,微软也利用了. Net实现了一个PetStore。所以,.Net与J2EE几乎是可以完全对等的。但微软确实是一家值得尊敬的公司——我指从技术上,象Web form这种东西,我相信很多Web应用开发者都梦想过甚至自己实现过,但Sun却一直无动于衷,而且似乎Borland也没有为此作过太多努力,好像有 过类似的东西,但肯定是不怎么成功——Borland已经很让人敬佩了,我们也许无法要求太多。
2.4 COM技术
COM应当 是个更广泛的词汇,其实我觉得Axtive X、OLE、Auto mation、COM+都应当提及,因为如果你不理解COM,上面的东西你是无法理解的。而且,我只是想说明COM是一种即将消亡的技术,仅仅说说COM 的复杂性和他的问题就够了,所以不会提及那些东西。为什么会出现COM技术?COM的根本目标是实现代码的对象化的二进制重用,进而实现软件的组件化、软 件开发工作的分工。这要求他做到两点:第一,能够跨越不同的语言,第二,要跨越同一种语言的不同编译器。COM技术为这个目标付出了沉重的代价,尤其是为 了跨越不同的编译器,以至于无论对于使用者而言还是开发者而言,他都是一个痛苦的技术。但幸运的事,这一切终归要结束了。
让我们从这个 目的出发看看COM为什么会成为它现在的样子。其实COM不是什么新玩意,最初的DLL就是重用二进制代码的技术。DLL在C的年代可能还不错,但到了C ++的年代就不行了。原因在于如果你在.h文件中改变了类定义(增加或者减少了成员变量),代码库用户的代码必须重新编译才可以,否则用户的代码会按你的 旧类的结构为你的新类分配内存,这将产生什么后果可想而知。这就是为什么通过接口继承和通过接口操作对象成为COM的强制规范的原因,能够通过对象的方式 组织代码是COM的重要努力。那么著名的IUnknown接口又是怎么回事呢?这是为了让使用不同编译器的C++开发人员能够共享劳动成果。
首先看QueryInterface,因为COM是基于接口的,那么一个类可能实现了几个接口,而对于用户来说,你又只能通过操作接口来操作类,这样你 必须把类造型成某个特定的接口,使用Dynamic_cast吗?不行,因为这是编译器相关的,那么,就只好把它放在类的实现代码中了,这就是 QueryInterface的由来。至于AddRef和Release,他们产生的第一个原因是delete这个操作要求一个类具有虚析构函数(否则的 话,他的父类的析构函数将不会被调用),但不幸的是不同的编译器中析构函数在vtbl中的位置并不相同,这就是说你不能让用户直接调用delete,那么 你的COM对象必须提供自己删除自己的方法;另外的原因在于一个COM对象可能作为几个接口在被用户同时使用,如果用户粗暴的删掉了某个接口,那么其他的 接口也就不能用了,这样,只好要求用户在想用一个接口的时候通过AddRef通知COM对象“我要使用你了,你多了一个用户”,而在不用之后要调用 Release告诉COM对象“我不用你了,你少了一个用户”,如果COM对象发现自己没有用户了,它就把自己删掉。
再看看诡异的 HRESULT,这是跨语言和跨编译器的代价。其实,异常处理是物竞天择的结果——连一直用效率作标榜的C++都肯牺牲效率来实现这个try- catch,可见它的意义,但COM为了照顾那些低级的语言居然抛弃了这个特征——产生的结果就是HRESULT。我们可以看看他是多么愚蠢的东西。首 先,我们要通过一个整数来传递错误信息,通过IErrorInfo来查找真正的错误描述,本来在现代语言中一个try-catch可以解决的问题,现在我 们却需要让用户和开发者都走很多路才能解决,你怎么评价这个结果?其次,由于这个返回计算结果的位置被占用了,我们不得不用怪异的out参数来返回结果。 想想一个简单的int add(intop1,intop2)在COM中竟然要变成HRESULT add(intop1,intop2,int* result),我不知道你对此作何感想。而且由于COM的方法无法返回一个对象而只能返回一个指针,为了完成一个简单的std::string GetName()这一类的操作,你要费多少周折——你需要先分配一块内存空间,然后在COM实现中把一个字符串拷贝到这个空间,用完了你要删掉这个空 间,不知道你是否觉得这种工作很幸福,反正我觉得很痛苦。还有COM为了照顾那些解释性的语言,又加入了Automation技术,你有没有为此觉得痛 苦?本来一个简单的方法调用,现在却需要传给你一个标志变量,然后让你根据这个标志变量去执行相应的操作。(这一点我现在仍然不明白,为什么解释性的语言 需要通过这个方式来执行一个方法)。“我受够了,我觉得头痛”,你会说,是啊,我想所有的人都受够了,所有这些因素实际上是把COM技术变成了一头让人无 法驾驭的怪兽。
人对复杂事物的掌控能力终究是有限的,C++本身已经够复杂了,COM的复杂性已经超出了我们大部分人的控制能力,你需 要忍受种种痛苦得到的东西与你付出的代价相比是不是太不值得了?我们学习是为了解决问题,而现在我们却需要为了学习这件事情本身耗费太多的精力。这些痛苦 的东西太多了,我在这里说到的,其实只是一小部分而已。计算机技术是为人类服务的,而不是少数人的游戏(我想COM技术可能是他的设计者和一部分技术作者 的游戏),难道我们愿意成为计算机的奴隶吗?通过一种过于复杂的技术抛弃所有的人其实等于被所有的人抛弃,这么多年中选择J2EE的人我相信不乏高手,你 是不是因为COM的过于复杂才选择J2EE的?因为它可以用简单的途径实现差不多的目标——软件的“二进制”重用、组件化、专业分工(容器开发商和应用开 发商的分工)。事实上,你是被微软所抛弃的,同时,你也抛弃了微软。
现在让我们回来吧,我把你带进了COM的迷宫,现在让我把你领回 来。再让我们看看COM到底想实现什么目标,其实很简单,不过是代码的二进制重用,也就是说你不必给别人源代码,而且你的组件可以象计算机硬件那样“即插 即用”。我们回过头来看看Java,其实,这种二进制重用的困难是C++所带来的(这不是C++本身的错,而是静态编译的错),当Java出现的时候,很 多问题已经不存在了。你不需要一个头文件,因为Java的字节码是自解释的,它说明了自己是什么样子的,可以做什么事情。不像C++那样需要一个头文件来 解释这些事情;也不需要事先了解对象的内存结构,因为内存是在运行的时候才分配的。如果我们现在再回过头来解决COM要解决的问题,你会怎么做呢?首先你 会不再选择C++这种语言来实现代码的“二进制”重用,其次,你会把所有的语言编译成同样的“二进制”代码(实际上,应当说是字节码),这显然是更聪明的 做法,从前COM试图在源代码的级别抹平二进制层次的差异,这实际上是让人在做本来应当由机器做的事情,很愚蠢是吗?但他们一直做了那么多年,而且把这个 痛苦带给了整个计算机世界——因为他们掌握着事实的标准,为了用户,为了利润,为了能够在Windows上运行,尽管你知道你在做着一个很不聪明的事情, 但你还是做了。
COM技术为了照顾VB这个小兄弟和实现统一二进制世界的野心,实在浪费了太多的精力。首先,落后的东西的消亡是必然 的,就象C、Pascal在应用领域的消亡一样,Basic一点一点的改良运动是不符合历史潮流的做法,先进的东西和落后的东西在一起,要么先进的东西被 落后的东西拖住后腿,要么是同化落后的东西,显然我们更愿意看见后者,现在Basic终于被现代的计算机语言同化了。其次,统一二进制世界好像不是那么简 单的事情,而且也没什么必要,微软的COM技术奋战了10年,现在也只有他自己和Borland支持,.Net终于放弃了这个野心。这一切终于要结束了。
过去J2EE高歌猛进地占领着应用开发的领地,COM在这种进攻面前多少显得苍白无力。现在微软终于也有了可以和J2EE一较长短的.NET,对于开发 人员来讲,基于字节码的组件的二进制重用现在是天经地义的事情;你不用再为了能够以类方式发布组件做那么多乱七八糟的事情,因为现在所有的东西本来就是类 了;实现软件开发的分工也很自然,你是专业人员,就用C#吧,你是应用开发人员,你喜欢用VB.Net,你就用吧,反正你们写的东西最终都被翻译成了一样 的语言(其实我觉得这一点意义不大,因为一些不那么好用的语言被淘汰是正常的事情,C风格成为程序设计语言的主流风格,肯定是有它的原因的,语言的统一其 实是大势所趋,就象中国人民都要说普通话一样,我觉得Java不支持多语言算不上缺点——本来就是一种很好看很好用的语言了,为什么要把简单问题复杂化 呢?)。COM不会在短期内死去,因为我估计微软还不会马上用C#开发Office和Windows的图形界面部分,但我相信COM技术退出历史舞台的日 子已经不远了,作为一般的开发人员,忘了这段不愉快的历史吧——你将不会在你的世界里直接和COM打交道。若干年以后,我想COM也许会成为一场笑话,用 来讽刺那种野心过大、钻牛角尖的愚蠢的聪明人。
设计模式之Life time controller模式
Posted by dengwei
(作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作)
目的
对象生命周期管理者模式可以被用来控制对象的整个生命周期,从对象被首次使用前创建它们到应用程序中止前完全的销毁它们。此外通过在应用启动/中止时进行对象自动的预先创建/销毁,使这个模式能够用来替代静态对象的创建/销毁。
例子
单例(singleton)是一种通用的创建模式,它对唯一的类实例提供了一个全局的访问点同时能够延迟实例的创建直到它首次被访问。如果一个单例在程序 的整个生命周期中没有被需要,它将不会被创建。单例模式并没有提及在什么时候它的实例应该被销毁这个问题,但对于特定的应用或操作系统这将是个问题。
为了说明为什么提及销毁语义是重要的,考虑下面的日志组件,它通过向客户提供编程API接口实现分布式的日志服务。
class 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、WINDOWS,在程序中止时它们会被自动清理。 不幸的是,在下面的环境中资源泄漏会导致麻烦:
(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 nex