心理学课上,周正教授正在授课:“上次下课时,一个男孩子递了张纸条:’我是个比较内向的人,又没什么特长,不会踢足球,不会打篮球……唯一的爱好是写作。进入大学后,看到周围的同学在交往、工作中左右逢源、如鱼得水、洒脱干练,很是羡慕。就要步入社会了,我该如何规划自己的前途呢?……周教授,我想我还是当一名作家比较好,一个人,也不错,您说呢?’这个问题要不要回答?”周教授扬了扬手中的纸条。
“回——答——”同学们兴致大起,“作家梦”可是不少惮于竞争之人的救命稻草啊!
周教授放下手中的纸条:“好,今天我就当面回答这位同学——我的态度,很简单:凡是做’作家梦’的人,都是逃避现实的、无能的人……”
话音未落,下面已是一片嘘声。
“我来问问你们,一支笔、一张纸的事,谁不会?当作家,就是这么简单,人人都会。我常说,一流人才在军界和商界,二流人才在政界,三流人才在学术界。对军人而言,你领十万人,我领十万人,没本事,死的十万就是你的。这里要的是综合素质,是挑战,所以军界的人是最强的。商界也是如此,投入两个亿,三个月后,可能家破人亡,你干不干?要的是同样的素质。政界就不同,他可以调整、迂回,政策不行可以再改,是有余地的,但要负责任。而学术界,一次不行两次,两次不行三次……永远不行都可以。军人和商人的成败一目了然,唯有作家可进可退,无所谓胜负成败……”有人若有所思地点头。
“某著名作家在一所大学做讲座的时候,有同学问他怎样才能当作家?他说:’首先养活你的家,再说当作家!’”周教授认真地看着大家,眼神里自然地流露出一种深切的关爱,“《论语》中记载:有一次孔子来到卫国,见卫国人很多,就说’庶矣哉’,意思是’卫国人多啊’。旁边有人问’既庶矣,又何加焉?’意思是’人多,怎么办呢?’——问题来了,有人,人多了,怎么办?我们该做点什么呢?你们认为孔子会怎么说?”
“教之——”大家很自信,大教育家嘛。
周教授微笑着摇摇头:“子曰:’富之。’——孔子说:’让他们富起来。’你们以为有了人就要教育,却不知道在教育之前,首先要让人富起来。旁人又问:’既富矣,又何加焉?’——’人们富足以后,又该如何?’”
“教之——”大家会心一笑。
“对,人富足了才有条件接受教育。吃不好穿不暖的时候,教育是句空话,况且对衣食无安的人大谈教育,这种行为本身就不厚道。孔子不愧是教育大家,他这’不富不教’的意义很深远!按照心理学家马斯洛的需求层次理论,人只有满足了基本的生理需求以后,才会去考虑安全、爱与被爱的需要,才会有自我实现的需要。”
“举例而言:勒紧腰带过日子的小两口,到了情人节,丈夫一咬牙,送爱妻一大束玫瑰,这时候妻子是什么感觉?”
“浪漫吧?”
“是浪费!”同学们争起来。
“还不如送我一双毛皮手套呢!你看看,这个冬天我的手又冻了,净花冤枉钱……”周教授开玩笑似的嗔怪道,大家在嬉笑中亦有所感悟。
“当人过日子都紧张的时候,是不会想着浪漫的,那是有钱人的享受。问你们一个常识:知道雄鸟追求雌鸟的时候,送给雌鸟的是什么?”
“虫子。”
“对啊,一送虫子,雌鸟就会意了:这家伙生存能力强,跟着它,今后我们的孩子不会挨饿。这是一个连鸟都明白的道理。”
下面安静极了,生怕漏掉周教授的任何一句话。
“’仓廪实而知礼节,衣食足而知荣辱。’自古以来,人们便说’饥寒起盗心’。能吃得好、穿得好,生活安定之后才能让晚辈过正常的生活。如果没有东西吃,连父母的东西也会抢过来吃,兄弟的就更不用提了。在人们陷入最差的生活状态时,就顾不得什么道义。这就是人类真正的本性。中国的先哲早在几千年以前,就已指出了人类的真实形貌。”
“在衣食无法获得满足时,依然能保持礼节,这是凡人做不到的事情。”
“如果希望这种兽性不要出现,期待我们最理想的人性流露,也为了维持社会秩序,提高道义,彼此能懂得礼貌,并以此幸福生活,就必须确保每个人都能有自己的收入。换句话说,要让大家能赚到钱。基于义务,我们必须要赚钱才行。”
必须赚钱!——看来,所谓“以人为本”,我们并不比古人懂得多。
“雄性丧失了生存能力就丧失了天赋雄性之本性。历史表明,男人的不幸、民族的不幸源于贫穷。所谓’贫病交加’、’穷凶极恶’……因此,你们必须认识到:挣钱是公德,要重视金钱。我这样告诉你们:男孩子,你可以不会踢足球,不会打篮球,可以不会作诗,不会弹钢琴,不会做饭。可以什么都不会,但是必须会挣钱。”
周教授的话字字珠玑、鞭辟入里。我感觉很多男孩子的眼睛在放光,不知他们看到的是压力还是希望?
“最后,再给你们一个例子,你们用心思考。比如说,快到春节了,太太说:’该过春节了,咱爸咱妈想来深圳这边,看看咱们和小外孙。’她先生立刻就说:’来啥来?根本不用来!咱已经忙得够呛了,再让他们过来,净添乱!再说,这路上,老年人多不方便!’这个男人现在是什么状态?”
“气急败坏!”有人笑着回答。
“记住,凡是气急败坏的男人都是穷男人。但是另外一家,太太说:’老公啊,快过春节了,老人们都想过来看看咱们,一年没见面了!’先生说:’哦,好啊好啊,应该让他们过来。这样,你让他们坐飞机过来。’这个男人就不气急败坏,他很平静。’还有,你看,咱家的房子,这三层七八间,冷冷清清的,孩子也没有人陪着玩。爷爷奶奶来了,或者姥姥姥爷来了,家里有生气,过年过节的有生气多好!’他为什么这样说?因为这个男人有钱,他不怕,他有地方住,有钱让父母过来。他有办法显示他的孝心,而且这种男人往往不会发脾气。因为他有很大的控制权,有很好的基础,任何事情到他这里都可以化解,可以平静对待。然后,一家人高高兴兴地过了春节。老人走的时候,先生问太太:’爸妈他们有什么要求没有?’’没有没有,他们都很高兴,一点要求都没有。’先生说:’我听见了,他们说老三要结婚,没房子住,他们想空出房子去住老房。这怎么可以呢?这样,在老家花6万块钱,我们出3万,三弟拿3万,盖一栋两层小楼让他们住,爸妈就不用动了。’听了这话,太太抱着丈夫说不出话来,这样的老公哪个太太不爱不感激呢?好,房子盖成了,弟弟说姐夫是好人,全村羡慕,父母开心,一家生活幸福。3万块钱,只是他一个月的工资,他愿意拿出这3万块钱。”
周教授最后说:“愿意做哪一个,你们自己选择。但是,要记住:知识不一定会带来金钱,挣钱靠的是能力。”
我一直以都对阅读很感兴趣,特别是计算机、哲学、医学、宗教这四类,每每手上拿到一本书后都会迫不急待的去阅读。
检验真理的标准就是实践。
我是一个反主流的人,正如同事 Dassion 说我一样,我是一个逆向思考的人……任何一件事物,当别人从一面评价它时,我都会先从它的另一面来评价。或者可以说我这是不成熟的表现?青春末期的回光返照,再次逆反一下?
我不这么认为,当大家都说某件事物好的时候,你要学会从它的另一面来看待它,找出它的不足,不能盲目的追捧于某些人或者名人的言论,伟大的毛主席还有出错的时候,何况我们“凡“人?况且,之前大家不是都学过“两点论”、“两分法”么?
比如现在流行的 WEB2.0 ,提到这个词我就反胃,你看到 Flickr 还是 Delicious 成天在页面上展示这个词语了?你看到他们成天在说 UE 了?有句话如果我没记错的话是“行,甚于言”不要在那里叫唤,去做!就像我们老大常说的,“做好自己应该做的”。就算我是WEB1.0 就算我没有 UE,就算我不按 W3C标准设计页面,我全用 TABLE,只要我的服务是一部分用户需要的,并且我专注于如何改善我的服务,那么这就是好的。以用户为中心不是喊出来的,就像喊了十多年的“客户就是上帝”,结果呢?……
说远了,正因为我对以上主流词汇的反感,所以我更需要去了解它,不了解就没有发言权。如果它真是好的,精华的东西,那么我以前的确是错了,纠正了我的错误认知。如果在我深入了解它的时候更加印证了它是错误的,那么我会对它的错误有一个更好的理论上的认识。
为了更好的了解一些内容我买了几本书,其中一本是<Don’t Make Me Think>。
昨天晚上下班后,在和 GavinKwoe 聊了一些有关 Design Patterns 的话题后,我就开始读起了这本几乎是 UE Bible 的书。其中一些内容的确不错,但是在我读完之后有一个问题一直困绕着我。
在第5章,书中说要省略到不必要的文字,比如口号、欢迎词之类的。而在第7章,书里又说要有站点标识和使命,要有导读。而在我理解中,使命不就是口号?而导读不就是欢迎词?大家可以看到他在第7章举的 essential.com 的例子(第73页),君不见那大大的“Welcome to xxxx”,所以我想我认为这是欢迎词也无可厚非吧?
在用大约三、四个小时(包括思考)的时候读完“第一遍”的时候,我发现我以前对于一些可用性的理解和 UE 工程师的认知是有错误的,可用性和 UE 在我们的工作中的确起到了作用,但是没且现在网上吹捧得那么重。反过来思考为什么会有这么多人,特别是从事这个行业的人在吹捧?我的猜测是,把这个行业说得越重要,那么就表示他们在工作中越重要,相应的薪水就越多。
为什么说程序员是IT民工?我想可能是众位都是和我一样不是特别会表达自己的想法、观点,以致于让某些人错认为 coding 不过是“体力"劳动,而非“脑力”劳动。我们这些 Programmer 不是没有想法,相反,因为我们了解技术,所以我们的想法更多。但是正因为我们了解技术,所以我们清楚的认识到,哪些可以做,哪些不可以做,哪些好做,哪些不好做等等,而非产品人员的空中楼阁。大部分人认为用户体验仅仅需要考虑用户界面和操作过程而以,而我认为周边情况也属入用户体验的一部分。比如程序员一直考虑,而设计和产品人员从不关心的程序执行效率问题,单一来看,程序的执行效率和用户体验没有多大关系,但是如果你让程序的每一步操作都执行十几分钟才反回结果或者有反应,那么你认为用户会是什么感觉?有人说 Vista 效果好,用户体验好,但是你不想想,对于配置一般的计算机用户来说,为了用 Vista 不少人得必须花银子去升级硬件,这样才能更好的使用它,这对用户来说是一个好的体验么?花钱买你的产品,结果确是需要再花更多的钱以保证我能用上你这个产品。
如果您能读到这里,可能您会认为我的想法比较偏激,事实上我仅仅对一些问题用不同的角度去思考而以,而一些网上这些吹捧的人,或许仅仅是在跟风,他们并没有一个清楚的认识,我们为什么要喜欢、不喜欢它。就像我以前举的例子,小学的课文中有的文章里会有错别字,我问老师这个字是不是错了,结果老师说这是通假字。而当我在写作文用到的通假字时,确常常被老师批评。
我的语文一向不好,简单的总结上面的内容就是,希望大家不要盲从,当大部分人都说一样东西好时,你要从它另一面,不好的那面去思考,为什么它好、不好。正如反证法一般,当你无法找到足够的证据来证明它是错误的,那么就证明这样东西是好的、对的、正确的。
[请大家对上面这些文字中的“通假字”,不要过于计较,毕竟高考作文都忘了写标题的人能写出这些是多么的不容易]
META标签是HTML语言HEAD区的一个辅助性标签,它位于HTML文档头部的<HEAD>标记和<TITLE>标记之间,它提供用户不可见的信息。meta标签通常用来为搜索引擎robots定义页面主题,或者是定义用户浏览器上的cookie;它可以用于鉴别作者,设定页面格式,标注内容提要和关键字;还可以设置页面使其可以根据你定义的时间间隔刷新自己,以及设置RASC内容等级,等等。
下面介绍一些有关 标记的例子及解释。
META标签分两大部分:HTTP标题信息(HTTP-EQUIV)和页面描述信息(NAME)。
※ HTTP-EQUIV
HTTP-EQUIV类似于HTTP的头部协议,它回应给浏览器一些有用的信息,以帮助正确和精确地显示网页内容。常用的HTTP-EQUIV类型有:
1、Content-Type和Content-Language (显示字符集的设定)
说明:设定页面使用的字符集,用以说明主页制作所使用的文字已经语言,浏览器会根据此来调用相应的字符集显示page内容。
用法:<Meta http-equiv="Content-Type" Content="text/html; Charset=gb2312">
<Meta http-equiv="Content-Language" Content="zh-CN">
注意: 该META标签定义了HTML页面所使用的字符集为GB2132,就是国标汉字码。如果将其中的“charset=GB2312”替换成“BIG5”,则该页面所用的字符集就是繁体中文Big5码。当你浏览一些国外的站点时,IE浏览器会提示你要正确显示该页面需要下载xx语支持。这个功能就是通过读取HTML页面META标签的Content-Type属性而得知需要使用哪种字符集显示该页面的。如果系统里没有装相应的字符集,则IE就提示下载。其他的语言也对应不同的charset,比如日文的字符集是“iso-2022-jp ”,韩文的是“ks_c_5601”。
Content-Type的Content还可以是:text/xml等文档类型;
Charset选项:ISO-8859-1(英文)、BIG5、UTF-8、SHIFT-Jis、Euc、Koi8-2、us-ascii, x-mac-roman, iso-8859-2, x-mac-ce, iso-2022-jp, x-sjis, x-euc-jp,euc-kr, iso-2022-kr, gb2312, gb_2312-80, x-euc-tw, x-cns11643-1,x-cns11643-2等字符集;Content-Language的Content还可以是:EN、FR等语言代码。
2、Refresh (刷新)
说明:让网页多长时间(秒)刷新自己,或在多长时间后让网页自动链接到其它网页。
用法:<Meta http-equiv="Refresh" Content="30">
<Meta http-equiv="Refresh" Content="5; Url=http://www.xia8.net">
注意:其中的5是指停留5秒钟后自动刷新到URL网址。
3、Expires (期限)
说明:指定网页在缓存中的过期时间,一旦网页过期,必须到服务器上重新调阅。
用法:<Meta http-equiv="Expires" Content="0">
<Meta http-equiv="Expires" Content="Wed, 26 Feb 1997 08:21:57 GMT">
注意:必须使用GMT的时间格式,或直接设为0(数字表示多少时间后过期)。
4、Pragma (cach模式)
说明:禁止浏览器从本地机的缓存中调阅页面内容。
用法:<Meta http-equiv="Pragma" Content="No-cach">
注意:网页不保存在缓存中,每次访问都刷新页面。这样设定,访问者将无法脱机浏览。
5、Set-Cookie (cookie设定)
说明:浏览器访问某个页面时会将它存在缓存中,下次再次访问时就可从缓存中读取,以提高速度。
当你希望访问者每次都刷新你广告的图标,或每次都刷新你的计数器,就要禁用缓存了。通常HTML文件没有必要禁用缓存,对于ASP等页面,就可以使用禁用缓存,因为每次看到的页面都是在服务器动态生成的,缓存就失去意义。如果网页过期,那么存盘的cookie将被删除。
用法:<Meta http-equiv="Set-Cookie" Content="cookievalue=xxx; expires=Wednesday, 21-Oct-98 16:14:21 GMT; path=/">
注意:必须使用GMT的时间格式。
6、Window-target (显示窗口的设定)
说明:强制页面在当前窗口以独立页面显示。
用法:<Meta http-equiv="Widow-target" Content="_top">
注意:这个属性是用来防止别人在框架里调用你的页面。Content选项:_blank、_top、_self、_parent。
7、Pics-label (网页RSAC等级评定)
说明:在IE的Internet选项中有一项内容设置,可以防止浏览一些受限制的网站,而网站的限制级别就是通过该参数来设置的。
用法:<META http-equiv="Pics-label" Contect="(PICS-1.1′http://www.rsac.org/ratingsv01.html’ I gen comment ‘RSACi North America Sever’ by ‘inet@microsoft.com’ for ‘http://www.microsoft.com’ on ‘1997.06.30T14:21-0500′ r(n0 s0 v0 l0))">
注意:不要将级别设置的太高。RSAC的评估系统提供了一种用来评价Web站点内容的标准。用户可以设置Microsoft Internet Explorer(IE3.0以上)来排除包含有色情和暴力内容的站点。上面这个例子中的HTML取自Microsoft的主页。代码中的(n 0 s 0 v 0 l 0)表示该站点不包含不健康内容。级别的评定是由RSAC,即美国娱乐委员会的评级机构评定的,如果你想进一步了解RSAC评估系统的等级内容,或者你需要评价自己的网站,可以访问RSAC的站点:http://www.rsac.org/。
8、Page-Enter、Page-Exit (进入与退出)
说明:这个是页面被载入和调出时的一些特效。
用法:<Meta http-equiv="Page-Enter" Content="blendTrans(Duration=0.5)">
<Meta http-equiv="Page-Exit" Content="blendTrans(Duration=0.5)">
注意:blendTrans是动态滤镜的一种,产生渐隐效果。另一种动态滤镜RevealTrans也可以用于页面进入与退出效果:
<Meta http-equiv="Page-Enter" Content="revealTrans(duration=x,transition=y)">
<Meta http-equiv="Page-Exit" Content="revealTrans(duration=x,ransition=y)">
Duration 表示滤镜特效的持续时间(单位:秒)
Transition 滤镜类型。表示使用哪种特效,取值为0-23。
0 矩形缩小 1 矩形扩大
2 圆形缩小 3 圆形扩大
4 下到上刷新 5 上到下刷新
6 左到右刷新 7 右到左刷新
8 竖百叶窗 9 横百叶窗
10 错位横百叶窗 11 错位竖百叶窗
12 点扩散 13 左右到中间刷新
14 中间到左右刷新 15 中间到上下
16 上下到中间 17 右下到左上
18 右上到左下 19 左上到右下
20 左下到右上 21 横条
22 竖条 23 以上22种随机选择一种
9、MSThemeCompatible (XP主题)
说明:是否在IE中关闭 xp 的主题
用法:<Meta http-equiv="MSThemeCompatible" Content="Yes">
注意:关闭 xp 的蓝色立体按钮系统显示样式,从而和win2k 很象。
10、IE6 (页面生成器)
说明:页面生成器generator,是ie6
用法:<Meta http-equiv="IE6" Content="Generator">
注意:用什么东西做的,类似商品出厂厂商。
11、Content-Script-Type (脚本相关)
说明:这是近来W3C的规范,指明页面中脚本的类型。
用法:<Meta http-equiv="Content-Script-Type" Content="text/javascript">
注意:
※NAME变量
name是描述网页的,对应于Content(网页内容),以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)。
name的value值(name="")指定所提供信息的类型。有些值是已经定义好的。例如description(说明)、keyword(关键字)、refresh(刷新)等。还可以指定其他任意值,如:creationdate(创建日期) 、document ID(文档编号)和level(等级)等。
name的content指定实际内容。如:如果指定level(等级)为value(值),则Content可能是beginner(初级)、intermediate(中级)、advanced(高级)。
1、Keywords (关键字)
说明:为搜索引擎提供的关键字列表
用法:<Meta name="Keywords" Content="关键词1,关键词2,关键词3,关键词4,……">
注意:各关键词间用英文逗号“,”隔开。META的通常用处是指定搜索引擎用来提高搜索质量的关键词。当数个META元素提供文档语言从属信息时,搜索引擎会使用lang特性来过滤并通过用户的语言优先参照来显示搜索结果。例如:
<Meta name="Kyewords" Lang="EN" Content="vacation,greece,sunshine">
<Meta name="Kyewords" Lang="FR" Content="vacances,grè:ce,soleil">
2、Description (简介)
说明:Description用来告诉搜索引擎你的网站主要内容。
用法:<Meta name="Description" Content="你网页的简述">
注意:
3、Robots (机器人向导)
说明:Robots用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引。Content的参数有all、none、index、noindex、follow、nofollow。默认是all。
用法:<Meta name="Robots" Content="All|None|Index|Noindex|Follow|Nofollow">
注意:许多搜索引擎都通过放出robot/spider搜索来登录网站,这些robot/spider就要用到meta元素的一些特性来决定怎样登录。
all:文件将被检索,且页面上的链接可以被查询;
none:文件将不被检索,且页面上的链接不可以被查询;(和 "noindex, no follow" 起相同作用)
index:文件将被检索;(让robot/spider登录)
follow:页面上的链接可以被查询;
noindex:文件将不被检索,但页面上的链接可以被查询;(不让robot/spider登录)
nofollow:文件将不被检索,页面上的链接可以被查询。(不让robot/spider顺着此页的连接往下探找)
4、Author (作者)
说明:标注网页的作者或制作组
用法:<Meta name="Author" Content="张三,abc@sohu.com">
注意:Content可以是:你或你的制作组的名字,或Email
5、Copyright (版权)
说明:标注版权
用法:<Meta name="Copyright" Content="本页版权归Zerospace所有。All Rights Reserved">
注意:
6、Generator (编辑器)
说明:编辑器的说明
用法:<Meta name="Generator" Content="PCDATA|FrontPage|">
注意:Content="你所用编辑器"
7、revisit-after (重访)
说明:
用法:<META name="revisit-after" CONTENT="7 days" >
注意:
※Head中的其它一些用法
1、scheme (方案)
说明:scheme can be used when name is used to specify how the value of content should
be interpreted.
用法:<meta scheme="ISBN" name="identifier" content="0-14-043205-1" />
注意:
2、Link (链接)
说明:链接到文件
用法:<Link href="soim.ico" rel="Shortcut Icon">
注意:很多网站如果你把她保存在收件夹中后,会发现它连带着一个小图标,如果再次点击进入之后还会发现地址栏中也有个小图标。现在只要在你的页头加上这段话,就能轻松实现这一功能。<LINK> 用来将目前文件与其它 URL 作连结,但不会有连结按钮,用於 <HEAD> 标记间, 格式如下:
<link href="URL" rel="relationship">
<link href="URL" rev="relationship">
3、Base (基链接)
说明:插入网页基链接属性
用法:<Base href="http://www.csdn.net/" target="_blank">
注意:你网页上的所有相对路径在链接时都将在前面加上“http://www.cn8cn.com/”。其中
target="_blank"是链接文件在新的窗口中打开,你可以做其他设置。将“_blank”改为“_parent”是链接文件将在当前窗口的父级窗口中打开;改为“_self”链接文件在当前窗口(帧)中打开;改为“_top”链接文件全屏显示。
<head>
<title>文件头,显示在浏览器标题区</title>
<meta http-equiv="Content-Language" content="zh-cn">
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 4.0">
<meta name="ProgId" content="FrontPage.Editor.Document">
<meta name="制作人" content="Simonzy">
<meta name="主题词" content="HTML 网页制作 C# .NET JavaScript JS">
</head>
以上是META标签的一些基本用法,其中最重要的就是:Keywords和Description的设定。为什么呢?道理很简单,这两个语句可以让搜索引擎能准确的发现你,吸引更多的人访问你的站点!根据现在流行搜索引擎(Google,Lycos,AltaVista等)的工作原理,搜索引擎先派机器人自动在WWW上搜索,当发现新的网站时,便于检索页面中的Keywords和Description,并将其加入到自己的数据库,然后再根据关键词的密度将网站排序。
Tags: html, java, javascript, web, 类, 缓存
1 一个人的改变,源自于自我的一种积极进取,而不是等待什么天赐良机。
这个正好跟李开复做最好的自己里面提倡的成功同心圆的看法一样,一个成功的人士,必须具备积极进取的精神,有时主动能得到比你想要的多的东西。
2 在最艰难的时刻,更要相信自己手中握有最好的猎枪。
相信自己是优秀的,所以任何时候要给自己鼓励,特别是在最艰难的时刻,尤其要相信自己手中握有最好的猎枪。
3 任何一次对自己的原谅,都会导致下一次更大的错误。
世上没有后悔药,就是这个意思, 在同一个地方决不能有两次错误,这就是道理。
4 当你开始学会把自己变成一种成功的资本时,你一定能发现其实成功并不像人们所想的那样艰难。
呵呵,也许吧,你要你努力,成功就在你眼前,至少我从我最近学习的 建模来看,你感觉别人很厉害,其实也只是相同的操作,重要在于你是否付出努力。当然也有你的天分在里面。
5 从早晨到傍晚,你反问自己一天究竟做了什么?或许对第二天有更多的触动。
看看自己的blog吧,如果没有,建议建一个,她能让你自省,思考,你可以很快的看看自己最近做了什么,是否有意义,是否是你想要的。当些blog成为一种习惯,那恭喜你,你做的事已经触动了你的心。
6 相信别人,放弃自己,这是许多人失败人生的开始!
当我意识到这一点的时候,我开始让自己变的跟别人同样优秀,这样就可以让自己更自信。
7 做事成功的要诀就如同钥匙开锁的道理一样,如果你不能准确对号,那么一定无法打开成功之门。
兴趣与爱好是指引人生唯一的道路,没必要跟随他人,踏着别人的脚印,迟早会迷失自我。
8 用自己的能力证明自己,胜过用空话吹嘘自己。
我正在这样做,我希望我能证明自己,至少自己承认自己。
9 不懂放弃,等于固执;不能坚持,等于放弃目标。最聪明的做法是:不该坚持的,必须放弃!
均衡这一点事很困难的,毕竟未来的道路谁也不知道,但是当你清醒了你的错误,你还在坚持,那就是愚蠢之极的做法,但我还是要说,好好把我自己的理想,不到万不得已,不要放弃。
10 任何道路都是靠自己走出来的,而不是靠自己在梦中等来的。
其中准确迈出第一脚,是尤为重要的。 梦想和理想还是有差距的,还是以行动来说明一切吧。
11 没有一种成功是可以轻松实现的,但是只有你敢于攀登你所选择的山顶,成功就会越来越靠近你。
还是那句话:只要你想,一切皆有可能。
12 当雄鹰在天空飞翔时,它告诉人们:勇气和胆量,眼光和行动,是最重要的成功元素。
我把他理解为执行力,这是开复先生里面的一个成功的元素。
13 不断反思自己的弱点,是让自己获得更好成功的优良习惯。但有些人总怕这一点,所以最终成为弱者。
还是建议写 blog,暴露你的弱点,然后通过自己的努力去改正他,你也可以成为强者。
14 学会下一次进步,是做大自己的有效法则。因此千万不要让自己睡在已有的成功温床上。
这是对我的告诫,我想起了前几天的一篇blog,在变化中成长,也许我们需要一些变化,让我们更富有激情。
15 对待金钱,既要像朋友,有要像陌生人。如果因金钱而折磨自己,人生就会狭隘,如果用一种“坦然”心态待之,那么你的人生本身就已经拥有金钱!
生命中有比金钱更重要的东西~
原文转自:http://zjj.anyp.cn/blog/archive/636/060304115737515.aspx
Tags: blog
本文介绍 Windows 套接字应用程序可用的各种类型的传输控制协议 (TCP) 和用户数据报协议 (UDP) 端口及其在 Windows XP 和 Windows Server 2003 中的范围。
TCP 和 UDP 使用端口号来标识源和目标应用程序。对于典型的客户-服务器协议(例如那些用于 Web 和电子邮件访问的协议),通信是由客户端计算机发起的。服务器应用程序通常监听众所周知的 TCP 或 UDP 端口,它们是由 Internet 号码分配机构 (IANA) 分配的。对于源端口,客户端应用程序通常查询操作系统中已不再由其他应用程序占用的动态分配的 TCP 或 UDP 端口。当应用程序请求而后绑定到一个动态分配的端口时,这就是通常所说的通配绑定。
动态分配的端口也称为临时端口。术语“临时”(短暂)并不表示端口的生存期一定很短。例如,用于超文本传输协议的端口在数据传输完成之后会立即关闭。临时是指,与通常在整个服务器计算机运行期间都保持打开的服务器应用程序端口相比,客户端应用程序端口的生存期相对较短(至多只在应用程序运行期间保持打开)。
客户端计算机使用临时端口而不是众所周知的端口,以防与可能使用众所周知端口的本地服务发生冲突。例如,运行 Microsoft Windows XP 的计算机可能会使用 Internet Explorer,还可能运行 Internet 信息服务 (IIS)。当 Internet Explorer 访问网页时,它无法将 TCP 端口 80 用作源端口,因为该本地端口可能已被 IIS 占用。如果两个应用程序都设计成独占使用同一端口,则一次只有其中一个应用程序能够成功运行。
在 Microsoft Windows XP 或 Windows Server 2003 中,由 Windows 套接字分配给应用程序的临时 TCP 或 UDP 端口号的最大值是由注册表设置 MaxUserPort 控制的,该参数的默认值为 5000。临时端口从端口号 1025 开始编号。因此,默认情况下,Windows XP 或 Windows Server 2003 会为执行通配绑定的应用程序分配一个范围从 1025 到 5000 的号码。
要在运行 Windows XP 或 Windows Server 2003 的计算机上更改临时端口的最大值,请执行以下操作:
|
1. |
单击开始,再单击运行,键入 regedit.exe,然后单击确定。 |
|
2. |
找到而后单击以下注册表子项: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters |
|
3. |
在编辑菜单上,指向新建,然后单击双字节值。 |
|
4. |
键入 MaxUserPort,然后按 ENTER。 |
|
5. |
双击 MaxUserPort 值,然后以十进制或十六进制键入最大值。 键入的数值必须在 5000¨C65534(十进制)之间。如果此参数设置的值超出有效范围,则使用最接近的有效值(5000 或 65534)。 |
|
6. |
单击确定。 |
|
7. |
退出注册表编辑器。 |
|
警告 如果错误使用注册表编辑器,可能会导致严重问题以至于需要您重新安装操作系统。Microsoft 无法保证您能够解决由于错误使用注册表编辑器而引起的问题。您必须自行承担使用注册表编辑器所带来的风险。 |
必须重新启动计算机,方可使 MaxUserPort 注册表设置更改生效。
如果应用程序使用通配绑定同时打开大量连接,可能只需更改这个值,而且需确保应用程序不会用尽可用的临时端口。例如,一个使用文件传输协议 (FTP) 传输大量小文件的数据备份应用程序就可能用尽临时端口。
通过端口保留,应用程序可以阻止一定范围内的端口在通配绑定期间被分配。然而,保留某一端口范围并不会阻止应用程序在保留的范围内执行特定的绑定(请求使用特定端口)。保留端口范围时,所选择的端口号连续范围必须是从 1025 到 MaxUserPort 设置值(默认值为 5000)或从 49152 到 65535。多个客户端应用程序可保留相同的范围。取消保留(删除保留)时,Windows 套接字会删除它找到的第一个完全包含在取消保留请求内的条目。
还可以执行以下操作,通过注册表来指定保留端口的范围:
|
1. |
单击开始,再单击运行,键入 regedit.exe,然后单击确定。 |
|
2. |
找到而后单击以下注册表子项: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters |
|
3. |
在编辑菜单上,指向新建,然后单击多字符串值。 |
|
4. |
键入 ReservedPorts,然后按 ENTER。 |
|
5. |
双击 ReservedPorts 值,使用以下语法键入端口范围:x-y 要指定单个端口,请对 x 和 y 使用相同的值。例如,要指定端口 4000,请键入 4000-4000。 |
|
6. |
单击确定。 |
|
7. |
退出注册表编辑器。 |
通过端口封锁,应用程序可以防止其他应用程序执行对指定范围内端口的特定绑定。封锁端口范围时,应用程序所选择的连续端口号范围必须介于 MaxUserPort 设置值(默认值为 5000)+ 1 与 49151(对于未安装服务包的 Windows XP 和 Windows Server 2003)或 65535(对于 Windows Server 2003 Service Pack 1)之间。不能存在到封锁端口范围内端口的现有绑定。Windows 套接字会以句柄形式返回封锁范围内的最后一个端口号。解除封锁(取消封锁)时,Windows 套接字会解除与解除封锁请求具有相同左边界的封锁范围。
对于运行未安装服务包的 Windows XP 或 Windows Server 2003 的计算机,下面给出了不同的端口范围:
| • |
众所周知的端口范围(由 IANA 保留):0 到 1023 |
| • |
临时端口范围(对于通配绑定):1025 到 MaxUserPort 注册表设置值 |
| • |
可用作特定端口(对于特定绑定):从 0 到 65535 的任何未封锁端口 |
| • |
保留端口的可用范围:1025 到 MaxUserPort 以及 49152 到 65535 |
| • |
封锁端口的可用范围:MaxUserPort + 1 到 49151(除非 MaxUserPort 的值设置为 49152 或更高,这种情况下没有可封锁端口范围) |
对于运行未安装服务包的 Windows XP 或 Windows Server 2003 的计算机,下图显示了不同的端口范围。
尽管将众所周知的端口范围、可保留的端口范围以及可封锁的端口范围分开是很有意义的,但有时需要将 MaxUserPort 设置为高于 49152 的值并且需要封锁端口。为了给端口请求提供服务并保持向后兼容,Windows Server 2003 Service Pack 1 (SP1) 允许在保留范围内封锁端口。因此,对于 Windows Server 2003 SP1,范围如下所示:
| • |
众所周知的端口范围(由 IANA 保留):0 到 1023 |
| • |
临时端口范围:1025 到 MaxUserPort 注册表设置值 |
| • |
可用作特定端口:从 0 到 65535 的任何未封锁端口 |
| • |
保留端口的可用范围:1025 到 MaxUserPort 以及 49152 到 65535 |
| • |
封锁端口的可用范围:MaxUserPort + 1 到 65535 |
对于运行带 SP1 的 Windows Server 2003 的计算机,下图显示了不同的端口范围。
Tags: class, server, web, windows, 类
不知道明年今日,会不会出现一队只会用Ruby On Rails的毕业生,像当年的asp、jsp、php迅速剿了C++/perl的CGI那样,把我们给剿了。同好们劝我,根据大公司经济学,这基本不会发生。
在茫茫的框架之海认出一个Rails框架,基本上靠四个特征
1.一门动态语言
2.一个extreme simple to use的ORM框架
3.一个extreme simple to use的MVC框架
4.一些自动生成代码的命令、模版
其余ajax、web service、i18n等特性自由扩展
1.Ruby: Ruby on Rails
Rails系的旗手,一己之力搞得J2EE阵营鸡飞蛋打。
旗手的作用表现在:
*最接近1.0的版本,目前已出到最后一个RC版 1.0 rc4(0.14.3)
*拥有一本amazon超级畅销的《Agile Web Development with Rails》,而且这本书的组织也好,part I是一个渐进的sample application 让你快速入门,partII是几个重要领域的深入介绍。
*拥有自己的IDE: RadRails ,基于Eclipse但独立成军,目前出到0.5。(是不是怕惨了Eclipse的多变,现在这些IDE的发行版本开始反包含了Eclipse在内。)
2.Python: DJango、turbogears
认识不深,所以DJango请看limodou的blog,turbogears看xlp123的。
3.Groovy: grails
如果能顺产,绝对是J2EE阵营里感情分最高涨的项目。因为它语言用Groovy,ORM用annotation版Hibernate,MVC用Spring。不用担心它换汤不换药,因为经过extreme的封装,再结合Groovy,绝对不再是原来的Spring+Hibernate,而是和RoR差不多的一样东西了。不信可以看看他的Sample。
不过这个项目的源码目录树非一般的乱,也还没有0.1版释出。还有一样奇怪的事情,这个Groovy项目,大多数的class代码都是Java写的。
4.Php: symfony ,cake
据说cake比symfony弱非常多,所以没看。
symfonys是基于php5的项目,成功整合了Propel(ORM)、Mojavi3(MVC),再配合自动生成的脚本打造而成。看他的sample,最后整合出来的东西也很Rails了,除了ORM层的xml文件。
还有一个发现是Php项目现在可以用pear来安装,很像Ruby的gem,Java的Maven要努力了。
C: />pear channel-discover pear.symfony-project.com
C: />pear install symfony/symfony
这样就装完了symfonys和propel&croel, mojavi3,还有用来运行脚本命令的pake(php make),PHing(php ant?),一大堆东西。
from :http://www.blogjava.net/calvin/archive/2005/11/22/20938.html
author:江南白衣
Tags: blog, class, html, java, php, python, ror, ruby, web
Ruby on Rails 1.2 的一個重要進展是 RESTful,在了解怎麼用之前,我們要先了解什麼是 REST(Representational State Transfer)?
什麼是REST?
REST 是一種分散式超媒體系統(如WWW)的軟體架構風格,你可以想像它是一個良好設計的Web應用程式規則: 一組網路Web頁面(虛擬的狀態機器),其中 Client 透過點選超連結(狀態變換),結果是下個Web頁面(表示應用程式的下一個狀態)。
REST 所描述的網路系統包括三個部份:
data elements (resource, resource identifier, representation)
connectors (client, server, cache, resolver, tunnel)
components (origin server, gateway, proxy, user agent)
幾個重點:
Data elements 由標準化介面存取
Components 透過介面傳輸 資源表徵 (representations of resources) 來溝通,而不是直接操作資源本身。
Connectors 提供 component 的抽象介面來溝通,隱藏溝通機制的實作細節。
Components 使用 connectors 來存取,connectors 提供資源的存取或居中。
所有來自 connector 的 requests 必須包含所有必要的資訊來了解該 request,不需要依靠之前的request (即 stateless)
嚴格來說REST符合以下幾個條件:
應用程式的狀態跟功能拆成 resources
每個 resource 使用獨一無二用來當作超連結的通用定位語法(在WWW中即URI)
所有 resources 共用一致的介面在 client 跟 resource 之間轉換狀態,包括:
一組有限的良好定義操作 well-defined operations
一組有限的內容格式 content types,也許包括 可執行的程式碼 code-on-demand (在WWW中即Javascript)
這種通訊協定 protocol (在WWW中即用HTTP) 包含以下特色:
使用者端/伺服器端 Client/Server
狀態無關 Stateless
可以快取 Cacheable
分層的 Layered
符合 REST principles 的系統稱做 RESTful。
一般來說REST這個詞彙常被用來描述任何使用XML (or YAML, JSON, plain text) 的簡單介面,而不須靠其他機制(如SOAP)。另外 REST 雖然是個來自 WWW 的架構概念(比WWW晚),但是這兩者並不是綁在一起,有可能設計一個大型軟體系統符合REST但是不用HTTP協定也不需跟WWW互動,也有可能設計一個簡單的 XML+HTTP 界面使用 RPC model 而不符合 REST principles。
Resources
REST的一個最重要的觀念就是 resources (特定資訊的資源),每一個 resource 由一個 global identifier (即URI)所表示。為了操作這些 resources,網路的 components (即 clients 跟servers) 透過標準化的介面 (即HTTP) 來溝通並交換這些 resources 的 representations (即實際上傳達資訊的文件).
任意數量的 connectors (如clients, servers, caches, tunnels等) 可以居中 request,但是都不可以 “seeing past” (不需要其他 layer層)。這樣的應用程式跟一個 resource 互動根據兩件事情: resource的URI 跟 要做的動作 – 它不需要知道是否有 caches, proxies, gateways, firewalls, tunnels, 或其他任何藏在sever之間的東西。這個應用程式只需要知道資訊的格式 (representation),通常是 HTML或 XML 或圖片什麼的。
REST 有什麼優點?
支援快取 caching 將改善反應時間跟server的負載能力。
因為不必維持連結狀態,大大改善 server 的 scalability 能力。這表示不同server可以處理同一串 requests。
一個瀏覽器就可以存取任一應用程式跟資源,client 端不需使用別的軟體。
在HTTP之上不依存其他機制跟軟體。
跟其他連結方式相比(如RPC),可以提供相等的功能。
不需要其他的 discovery 機制,因為使用超連結了。
提供比RPC更好的長期相容性,因為 :
如同HTML這種文件具有後前及向後的相容能力
支援新的內容格式不需要丟掉舊的
另外比起RPC-style,URL command line 有極佳的 human-friendly 優點,容易 discovered/ transmitted /scripted/bookmarked 一個 URL。請參考 The Beauty of REST 跟 Tangled in the Threads 的優點介紹。
跟 RPC 的比較
跟 REST Web 應用程式對比的就是 RPC (Remote procedure call,實作上有XML-RPC或SOAP等) 了。RPC 應用程式曝露一或多個網路物件,每個有特定的 functions 可以呼叫。在 client 與物件溝通之前,它必須知道這個物件的知識來操作。
REST 的設計限制了 resource 的面向,它定義了介面 (動詞 verbs 跟內容型別 content types),導致比RPC更少的型別,但是更多的resource identifiers (名詞nouns)。REST 的設計尋求定義一組 resources 讓 Clients 可以一致性的互動,而且提供超連結在資源之間可以瀏覽,而不需要了解整個 resources 的知識 。Server 提供的表單 forms 也可以被用在 RESTful 的環境來描述 clients 如何建構 URL 來跟特定的resource做溝通。
這張圖指出了REST的三位一體 : Nouns, 有限的 Verbs 跟 Content Types。
需注意的是,根據不同的用法要求跟效率,在不同的應用程式環境中,該使用哪種架構仍有很大討論的空間跟爭議(SOAP的支持者認為REST仍有其侷限,只適合簡單的應用)。REST的重要性在於它有個目前最成功的大型軟體架構應用: Wide World Web。RESTful 是 Web 的天性,也是讓 Web 成功的原因,因此 RESTful 的支持者認為使用 RESTful 架構就是 Web上最好的方法來做到有擴充性(scalable),彈性跟有威力的應用程式。在有這麼多複雜的RPC技術之後,REST因其簡單又有威力的架構近來越來越受到矚目跟重視。
舉例
一個 RPC 應用程式可能會定義以下的操作:
getUser()
addUser()
removeUser()
updateUser()
getLocation()
addLocation()
removeLocation()
updateLocation()
listUsers()
listLocations()
findLocation()
findUser()
Client 的程式碼可能會這樣存取:
exampleAppObject = new ExampleApp(”example.com:1234″)
exampleAppObject.getUser()
在 REST中,重點在 resources(或稱作 nouns)的多樣性,比如說可能有以下的用法:
http://example.com/users/
http://example.com/users/{user} (one for each user)
http://example.com/findUserForm
http://example.com/locations/
http://example.com/locations/{location} (one for each location)
http://example.com/findLocationForm
Client 的程式碼可能這樣存取:
userResource = new Resource(http://example.com/users/001)
userResource.get()
每個 resource 擁有自己的識別名詞,而 Clients 從單一 resource 開始瀏覽,透過標準操作走訪 resource ,如 GET 下載,PUT更新,DELETE刪除,POST新增,注意到每個物件有自己的URL,而且可以容易被快取,複製跟書籤化(bookmarked)。
參考資源
Representational State Transfer
RestWiki
How to Create a REST Protocol
From :http://ihower.idv.tw/blog/archives/1542
Tags: blog, cache, html, java, javascript, ruby, server, web
转至 www.jdon.com
注: 理解ACE中的Reactor模式, 高效的响应IO等事件.
当前分布式计算 Web Services盛行天下,这些网络服务的底层都离不开对socket的操作。他们都有一个共同的结构:
1. Read request
2. Decode request
3. Process service
4. Encode reply
5. Send reply
经典的网络服务的设计如下图,在每个线程中完成对数据的处理:
但这种模式在用户负载增加时,性能将下降非常的快。我们需要重新寻找一个新的方案,保持数据处理的流畅,很显然,事件触发机制是最好的解决办法,当有事件发生时,会触动handler,然后开始数据的处理。
Reactor模式类似于AWT中的Event处理:
Reactor模式参与者
1.Reactor 负责响应IO事件,一旦发生,广播发送给相应的Handler去处理,这类似于AWT的thread
2.Handler 是负责非堵塞行为,类似于AWT ActionListeners;同时负责将handlers与event事件绑定,类似于AWT addActionListener
如图:
Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。
我们来看看Reactor模式代码:
| public class Reactor implements Runnable{
final Selector selector; Reactor(int port) throws IOException { serverSocket.configureBlocking(false); logger.debug("–>Start serverSocket.register!"); //利用sk的attache功能绑定Acceptor 如果有事情,触发Acceptor public void run() { // normally in a new Thread //运行Acceptor或SocketReadHandler } class Acceptor implements Runnable { // inner |
以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,可以立即触发相应链接的Handler。
再看看Handler代码:
| public class SocketReadHandler implements Runnable {
public static Logger logger = Logger.getLogger(SocketReadHandler.class); private Test test=new Test(); final SocketChannel socket; static final int READING = 0, SENDING = 1; public SocketReadHandler(Selector sel, SocketChannel c) socket = c; socket.configureBlocking(false); //将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。 //同时将SelectionKey标记为可读,以便读取。 public void run() { /** ByteBuffer input = ByteBuffer.allocate(1024); int bytesRead = socket.read(input); …… //激活线程池 处理这些request ….. }catch(Exception e) { } |
注意在Handler里面又执行了一次attach,这样,覆盖前面的Acceptor,下次该Handler又有READ事件发生时,将直接触发Handler.从而开始了数据的读 处理 写 发出等流程处理。
将数据读出后,可以将这些数据处理线程做成一个线程池,这样,数据读出后,立即扔到线程池中,这样加速处理速度:

更进一步,我们可以使用多个Selector分别处理连接和读事件。
一个高性能的Java网络服务机制就要形成,激动人心的集群并行计算即将实现。
Tags: class, debug, java, ror, server, web, 类
(作者: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 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_); }
Tags: cache, class, java, unix, windows, 优化, 平台, 开发, 技术, 类
(作者:Douglas C. Schmidt ,by huihoo.org CORBA课题 Thzhang 译 , Allen整理,制作)
无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,可以用Double Checked Locking 模式来减少竞争和加锁载荷。
1、标准的单例。开发正确的有效的并发应用是困难的。程序员必须学习新的技术(并发控制和防止死锁的算法)和机制(如多线程和同步API)。此外,许多熟 悉的设计模式(如单例和迭代子)在包含不使用任何并发上下文假设的顺序程序中可以工作的很好。为了说明这点,考虑一个标准的单例模式在多线程环境下的实 现。单例模式保证一个类仅有一个实例同时提供了全局唯一的访问这个实例的入口点。在c++程序中动态分配单例对象是通用的方式,这是因为c++程序没有很 好的定义静态全局对象的初始化次序,因此是不可移植的。而且,动态分配避免了单例对象在永远没有被使用情况下的初始化开销。
class 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来初始化它自己。