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

<channel>
	<title>Tony Bai &#187; 感悟</title>
	<atom:link href="http://tonybai.com/tag/%e6%84%9f%e6%82%9f/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Sun, 12 Apr 2026 22:30:28 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>又当爸爸了！</title>
		<link>https://tonybai.com/2020/07/29/my-second-daughter-was-born/</link>
		<comments>https://tonybai.com/2020/07/29/my-second-daughter-was-born/#comments</comments>
		<pubDate>Wed, 29 Jul 2020 06:01:34 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[生活簿]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[七月]]></category>
		<category><![CDATA[二宝]]></category>
		<category><![CDATA[二胎]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[女儿]]></category>
		<category><![CDATA[家庭]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[摄影]]></category>
		<category><![CDATA[果果]]></category>
		<category><![CDATA[爸爸]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[老婆]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2932</guid>
		<description><![CDATA[2020年7月23日早6点46分，随着我家二宝(小名：七月)的呱呱坠地，我又当爸爸了! 图：二宝出生后的第一张照片 距离我家大宝(果果)的出生已经十年了。在这十年间，果果已经出落成一个聪明可爱、灵通剔透、漂亮温柔的大姑娘了，妥妥的是妈妈的小棉袄，爸爸的小情人:)，姥姥的小粘包，爷爷奶奶的乖孙女。 图：大宝果果是大姑娘了 但每每当果果提到其同班同学多数都有姐妹或兄弟陪伴上学、上才艺课的时候，我和我老婆的心里就会一动：究竟该不该给果果生一个亲弟弟/妹妹呢？ 2019下半年，我们决定为果果生个弟弟或妹妹。我们计划尝试半年，如果不行，我们年龄也大了，也许真的就不会再要了。结果上天十分眷顾我们，老婆在10月末怀上了。 大宝的愿望是我们要二宝的最直接和最主要的原因，我们也觉得两个宝宝在人生路上能相互陪伴总是更好的。其次，大宝出生时，我们还年轻，体验不充分，这次想再来一遍(也不知道哪来的这份勇气^_^)；再次，老人那边还有精力，还可以帮忙照顾孩子，我们在忙事业的同时，也不会有太多的后顾之忧；最后，两个宝宝也让家庭结构更合理。 写到这里，我也感觉上面的理由写得有些“冠冕堂皇”！想要就要，哪还需要这么多“借口”^_^。 生二宝唯一的担心就是已经是“高龄”的老婆。和十年前年轻的她相比，这次在孕期、生产和产后的风险都要高出许多。因此，在整个十月怀胎以及生产的过程中，我都更为紧张，但能做的也只有全程守护在老婆身边：制定营养计划、每天接送、全程陪检等。老婆本人倒是没有这方面担心，鉴于大宝生产前后的顺利，她坚信这次二宝也会同样顺利。 整个孕期也正如老婆坚信的那样，一切都很顺利，除了老婆患上了妊娠期糖尿病。老婆的一个性格特点就是认准一件事后，就能坚定不移地、自律地执行下去。由于妊娠期糖尿病对饮食的要求，老婆整整几个月都远离美味的“糖分”，保证了二宝在肚肚里的健康发育。同时，为了能够像一胎那样顺产，老婆坚持每天都要走上1w步，风雨无阻，天气不好，就在楼梯间里爬楼梯或在顶楼露台来回踱步。这份坚毅让老婆在38周的彩超检查中收获了期望的结论：医生看完彩超结果后给了我老婆十分肯定的诊断意见：你一定可以自己生！ 老婆，你真的很伟大！ 老婆在7月22日早晨见红了。按照一般经验，见红后24-48小时二宝就会出生了。7月23日凌晨3点老婆有了宫缩反应，虽然还不规律，但保险起见，我和老婆还是决定带上行李赶往医院。3:30到达医院急诊，产科的急诊大夫给开了一些检查和检验，结果出来后，就安排老婆去了(盛京医院滑翔园区)第五产科住院处办理住院。住院医生给老婆做了内检，说老婆今天很快就能生。早上5:00多，老婆进入待产室。随着规律性宫缩的到来，老婆十分痛苦。6点20分，医生决定让老妈进分娩室，我就在外面焦急等待。 老婆肚子里的二宝仿佛知道体谅她的妈妈，十分配合妈妈生产，让产程大大缩短，大大减少了老婆生产过程中承受的疼痛的时长。 在老婆进入分娩室后仅30分钟，站在分娩室外的我就听到了我家二宝第一声响亮的婴儿啼哭声。那时那刻，我和大宝出生时一样，流下了兴奋而又心疼老婆的眼泪。 母女平安！我的一颗高悬的心终于放下了，我再次当爸爸了！ 微信赞赏： &#169; 2020, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p>2020年7月23日早6点46分，随着我家<a href="https://daughter2.tonybai.com">二宝(小名：七月)</a>的呱呱坠地，<strong>我又当爸爸了</strong>!</p>
<p><img src="https://tonybai.com/wp-content/uploads/my-second-daughter/ally-was-born.jpeg" alt="img{512x368}" /><br />
<center>图：二宝出生后的第一张照片</center></p>
<p>距离我家<a href="https://tonybai.com/2010/05/11/now-i-am-a-father/">大宝(果果)的出生</a>已经十年了。在这十年间，<a href="https://daughter.tonybai.com">果果</a>已经出落成一个聪明可爱、灵通剔透、漂亮温柔的大姑娘了，妥妥的是<strong>妈妈的小棉袄，爸爸的小情人:)，姥姥的小粘包，爷爷奶奶的乖孙女</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-10-years-old-1.jpg" alt="img{512x368}" /><br />
<center>图：大宝果果是大姑娘了</center></p>
<p>但每每当果果提到其同班同学多数都有姐妹或兄弟陪伴上学、上才艺课的时候，我和我老婆的心里就会一动：<strong>究竟该不该给果果生一个亲弟弟/妹妹呢？</strong> 2019下半年，我们决定为果果生个弟弟或妹妹。我们计划尝试半年，如果不行，我们年龄也大了，也许真的就不会再要了。结果<strong>上天十分眷顾我们，老婆在10月末怀上了</strong>。</p>
<p>大宝的愿望是我们要二宝的最直接和最主要的原因，我们也觉得两个宝宝在人生路上能相互陪伴总是更好的。其次，大宝出生时，我们还年轻，体验不充分，这次想再来一遍(<strong>也不知道哪来的这份勇气^_^</strong>)；再次，老人那边还有精力，还可以帮忙照顾孩子，我们在忙事业的同时，也不会有太多的后顾之忧；最后，两个宝宝也让家庭结构更合理。</p>
<blockquote>
<p>写到这里，我也感觉上面的理由写得有些“冠冕堂皇”！想要就要，哪还需要这么多“借口”^_^。</p>
</blockquote>
<p>生二宝唯一的担心就是已经是“高龄”的老婆。和十年前年轻的她相比，这次在孕期、生产和产后的风险都要高出许多。因此，在整个十月怀胎以及生产的过程中，我都更为紧张，但能做的也只有全程守护在老婆身边：制定营养计划、每天接送、全程陪检等。老婆本人倒是没有这方面担心，鉴于大宝生产前后的顺利，她坚信这次二宝也会同样顺利。</p>
<p>整个孕期也正如老婆坚信的那样，一切都很顺利，除了老婆患上了妊娠期糖尿病。老婆的一个性格特点就是认准一件事后，就能坚定不移地、自律地执行下去。由于妊娠期糖尿病对饮食的要求，老婆整整几个月都远离美味的“糖分”，保证了二宝在肚肚里的健康发育。同时，为了能够像一胎那样顺产，老婆坚持每天都要走上1w步，风雨无阻，天气不好，就在楼梯间里爬楼梯或在顶楼露台来回踱步。这份坚毅让老婆在38周的彩超检查中收获了期望的结论：医生看完彩超结果后给了我老婆十分肯定的诊断意见：<strong>你一定可以自己生</strong>！</p>
<p><strong>老婆，你真的很伟大！</strong></p>
<p>老婆在7月22日早晨见红了。按照一般经验，见红后24-48小时二宝就会出生了。7月23日凌晨3点老婆有了宫缩反应，虽然还不规律，但保险起见，我和老婆还是决定带上行李赶往医院。3:30到达医院急诊，产科的急诊大夫给开了一些检查和检验，结果出来后，就安排老婆去了(盛京医院滑翔园区)第五产科住院处办理住院。住院医生给老婆做了内检，说老婆今天很快就能生。早上5:00多，老婆进入待产室。随着规律性宫缩的到来，老婆十分痛苦。6点20分，医生决定让老妈进分娩室，我就在外面焦急等待。</p>
<p>老婆肚子里的二宝仿佛知道体谅她的妈妈，十分配合妈妈生产，让产程大大缩短，大大减少了老婆生产过程中承受的疼痛的时长。 在老婆进入分娩室后仅30分钟，站在分娩室外的我就听到了我家二宝第一声响亮的婴儿啼哭声。那时那刻，<strong>我和大宝出生时一样，流下了兴奋而又心疼老婆的眼泪</strong>。</p>
<p>母女平安！我的一颗高悬的心终于放下了，我再次当爸爸了！</p>
<hr />
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/07/29/my-second-daughter-was-born/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>果果十周岁了！</title>
		<link>https://tonybai.com/2020/05/03/guoguo-ten-years-old/</link>
		<comments>https://tonybai.com/2020/05/03/guoguo-ten-years-old/#comments</comments>
		<pubDate>Sun, 03 May 2020 05:24:50 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[生活簿]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[七月]]></category>
		<category><![CDATA[二宝]]></category>
		<category><![CDATA[二胎]]></category>
		<category><![CDATA[十岁]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[女儿]]></category>
		<category><![CDATA[家庭]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[摄影]]></category>
		<category><![CDATA[果果]]></category>
		<category><![CDATA[爸爸]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[老婆]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2937</guid>
		<description><![CDATA[好久没有在我的博客上写关于果果的事情了，因为很多关于果果成长的经历都记录在她自己的博客中了。但今天是她十周岁的生日，是个值得纪念的日子。闺女成长的十年，也是我学习为人父的十年。作为父亲，我发自内心地想说点啥，是回顾，也是感受，亦有些寄语^_^。 图：果果成长的十年 出生 老婆在2009年7月怀上了果果。那时我们刚刚新婚不久，二人世界还没过够^&#95;^，小家伙的突然到来还让我们有些“手足无措”。为此，我们还认真地讨论了两天，最终老婆拍板：我要生下这个孩子，于是果果保住了^&#95;^。如今，每当果果提及此事，都会“发狠”地盯上我几眼，我也只能呵呵呵呵地应对^_^。 老婆怀胎中段，我一直在福建出差，虽然有岳母陪在老婆身边，但年底那两个月，老婆心情十分差。直到那一年大年三十的下午2点，我才在桃仙机场下的飞机，匆匆赶回家。大街上连车的打不到，多亏还有公交系统。进入家门，心里满满的都是对老婆的愧疚。记忆中老婆似乎并没有说啥，只说了一句“吃饭吧”，我顿感心里热乎乎的。 果果似乎很享受在妈妈子宫中待着，预产期都过了几天了，她还没有反应，直到2010年5月3日凌晨1点多，规律性的宫缩“来袭”。我们匆忙赶到医院，早上6点半多，老婆进入产室，9点多，我在产室外听到了果果呱呱坠地后的第一声啼哭。 图：刚出生的果果 果果出生后，恰逢徐峥的作品《人在囧途》上映，影片中徐峥扮演角色的孩子叫果果，我们觉得这个小名不错，于是便给我们宝宝起名为果果。 第一次照顾这么小的孩子也着实让我们这些大人手忙脚乱一段时间。出月子后，生活逐渐恢复平稳。果果每天除了吃奶就是睡觉，也算是比较省心了。稍大一些后，果果似乎并不太愿意睡觉了，每次喂完奶都得放到小车上在厅里推过来推过去哄她睡觉，后来果果姥姥买的一个能发出大恐龙吼声的玩具也加入到促进果果睡眠的行列中^&#95;^。 第一次走路(0岁) 果果一直母乳喂养，身体也很壮实。抬头、翻身、学会爬行和其他孩子相比都略有提前。最让我印象深刻是她第一次学会独立行走。记得那是2010年农历春节前，我们家当时的供暖非常好，室温在28度以上。果果在家只穿一套内衣裤，因此行动和玩耍起来非常方便。之前果果就可以扶着床沿踱步了并且她似乎也很喜欢站起来的感觉。那天她自己在卧室的地板上玩耍，我在门口偷偷观察她。她玩了一会儿就开始扶着床站起来，看我站在门口，她居然放开了扶着床沿的双手，向我摇摇晃晃地走了过来。从床边到卧室门口大约有不到2米的距离，我也兴奋地张开双臂，引导她向我走来。她边走边兴奋地笑，似乎也在惊讶于自己能独立行走了。在她扑到我的怀中的那一刻，我才意识到我见证了果果人生第一次独立行走，老婆听到这个消息也是兴奋不已。自从果果学会了走路，此后便一发不可收拾^&#95;^。 打针不哭(1岁) 可能是因为母乳喂养，果果在一岁的这一年中没有患过任何感冒发烧的病症。但当母体带给她的免疫力逐渐失去作用后，果果便和其他小朋友一样，会得感冒，也会发烧。记得果果第一次感冒(刚过一岁生日没多久)就烧的特别厉害。由于我们也是第一次遇到这种情况，特别担心，于是就带她去医院检查。虽然我们期望医生开立口服药，但医生最终还是开了点滴。在护士站门口，果果看到其他孩子扎针时都哇哇的哭，心里也胆怯了。果不其然，当针头穿透她的皮肤进入血管的那一刻，果果也像其他孩子一样，哇哇大哭起来。 有了这次“痛苦”的扎针经历后，我们也对她进行了心理疏导，教她要学会坚强，从她的眼神看得出来，她似乎听懂了。在2011年的秋冬换季，果果又患感冒发烧。同样去了医院，同样开了点滴，但在护士站扎针的时候，果果居然很坚强的忍住了，没有哇哇的哭泣，这让看惯了孩子痛哭的护士也是惊奇不已。看着泪水在眼圈里打转但没有哭出来的果果，我们心里更是心疼她了。 送去幼儿园学说话(2岁) 果果在11个月的时候就会喊妈妈了，但直到2岁半她能吐出的字依然只有“妈妈”，偶尔也有“爸爸”。现在看来，果果说话晚是因为我们给她的语言刺激太少了。果果不愿意睡觉，一旦睡着了，老人生怕声音大吵醒她，于是就命令我们不许出声。久而久之，果果在潜意思中得到的声音刺激、语言刺激照比其他小朋友就要少很多。为此，我们还带着果果去看了生长发育门诊、做过筛查，结果都显示果果没有任何生理性的疾病。 我们需要找到一个让果果接受更多语言刺激的方法，最终我们决定在果果2岁零5个月的时候送她去幼儿园“学说话”。做过家长的都知道，送孩子去幼儿园的过程是“痛苦”的，孩子哭闹，家长心疼，但这个过程是必须要经历的。付出了就有收获。在果果上幼儿园后的一个月，果果的“话匣子”就彻底打开了^&#95;^。 更像女娃了，但怕大海(3岁) 出生时，果果头发稀少，为了让果果长出更好的头发，我们每隔一段时间就把她的头发剃的很短(几近光头)。在2岁之内，果果更像一个“男娃”。直到3岁以后，我们开始给她留头发了。小家伙似乎也知道留头发后自己更好看了，姥姥每次给她梳头扎辫她都很喜欢。留着还有些短的头发让她更像女娃了： 图：更像女娃的果果 下半年，我们把果果转到了更大的幼儿园，并且果果每天上幼儿园都不再费劲了。她在幼儿园也学到了许多知识、技能和礼节。 3岁的果果的身体显得比同龄的女宝高大一圈，我们也开始带着她到处出行游玩。劳动节黄金周我们第一次带孩子去海边。那天的风浪比较大，浪花拍击礁石的声音震耳欲聋，果果显得很害怕。我们抱着她向海边靠近，但她却一直在挣扎并大喊：“离开、离开、走、走”。当我自己独自向大海靠近时，她也大喊：“爸爸，你回来，你回来”。见此情景，我们都哈哈大笑起来。 后来我们去了一处比较海浪比较舒缓的地带，没有了海浪拍打的巨大轰隆声，果果镇定了许多。也开始站在沙滩上和其他小朋友一起挖起沙子了。 独立爬山(4岁) 4岁的果果不仅个头高，而且壮实了。我们在权衡了之后，决定在假期带她去爬山，并且我已经做好了背她上山的准备。那次我们爬的是千山。千山在整个省内的爬山困难指数排行榜上也是名列前茅的。不过小家伙似乎很喜欢爬山，在登山栈道上显得十分兴奋，我们也给她做心里建设，希望她自己爬到顶峰。虽然在中段她也曾打过退堂鼓，但最终小家伙还是凭借自己的双腿和毅力爬上了山顶，我和老婆也都是非常惊讶。下山过程中，小家伙也是一路欢喜，并没用我们费心。只是由于累了，在回程的车上，小家伙呼呼的睡了一道。 正因为此次爬山的经历，果果爱上了爬山。后续选择旅游景点，她总是先挑那些有山可爬的地方，比如：2019年的陕西的华山、骊山等。 和妈妈一起去普吉岛(5岁) 孩子小的时候，出行很麻烦，而且孩子能收获的东西有限。5岁是一个很好的节点，她基本能自立了，而且感知和吸收外界信息的能力已经很强了。 5岁这一年是果果第一次和妈妈出国旅游，此次出行的目的是泰国普吉岛，和她们一起去的还有老婆的同事，这些同事也能帮助老婆照顾照顾果果，顺道还能锻炼一下果果的交际能力。这也是果果第一次乘飞机出行，在机场她十分兴奋。她们的航班在首尔中转，从首尔飞到普吉需要5-6个小时，这下让果果过了一把飞行瘾，她尤其喜欢飞机起降过程中的那种感觉，以至于以后每次出行，她都嚷嚷着要买多次经停或中转的航班^&#95;^。 更难得可贵的是，这次的旅游经历深深印在果果的记忆中，至今每当翻看那时的照片时，她还能头头是道的给我们讲当时发生的故事。有些事情，我老婆都已经记不得了。 上小学了(6岁) 转眼间，果果来到了6周岁，已经到了上学的年龄了。9月份，果果正式成为一名珠江街第五小学一年级的“小豆包”。和第一次上幼儿园不同，这次果果适应的很快，也没有哭鼻子的情况。反倒是回来和我们说她班级有小朋友一直哭，她还很疑惑这些小朋友为什么要哭^&#95;^。 上学后，更多的教育责任“甩”给了班主任老师，我们平时更多是帮忙批改批改作业，督促读读书，带着上上才艺班。果果的古筝是各门才艺课中学的最好的，果果也有了那么一些古典的气质： 图：有一丝古典气质的果果 这一年我还给果果开了博客。有些东西，光靠脑子是记不住的，写下来，留给多年后的自己和孩子慢慢回味。在果果能自己维护这个博客之前，我就先替她维护了。 叛逆与独立(7~9岁) 进入到7岁以后，果果受到的教育多了，读的书多了，渐渐了有了自己的主见和小脾气，再也不是那个将父母话“奉为圭臬”的小女娃了。如果就某事“辩论”，她姥姥已经完全不是对手了。也只有我和老婆偶尔还能“恩威并举”的降住她:(。 果果喜欢读故事书。她最喜欢读郑渊洁的童话，按照她的说法，市面上郑渊洁的书她基本都读完了，有些书，她已经读过不止一遍了。受郑渊洁风格的影响，她喜欢写幻想类的作文，喜欢天马行空，因此在细节描写上就差了一些。 她还喜欢“米小圈上学记”，每天晚上都是在天猫精灵播放的米小圈上学记中入睡的。 她喜欢宜家买来的老虎和小狗玩偶，一个起名为花果，一个起名为木果，每天一左一右的陪她入睡。 天猫精灵是她每天不可或缺的“伙伴”。早上听新闻早报，天气预报；晚上听故事，听历史，听音乐；偶尔还和天猫精灵玩玩互动猜谜游戏。真不愧为互联网和智能时代的原著民。 8岁的果果，其古筝考级已经通过了10级，这还是在她不是很勤奋练琴的情况下取得的。 这个阶段的果果也十分贪玩，喜欢去游乐场，玩老爸都不敢玩的惊险刺激的项目(只能由她妈妈陪着)。 9岁时，她偶尔和妈妈看了一集“家有儿女”，从那时起就“沉迷”于该剧：只要拿起iPad就必然打开“家有儿女”视频。她看电视剧和她看书的特点一样，如果某一集是她喜欢的，她会反复看上好多遍，丝毫没有不耐烦的迹象。有些集的台词她都能背下来，并粘着我和我老婆要给我们讲。还别说，讲的还头头是道的^&#95;^。 亭亭玉立的大姑娘(10岁) 由于新冠疫情的影响，果果的10岁生日在家里过的，我们也没法带她出去“玩耍”一番。10岁的她已经是一个亭亭玉立的大姑娘了。她的个头快赶上她妈妈了，大长腿，身材是“青出于蓝而胜于蓝”。 图：十岁的果果 图：亭亭玉立的果果 这次10周岁的生日除了生日蛋糕，还有一个更为特殊的礼物，那就是妈妈肚里的二宝，这也是果果一直想要的弟弟/妹妹。自从知道妈妈怀了二宝之后，果果变得更加懂事了。每天晚上对着妈妈的肚子给二宝讲故事，晚上睡觉前也会对着妈妈肚子猛“亲”几口^&#95;^。 为了留下美好回忆，我们还特地在果果十周岁生日的时候在影楼留下了一家四口的合影： 图：十岁果果生日时一家四口合影 小结 果果成长的十年给我的最大感受就是：快！一晃间，果果都10岁了，二宝也即将出生。我和老婆也即将步入中年。这里做的这个阶段性的回顾，以期若干年后当记忆模糊时还能通过这篇文章回忆起当年果果小时候的点点滴滴。 这里也希望果果在未来的人生道路中能继续一帆风顺，身体健健康康，每天快快乐乐。 希望果果和即将出生的二宝一起姐妹情深，相濡以沫，共同走好人生之路。 [...]]]></description>
			<content:encoded><![CDATA[<p>好久没有在我的博客上写关于<a href="https://daughter.tonybai.com">果果</a>的事情了，因为很多关于果果成长的经历都记录在<a href="https://daughter.tonybai.com">她自己的博客</a>中了。但今天是她<a href="https://daughter.tonybai.com/2020/05/03/i-am-10-years-old/">十周岁的生日</a>，是个值得纪念的日子。闺女成长的十年，也是我学习为人父的十年。作为父亲，我发自内心地想说点啥，是回顾，也是感受，亦有些寄语^_^。</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-10-years-old-2.jpg" alt="img{512x368}" /><br />
<center>图：果果成长的十年</center></p>
<h3>出生</h3>
<p>老婆在2009年7月怀上了果果。那时我们刚刚新婚不久，二人世界还没过够^&#95;^，小家伙的突然到来还让我们有些“手足无措”。为此，我们还认真地讨论了两天，最终老婆拍板：<strong>我要生下这个孩子</strong>，于是果果保住了^&#95;^。如今，每当果果提及此事，都会“发狠”地盯上我几眼，我也只能呵呵呵呵地应对^_^。</p>
<p>老婆怀胎中段，我一直在福建出差，虽然有岳母陪在老婆身边，但年底那两个月，老婆心情十分差。直到那一年大年三十的下午2点，我才在桃仙机场下的飞机，匆匆赶回家。大街上连车的打不到，多亏还有公交系统。进入家门，心里满满的都是对老婆的愧疚。记忆中老婆似乎并没有说啥，只说了一句“吃饭吧”，我顿感心里热乎乎的。</p>
<p>果果似乎很享受在妈妈子宫中待着，预产期都过了几天了，她还没有反应，直到2010年5月3日凌晨1点多，规律性的宫缩“来袭”。我们匆忙赶到医院，早上6点半多，老婆进入产室，9点多，我在产室外听到了果果呱呱坠地后的第一声啼哭。</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-was-born.JPG" alt="img{512x368}" /><br />
<center>图：刚出生的果果</center></p>
<p>果果出生后，恰逢徐峥的作品《人在囧途》上映，影片中徐峥扮演角色的孩子叫果果，我们觉得这个小名不错，于是便给我们宝宝起名为<strong>果果</strong>。</p>
<p>第一次照顾这么小的孩子也着实让我们这些大人手忙脚乱一段时间。出月子后，生活逐渐恢复平稳。果果每天除了吃奶就是睡觉，也算是比较省心了。稍大一些后，果果似乎并不太愿意睡觉了，每次喂完奶都得放到小车上在厅里推过来推过去哄她睡觉，后来果果姥姥买的一个能发出大恐龙吼声的玩具也加入到促进果果睡眠的行列中^&#95;^。</p>
<h3>第一次走路(0岁)</h3>
<p>果果一直母乳喂养，身体也很壮实。抬头、翻身、学会爬行和其他孩子相比都略有提前。最让我印象深刻是她第一次学会独立行走。记得那是2010年农历春节前，我们家当时的供暖非常好，室温在28度以上。果果在家只穿一套内衣裤，因此行动和玩耍起来非常方便。之前果果就可以扶着床沿踱步了并且她似乎也很喜欢站起来的感觉。那天她自己在卧室的地板上玩耍，我在门口偷偷观察她。她玩了一会儿就开始扶着床站起来，看我站在门口，她居然放开了扶着床沿的双手，向我摇摇晃晃地走了过来。从床边到卧室门口大约有不到2米的距离，我也兴奋地张开双臂，引导她向我走来。她边走边兴奋地笑，似乎也在惊讶于自己能独立行走了。在她扑到我的怀中的那一刻，我才意识到我见证了果果人生第一次独立行走，老婆听到这个消息也是兴奋不已。自从果果学会了走路，此后便一发不可收拾^&#95;^。</p>
<h3>打针不哭(1岁)</h3>
<p>可能是因为母乳喂养，果果在一岁的这一年中没有患过任何感冒发烧的病症。但当母体带给她的免疫力逐渐失去作用后，果果便和其他小朋友一样，会得感冒，也会发烧。记得果果第一次感冒(刚过一岁生日没多久)就烧的特别厉害。由于我们也是第一次遇到这种情况，特别担心，于是就带她去医院检查。虽然我们期望医生开立口服药，但医生最终还是开了点滴。在护士站门口，果果看到其他孩子扎针时都哇哇的哭，心里也胆怯了。果不其然，当针头穿透她的皮肤进入血管的那一刻，果果也像其他孩子一样，哇哇大哭起来。</p>
<p>有了这次“痛苦”的扎针经历后，我们也对她进行了心理疏导，教她要学会坚强，从她的眼神看得出来，她似乎听懂了。在2011年的秋冬换季，果果又患感冒发烧。同样去了医院，同样开了点滴，但在护士站扎针的时候，果果居然很坚强的忍住了，没有哇哇的哭泣，这让看惯了孩子痛哭的护士也是惊奇不已。看着泪水在眼圈里打转但没有哭出来的果果，我们心里更是心疼她了。</p>
<h3>送去幼儿园学说话(2岁)</h3>
<p>果果在11个月的时候就会喊妈妈了，但直到2岁半她能吐出的字依然只有“妈妈”，偶尔也有“爸爸”。现在看来，果果说话晚是因为我们给她的语言刺激太少了。果果不愿意睡觉，一旦睡着了，老人生怕声音大吵醒她，于是就命令我们不许出声。久而久之，果果在潜意思中得到的声音刺激、语言刺激照比其他小朋友就要少很多。为此，我们还带着果果去看了生长发育门诊、做过筛查，结果都显示果果没有任何生理性的疾病。</p>
<p>我们需要找到一个让果果接受更多语言刺激的方法，最终我们决定在果果2岁零5个月的时候送她去幼儿园“学说话”。做过家长的都知道，送孩子去幼儿园的过程是“痛苦”的，孩子哭闹，家长心疼，但这个过程是必须要经历的。付出了就有收获。在果果上幼儿园后的一个月，果果的“话匣子”就彻底打开了^&#95;^。</p>
<h3>更像女娃了，但怕大海(3岁)</h3>
<p>出生时，果果头发稀少，为了让果果长出更好的头发，我们每隔一段时间就把她的头发剃的很短(几近光头)。在2岁之内，果果更像一个“男娃”。直到3岁以后，我们开始给她留头发了。小家伙似乎也知道留头发后自己更好看了，姥姥每次给她梳头扎辫她都很喜欢。留着还有些短的头发让她更像女娃了：</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-3-years-old.jpg" alt="img{512x368}" /><br />
<center>图：更像女娃的果果</center></p>
<p>下半年，我们把果果转到了更大的幼儿园，并且果果每天上幼儿园都不再费劲了。她在幼儿园也学到了许多知识、技能和礼节。</p>
<p>3岁的果果的身体显得比同龄的女宝高大一圈，我们也开始带着她到处出行游玩。劳动节黄金周我们第一次带孩子去海边。那天的风浪比较大，浪花拍击礁石的声音震耳欲聋，果果显得很害怕。我们抱着她向海边靠近，但她却一直在挣扎并大喊：“离开、离开、走、走”。当我自己独自向大海靠近时，她也大喊：“爸爸，你回来，你回来”。见此情景，我们都哈哈大笑起来。</p>
<p>后来我们去了一处比较海浪比较舒缓的地带，没有了海浪拍打的巨大轰隆声，果果镇定了许多。也开始站在沙滩上和其他小朋友一起挖起沙子了。</p>
<h3>独立爬山(4岁)</h3>
<p>4岁的果果不仅个头高，而且壮实了。我们在权衡了之后，决定在假期带她去爬山，并且我已经做好了背她上山的准备。那次我们爬的是千山。千山在整个省内的爬山困难指数排行榜上也是名列前茅的。不过小家伙似乎很喜欢爬山，在登山栈道上显得十分兴奋，我们也给她做心里建设，希望她自己爬到顶峰。虽然在中段她也曾打过退堂鼓，但最终小家伙还是凭借自己的双腿和毅力爬上了山顶，我和老婆也都是非常惊讶。下山过程中，小家伙也是一路欢喜，并没用我们费心。只是由于累了，在回程的车上，小家伙呼呼的睡了一道。</p>
<p>正因为此次爬山的经历，果果爱上了爬山。后续选择旅游景点，她总是先挑那些有山可爬的地方，比如：2019年的<a href="https://daughter.tonybai.com/2019/08/17/xian-chongqing-tour-2nd-day-2019/">陕西的华山</a>、<a href="https://daughter.tonybai.com/2019/08/17/xian-chongqing-tour-2nd-day-2019/">骊山</a>等。</p>
<h3>和妈妈一起去普吉岛(5岁)</h3>
<p>孩子小的时候，出行很麻烦，而且孩子能收获的东西有限。5岁是一个很好的节点，她基本能自立了，而且感知和吸收外界信息的能力已经很强了。</p>
<p>5岁这一年是果果第一次和妈妈出国旅游，此次出行的目的是泰国普吉岛，和她们一起去的还有老婆的同事，这些同事也能帮助老婆照顾照顾果果，顺道还能锻炼一下果果的交际能力。这也是果果第一次乘飞机出行，在机场她十分兴奋。她们的航班在首尔中转，从首尔飞到普吉需要5-6个小时，这下让果果过了一把飞行瘾，她尤其喜欢飞机起降过程中的那种感觉，以至于以后每次出行，她都嚷嚷着要买多次经停或中转的航班^&#95;^。</p>
<p>更难得可贵的是，这次的旅游经历深深印在果果的记忆中，至今每当翻看那时的照片时，她还能头头是道的给我们讲当时发生的故事。有些事情，我老婆都已经记不得了。</p>
<h3>上小学了(6岁)</h3>
<p>转眼间，果果来到了6周岁，已经到了上学的年龄了。9月份，果果正式成为一名珠江街第五小学一年级的“小豆包”。和第一次上幼儿园不同，这次果果适应的很快，也没有哭鼻子的情况。反倒是回来和我们说她班级有小朋友一直哭，她还很疑惑这些小朋友为什么要哭^&#95;^。</p>
<p>上学后，更多的教育责任“甩”给了班主任老师，我们平时更多是帮忙批改批改作业，督促读读书，带着上上才艺班。果果的古筝是各门才艺课中学的最好的，果果也有了那么一些古典的气质：</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-gavatar.jpg" alt="img{512x368}" /><br />
<center>图：有一丝古典气质的果果</center></p>
<p>这一年我还给果果开了<a href="https://daughter.tonybai.com">博客</a>。有些东西，光靠脑子是记不住的，写下来，留给多年后的自己和孩子慢慢回味。在果果能自己维护这个博客之前，我就先替她维护了。</p>
<h3>叛逆与独立(7~9岁)</h3>
<p>进入到7岁以后，果果受到的教育多了，读的书多了，渐渐了有了自己的主见和小脾气，再也不是那个将父母话“奉为圭臬”的小女娃了。如果就某事“辩论”，她姥姥已经完全不是对手了。也只有我和老婆偶尔还能“恩威并举”的降住她:(。</p>
<p>果果喜欢读故事书。她最喜欢读郑渊洁的童话，按照她的说法，市面上郑渊洁的书她基本都读完了，有些书，她已经读过不止一遍了。受郑渊洁风格的影响，她喜欢写幻想类的作文，喜欢天马行空，因此在细节描写上就差了一些。</p>
<p>她还喜欢“米小圈上学记”，每天晚上都是在天猫精灵播放的米小圈上学记中入睡的。</p>
<p>她喜欢宜家买来的老虎和小狗玩偶，一个起名为花果，一个起名为木果，每天一左一右的陪她入睡。</p>
<p>天猫精灵是她每天不可或缺的“伙伴”。早上听新闻早报，天气预报；晚上听故事，听历史，听音乐；偶尔还和天猫精灵玩玩互动猜谜游戏。真不愧为互联网和智能时代的原著民。</p>
<p>8岁的果果，其古筝考级已经通过了10级，这还是在她不是很勤奋练琴的情况下取得的。</p>
<p>这个阶段的果果也十分贪玩，喜欢去游乐场，玩老爸都不敢玩的惊险刺激的项目(只能由她妈妈陪着)。</p>
<p>9岁时，她偶尔和妈妈看了一集“家有儿女”，从那时起就“沉迷”于该剧：只要拿起iPad就必然打开“家有儿女”视频。她看电视剧和她看书的特点一样，如果某一集是她喜欢的，她会反复看上好多遍，丝毫没有不耐烦的迹象。有些集的台词她都能背下来，并粘着我和我老婆要给我们讲。还别说，讲的还头头是道的^&#95;^。</p>
<h3>亭亭玉立的大姑娘(10岁)</h3>
<p>由于新冠疫情的影响，果果的10岁生日在家里过的，我们也没法带她出去“玩耍”一番。10岁的她已经是一个亭亭玉立的大姑娘了。她的个头快赶上她妈妈了，大长腿，身材是“青出于蓝而胜于蓝”。</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-10-years-old-1.jpg" alt="img{512x368}" /><br />
<center>图：十岁的果果</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-10-years-old-4.jpg" alt="img{512x368}" /><br />
<center>图：亭亭玉立的果果</center></p>
<p>这次10周岁的生日除了生日蛋糕，还有一个更为特殊的礼物，那就是妈妈肚里的二宝，这也是果果一直想要的弟弟/妹妹。自从知道妈妈怀了二宝之后，果果变得更加懂事了。每天晚上对着妈妈的肚子给二宝讲故事，晚上睡觉前也会对着妈妈肚子猛“亲”几口^&#95;^。</p>
<p>为了留下美好回忆，我们还特地在果果十周岁生日的时候在影楼留下了一家四口的合影：</p>
<p><img src="https://tonybai.com/wp-content/uploads/guoguo-ten-years-old/guoguo-10-years-old-3.jpg" alt="img{512x368}" /><br />
<center>图：十岁果果生日时一家四口合影</center></p>
<h3>小结</h3>
<p>果果成长的十年给我的最大感受就是：<strong>快</strong>！一晃间，果果都10岁了，二宝也即将出生。我和老婆也即将步入中年。这里做的这个阶段性的回顾，以期若干年后当记忆模糊时还能通过这篇文章回忆起当年果果小时候的点点滴滴。</p>
<p>这里也希望果果在未来的人生道路中能继续一帆风顺，身体健健康康，每天快快乐乐。</p>
<p>希望果果和即将出生的二宝一起姐妹情深，相濡以沫，共同走好人生之路。</p>
<p>最后，<strong>人生在于经历，而不在于得失</strong>。</p>
<hr />
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/05/03/guoguo-ten-years-old/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>探讨Docker容器中修改系统变量的方法</title>
		<link>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/</link>
		<comments>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/#comments</comments>
		<pubDate>Tue, 14 Oct 2014 13:56:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[phusion]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[SharedMemory]]></category>
		<category><![CDATA[shmget]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[共享内存]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1563</guid>
		<description><![CDATA[探讨完Docker对共享内存状态持久化的支持状况后，我将遗留产品build到一个pre-production image中，测试启动是否OK。很显然，我过于乐观了，Docker之路并不平坦。我收到了shmget报出的EINVAL错误码，提示参数非法。 shmget的manual对EINVAL错误码的说明如下： EINVAL： A&#160; new&#160; segment&#160; was&#160; to&#160; be&#160; created&#160; and size &#60; SHMMIN or size &#62; SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment. 显然我们要创建的shared memory的size很可能大于SHMMAX这个系统变量了。那么一个从base image创建出的容器中的系统变量到底是什么值呢？我们来查看一下，我们基于&#34;centos:centos6&#34;启动一个Docker容器，并检查其中的 系统变量值设置： $ sudo docker run -it &#34;centos:centos6&#34; /bin/bash bash-4.1# [...]]]></description>
			<content:encoded><![CDATA[<p>探讨完<a href="http://tonybai.com/2014/10/12/discussion-on-shared-mem-support-in-docker/">Docker对共享内存状态持久化的支持状况</a>后，我将遗留产品build到一个pre-production image中，测试启动是否OK。很显然，我过于乐观了，<a href="http://www.docker.com">Docker</a>之路并不平坦。我收到了shmget报出的EINVAL错误码，提示参数非法。 shmget的manual对EINVAL错误码的说明如下：</p>
<p><font face="Courier New">EINVAL：<br />
	A&nbsp; new&nbsp; segment&nbsp; was&nbsp; to&nbsp; be&nbsp; created&nbsp; and size &lt; SHMMIN or size &gt; SHMMAX, or no new segment was to be created, a segment with given key existed, but size is greater than the size of that segment.</font></p>
<p>显然我们要创建的shared memory的size很可能大于SHMMAX这个系统变量了。那么一个从base image创建出的容器中的系统变量到底是什么值呢？我们来查看一下，我们基于&quot;centos:centos6&quot;启动一个Docker容器，并检查其中的 系统变量值设置：</p>
<p><font face="Courier New">$ sudo docker run -it &quot;centos:centos6&quot; /bin/bash<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432<br />
	bash-4.1# sysctl -a|grep shmmax<br />
	kernel.shmmax = 33554432</font></p>
<p>可以看出默认情况下，当前容器中root账号看到的shmmax值我<font face="Courier New">33554432</font>， 我的程序要创建的shm size的确要大于这个值，报出EINVAL错误也就无可厚非了。我尝试按照物理机上的方法临时修改一下该值：</p>
<p><font face="Courier New">bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash: /proc/sys/kernel/shmmax: Read-only file system</font></p>
<p><font face="Courier New">/proc/sys/kernel/shmmax居然是只读的，无法修改。</font></p>
<p><font face="Courier New">我又尝试修改/etc/sysctl.conf这个持久化系统变量的地方，但打开/etc/sysctl.conf文件，我发现我又错了，这 个文件中shmmax的值如下：</font></p>
<p><font face="Courier New"># Controls the maximum shared segment size, in bytes<br />
	kernel.shmmax = 68719476736</font></p>
<p><font face="Courier New"><font face="Courier New">/etc/sysctl.conf文件 中的系统变量shmmax的值是68719476736，而系统当前的实际值则是33554432，难道是/etc /sysctl.conf中的值没有生效，于是我手工重新加载一次该文件：</font></font></p>
<p><font face="Courier New"><font face="Courier New">-bash-4.1# sysctl -p<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.ip_forward&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.conf.default.rp_filter&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;net.ipv4.conf.default.accept_source_route&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.sysrq&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.core_uses_pid&quot;<br />
	error: &quot;net.ipv4.tcp_syncookies&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-ip6tables&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-iptables&quot; is an unknown key<br />
	error: &quot;net.bridge.bridge-nf-call-arptables&quot; is an unknown key<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.msgmnb&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.msgmax&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.shmmax&quot;<br />
	error: &quot;Read-only file system&quot; setting key &quot;kernel.shmall&quot;</font></font></p>
<p><font face="Courier New"><font face="Courier New">我得到了和之前类似的错误结果：只读文件系统，无法修改。于是乎两个问题萦绕在我的面前：<br />
	1、为什么容器内当前系统变量值与sysctl.conf中的不一致？<br />
	2、为什么无法修改当前系统变量值?</font></font></p>
<p><font face="Courier New"><font face="Courier New">在翻阅了Stackoverflow, github docker issues后，我得到了的答案如下：</font></font></p>
<p><font face="Courier New"><font face="Courier New">1、Docker的base image做的很精简，甚至都没有init进程，原本在OS启动时执行生效系统变量的过程(sysctl -p)也给省略了，导致这些系统变量依旧保留着kernel默认值。以CentOs为例，在linux kernel boot后，init都会执行/etc/rc.d/rc.sysinit，后者会加载/etc/sysctl.conf中的系统变量值。下面是 CentOs5.6中的rc.sysinit代码摘录：</font></font></p>
<p><font face="Courier New"><font face="Courier New">&#8230; &#8230;<br />
	# Configure kernel parameters<br />
	update_boot_stage RCkernelparam<br />
	sysctl -e -p /etc/sysctl.conf &gt;/dev/null 2&gt;&amp;1<br />
	&#8230; &#8230;</font></font></p>
<p><font face="Courier New"><font face="Courier New">2、Docker容器中的系统变量在non-priviledged模式下目前(我使用的时docker 1.2.0版本)就无法修改，</font></font>这 和resolv.conf、hosts等文件映射到宿主机对应的文件有不同。</p>
<p><font face="Courier New">$ mount -l<br />
	&#8230;. &#8230;.<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	/dev/mapper/ubuntu&#8211;Server&#8211;14&#8211;vg-root on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)<br />
	&#8230; &#8230;</font></p>
<p>那么我们该如何修改系统变量值来满足遗留产品的需求呢？</p>
<p><b>一、使用&#8211;privileged选项</b></p>
<p>我们使用&#8211;privileged这个特权选项来启动一个基于centos:centos6的新容器，看看是否能对shmmax这样的系统变量值 进行修改：</p>
<p><font face="Courier New">$ sudo docker run -it &#8211;privileged&nbsp; &quot;centos:centos6&quot; /bin/bash<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432<br />
	bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	68719476736</font><br />
	<font face="Courier New">bash-4.1# sysctl -p<br />
	net.ipv4.ip_forward = 0<br />
	net.ipv4.conf.default.rp_filter = 1<br />
	net.ipv4.conf.default.accept_source_route = 0<br />
	kernel.sysrq = 0<br />
	kernel.core_uses_pid = 1<br />
	&#8230; &#8230;<br />
	kernel.msgmnb = 65536<br />
	kernel.msgmax = 65536<br />
	kernel.shmmax = 68719476736<br />
	kernel.shmall = 4294967296</font></p>
<p>可以看出，通过&#8211;privileged选项，容器获得了额外的特权，并且可以对系统变量的值进行修改了。不过这样的修改是不能保存在容器里的， 我们stop 容器，再重启该容器就能看出来：</p>
<p><font face="Courier New">$ sudo docker start 3e22d65a7845<br />
	$ sudo docker attach 3e22d65a7845<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	33554432</font></p>
<p>shmmax的值在容器重启后又变回了原先的那个默认值。不过重启后的容器依旧具有privileged的特权，我们还可以重新手工执行命令对系 统变量进行修改：</p>
<p><font face="Courier New">bash-4.1# echo 68719476736 &gt; /proc/sys/kernel/shmmax<br />
	bash-4.1# cat /proc/sys/kernel/shmmax<br />
	68719476736</font></p>
<p>但即便这样，也无法满足我们的需求，我们总不能每次都在容器中手工执行系统变量值修改的操作吧。privileged选项的能力能否带到 image中呢？答案是目前还不能，我们无法在build image时通过privileged选项修改系统变量值。</p>
<p>这样一来，我们能做的只有把产品启动与系统变量值修改放在一个脚本中了，并将该脚本作为docker 容器的cmd命令来执行，比如我们构建一个Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./start.sh /bin/start.sh<br />
	RUN chmod +x /bin/start.sh<br />
	CMD ["/bin/start.sh]</font></p>
<p><font face="Courier New">//start.sh<br />
	sysctl -p<br />
	/usr/bin/supervisord</font></p>
<p>这样，start.sh在supervisord启动前将系统变量值重新加载，而supervisord后续启动的程序就可以看到这些新系统变量 的值了。不过别忘了利用这个image启动容器时要加上&#8211;priviledged选项，否则容器启动就会失败。</p>
<p><b>二、使用phusion/baseimage</b></p>
<p>前面说过/etc/sysctl.conf中的值没有生效是因为docker官方提供的centos:centos6把init进程的初始化过程给精 简掉了。<a href="https://registry.hub.docker.com/u/phusion/baseimage/">phusion/baseimage</a>是目前docker registery上仅次于ubuntu和centos两个之后的base image，其提供了/sbin/my_init这个init进程，用于在container充当init进程的角色。那么my_init是否可以用于执行sysctl -p呢？我们试验一下：</p>
<p>我们先pull这个base image下来：<font face="Courier New">sudo docker pull phusion/baseimage。pull成功后，我们先基于&ldquo;phusion/baseimage&rdquo;启动一个容器做一些explore工作：</font></p>
<p><font face="Courier New">$ sudo docker run -i -t &quot;phusion/baseimage&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/rc.local&#8230;<br />
	*** Booting runit daemon&#8230;<br />
	*** Runit started as PID 100</font></p>
<p><font face="Courier New">通过nsenter进去，查看一下/sbin/my_init的源码，我们发现这是一个python脚本，不过从头到尾浏览一遍，没有发现sysctl加载/etc/sysctl.conf系统变量的操作。</font></p>
<p><font face="Courier New">不过，phusion文档中说my_init可以在初始化过程中执行/etc/my_init.d下的脚本。那是不是我们将一个执行sysctl -p的脚本放入/etc/my_init.d下就可以实现我们的目的了呢？试试。</font></p>
<p><font face="Courier New">我们编写一个脚本：load_sys_varibles.sh</font></p>
<p><font face="Courier New">#!/bin/sh<br />
	sysctl -p &gt; init.txt</font></p>
<p><font face="Courier New">下面是制作image的Dockerfile:</font></p>
<p><font face="Courier New">FROM phusion/baseimage:latest<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN echo &quot;kernel.shmmax = 68719476736&quot; &gt;&gt; /etc/sysctl.conf<br />
	RUN mkdir -p /etc/my_init.d<br />
	ADD load_sys_varibles.sh /etc/my_init.d/load_sys_varibles.sh<br />
	RUN chmod +x /etc/my_init.d/load_sys_varibles.sh<br />
	CMD ["/sbin/my_init"]</font></p>
<p><font face="Courier New">phusion/baseimage是基于ubuntu的OS，其sysctl.conf默认情况下没啥内容，所以我们在Dockerfile中向这个文件写入我们需要的系统变量值。构建image并启动容器：</font></p>
<p><font face="Courier New">$ sudo docker build -t &quot;myphusion:v1&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM phusion/baseimage:latest<br />
	&nbsp;&#8212;&gt; cf39b476aeec<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; d0e9b51a3e4f<br />
	Step 2 : RUN echo &quot;kernel.shmmax = 68719476736&quot; &gt;&gt; /etc/sysctl.conf<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 2c800687cc83<br />
	Step 3 : RUN mkdir -p /etc/my_init.d<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; fe366eea5eb4<br />
	Step 4 : ADD load_sys_varibles.sh /etc/my_init.d/load_sys_varibles.sh<br />
	&nbsp;&#8212;&gt; a641bb595fb9<br />
	Removing intermediate container c381b9f001c2<br />
	Step 5 : RUN chmod +x /etc/my_init.d/load_sys_varibles.sh<br />
	&nbsp;&#8212;&gt; Running in 764866552f25<br />
	&nbsp;&#8212;&gt; eae3d7f1eac5<br />
	Removing intermediate container 764866552f25<br />
	Step 6 : CMD ["/sbin/my_init"]<br />
	&nbsp;&#8212;&gt; Running in 9ab8d0b717a7<br />
	&nbsp;&#8212;&gt; 8be4e7b6b174<br />
	Removing intermediate container 9ab8d0b717a7<br />
	Successfully built 8be4e7b6b174</font></p>
<p><font face="Courier New">$ sudo docker run -it &quot;myphusion:v1&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/my_init.d/load_sys_varibles.sh&#8230;<br />
	sysctl: setting key &quot;kernel.shmmax&quot;: Read-only file system<br />
	*** /etc/my_init.d/load_sys_varibles.sh failed with status 255</font></p>
<p><font face="Courier New">*** Killing all processes&#8230;</font></p>
<p><font face="Courier New">唉，还是老问题！即便是在my_init中执行，依旧无法逾越Read-only file system，查看Phusion/baseimage的Dockerfile才知道，它也是From ubuntu:14.04的，根不变，上层再怎么折腾也没用。</font></p>
<p><font face="Courier New">换一种容器run方法吧，加上&#8211;privileged：</font></p>
<p><font face="Courier New">$ sudo docker run -it &#8211;privileged&nbsp; &quot;myphusion:v1&quot;<br />
	*** Running /etc/my_init.d/00_regen_ssh_host_keys.sh&#8230;<br />
	No SSH host key available. Generating one&#8230;<br />
	Creating SSH2 RSA key; this may take some time &#8230;<br />
	Creating SSH2 DSA key; this may take some time &#8230;<br />
	Creating SSH2 ECDSA key; this may take some time &#8230;<br />
	Creating SSH2 ED25519 key; this may take some time &#8230;<br />
	invoke-rc.d: policy-rc.d denied execution of restart.<br />
	*** Running /etc/my_init.d/load_sys_varibles.sh&#8230;<br />
	*** Running /etc/rc.local&#8230;<br />
	*** Booting runit daemon&#8230;<br />
	*** Runit started as PID 102</font></p>
<p><font face="Courier New">这回灵光了。enter到容器里看看设置的值是否生效了：</font></p>
<p><font face="Courier New">root@9e399f46372a:~#cat /proc/sys/kernel/shmmax<br />
	68719476736</font></p>
<p><font face="Courier New">结果如预期。这样来看phusion/baseimage算是为sysctl -p加载系统变量值提供了一个便利，但依旧无法脱离&#8211;privileged，且依旧无法在image中持久化这个设置。</font></p>
<p><font face="Courier New">在Docker github的issue中有人提出建议在Dockerfile中加入类似RUNP这样的带有特权的指令语法，但不知何时才能在Docker中加入这一功能。</font></p>
<p><font face="Courier New">总而言之，基于目前docker官网提供的base image，我们很难找到特别理想的修改系统变量值的方法，除非自己制作base image，这个还没尝试过，待后续继续研究。</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/14/discussion-on-the-approach-to-modify-system-variables-in-docker/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>docker容器内服务程序的优雅退出</title>
		<link>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/</link>
		<comments>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/#comments</comments>
		<pubDate>Thu, 09 Oct 2014 13:58:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Dockerfile]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Namespace]]></category>
		<category><![CDATA[nsenter]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[Signal]]></category>
		<category><![CDATA[supervisor]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[vm]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[命令行]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[映像]]></category>
		<category><![CDATA[最佳实践]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[虚拟化]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1555</guid>
		<description><![CDATA[近期在试验如何将我们的产品部署到docker容器中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。 一、优雅退出的原理 对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&#8220;优雅退出&#8221;操作。 与&#8220;优雅退出&#8221;对立的是&#8220;暴力退出&#8221;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&#8220;不一致&#8221;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。 二、测试用&#8220;服务程序&#8221; 为了测试docker容器对优雅退出的支持，我们编写如下&#8220;服务程序&#8221;用于放在docker容器中运行： //dockerapp1.go package main import &#34;fmt&#34; import &#34;time&#34; import &#34;os&#34; import &#34;os/signal&#34; import &#34;syscall&#34; type signalHandler func(s os.Signal, arg interface{}) type signalSet struct { &#160;&#160;&#160;&#160;&#160;&#160;&#160; m map[os.Signal]signalHandler } func signalSetNew() *signalSet { &#160;&#160;&#160;&#160;&#160;&#160;&#160; ss := new(signalSet) [...]]]></description>
			<content:encoded><![CDATA[<p><span style="line-height: 1.6em;">近期在试验如何将我们的产品部署到</span><a href="http://docker.com" style="line-height: 1.6em;">docker容器</a><span style="line-height: 1.6em;">中去，这其中涉及到一个技术环节，那就是如何让docker容器退出时其内部运行的服务程序也 可以优雅的退出。所谓优雅退出，指的就是程序在退出前有清理资源（比如关闭文件描述符、关闭socket），保存必要中间状态，持久化内存数据 （比如将内存中的数据flush到文件中）的机会。docker作为目前最火的轻量级虚拟化技术，其在后台服务领域的应用是极其广泛的，其设计者 在程序优雅退出方面是有考虑的。下面我们由简单到复杂逐一考量一下。</span></p>
<p><b>一、优雅退出的原理</b></p>
<p>对于服务程序而言，一般都是以daemon形式运行在后台的。通知这些服务程序退出需要使用到系统的signal机制。一般服务程序都会监听某个 特定的退出signal，比如SIGINT、SIGTERM等（通过kill -l命令你可以查看到几十种signal）。当我们使用kill + 进程号时，系统会默认发送一个SIGTERM给相应的进程。该进程通过signal handler响应这一信号，并在这个handler中完成相应的&ldquo;优雅退出&rdquo;操作。</p>
<p>与&ldquo;优雅退出&rdquo;对立的是&ldquo;暴力退出&rdquo;，也就是我们常说的使用kill -9，也就是kill -s SIGKILL + 进程号，这个行为不会给目标进程任何时间空隙，而是直接将进程杀死，无论进程当前在做何种操作。这种操作常常导致&ldquo;不一致&rdquo;状态的出现。SIGKILL这 个信号比较特殊，进程无法有效监听该信号，无法有效针对该信号设置handler，无法改变其信号的默认处理行为。</p>
<p><b>二、</b><b>测试用&ldquo;服务程序&rdquo;</b></p>
<p>为了测试docker容器对优雅退出的支持，我们编写如下&ldquo;服务程序&rdquo;用于放在docker容器中运行：</p>
<p><font face="Courier New">//dockerapp1.go</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;<br />
	import &quot;os&quot;<br />
	import &quot;os/signal&quot;<br />
	import &quot;syscall&quot;</font></p>
<p><font face="Courier New">type signalHandler func(s os.Signal, arg interface{})</font></p>
<p><font face="Courier New">type signalSet struct {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m map[os.Signal]signalHandler<br />
	}</font></p>
<p><font face="Courier New">func signalSetNew() *signalSet {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := new(signalSet)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.m = make(map[os.Signal]signalHandler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return ss<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) register(s os.Signal, handler signalHandler) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[s]; !found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[s] = handler<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func (set *signalSet) handle(sig os.Signal, arg interface{}) (err error) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if _, found := set.m[sig]; found {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set.m[sig](sig, arg)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return fmt.Errorf(&quot;No handler available for signal %v&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; panic(&quot;won&#39;t reach here&quot;)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;unknown signal received: %v, app exit unexpectedly\n&quot;, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>关于<a href="http://tonybai.com/tag/golang">Go语言</a>对系统Signal的处理，可以参考《<a href="http://tonybai.com/2012/09/21/signal-handling-in-go/">Go中的系统Signal处理</a>》一文。</p>
<p><b>三、制作测试用docker image</b></p>
<p>在《 <a href="http://tonybai.com/2014/09/26/install-docker-on-ubuntu-server-1404/">Ubuntu Server 14.04安装docker</a>》一文中，我们完成了在ubuntu 14.04上安装docker的步骤。要制作测试用docker image，我们首先需要pull一个base image。我们以CentOS6.5为例：</p>
<p>在Ubuntu 14.04上执行：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">sudo&nbsp; docker pull centos:centos6</font></p>
<p>docker会自动从<a href="https://registry.hub.docker.com">官方仓库</a>下载一个制作好的docker image。下载成功后，我们可以run一下试试，像这样：</p>
<p><font face="Courier New">$&gt; sudo docker run -t -i centos:centos6 /bin/bash</font></p>
<p>我们查看一下CentOS6的小版本：<br />
	<font face="Courier New">$&gt; cat /etc/centos-release<br />
	CentOS release 6.5 (Final)</font></p>
<p>这是一个极其精简的CentOS，各种工具均未安装：<br />
	<font face="Courier New">bash-4.1# telnet<br />
	bash: telnet: command not found<br />
	bash-4.1# ssh<br />
	bash: ssh: command not found<br />
	bash-4.1# ftp<br />
	bash: ftp: command not found<br />
	bash-4.1# echo $PATH<br />
	/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</font></p>
<p>如果你要安装一些必要的工具，可以直接使用yum install，默认的base image已经将yum配置好了，可以直接使用。如果通过公司代理访问外部网络，别忘了先export http_proxy。另外docker直接使用宿主机的/etc/resolv.conf作为容器的DNS，我们也无需额外设置DNS。</p>
<p>接下来，我们就制作我们的第一个测试用image。安装官方推荐的Best Practice，我们使用Dockerfile来bulid一个测试用image。步骤如下：</p>
<p>- 建立~/ImagesFactory目录<br />
	- 将构建好的dockerapp1拷贝到~/ImagesFactory目录下<br />
	- 进入~/ImagesFactory目录，创建Dockerfile文件，Dockerfile内容如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin<br />
	CMD /bin/dockerapp1</font></p>
<p>- 执行docker build，结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin<br />
	2014/10/09 16:05:25 lchown /var/lib/docker/aufs/mnt/fb0e864d3f07ca17ef8b6b69f034728e1f1158fd3f9c83fa48243054b2f26958/bin/dockerapp1: not a directory</font></p>
<p>居然build失败，提示什么not a directory。于是各种Search，终于发现问题所在，原来是&ldquo;<font face="Courier New">COPY ./dockerapp1 /bin</font>&rdquo;这条命令错了，少了个&ldquo;/&rdquo;，将&quot; /bin&quot;改为&ldquo;/bin/&rdquo;就OK了，Docker真是奇怪啊，这块明显应该做得更兼容些。新的Dockerfile如下：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	COPY ./dockerapp1 /bin/<br />
	CMD /bin/dockerapp1</font></p>
<p>构建结果如下：</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v1&quot; ./<br />
	Sending build context to Docker daemon 7.496 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 20c3783c42ab<br />
	Removing intermediate container cab639ab4321<br />
	Step 3 : CMD /bin/dockerapp1<br />
	&nbsp;&#8212;&gt; Running in 31875d3c37f9<br />
	&nbsp;&#8212;&gt; 21a720a808a7<br />
	Removing intermediate container 31875d3c37f9<br />
	Successfully built 21a720a808a7</font></p>
<p><font face="Courier New">$ sudo docker images<br />
	REPOSITORY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TAG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VIRTUAL SIZE<br />
	test&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 21a720a808a7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 59 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 214.6 MB</font></p>
<p><b>四、第一个测试容器</b></p>
<p>我们基于image &quot;test:v1&quot;启动一个测试容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v1&quot;<br />
	daf3ae88fec23a31cde9f6b9a3f40057953c87b56cca982143616f738a84dcba</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	daf3ae88fec2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 17 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; condescending_sammet&nbsp;&nbsp; </font></p>
<p>通过docker run命令，我们基于image&quot;test:v1&quot;启动了一个容器。通过docker ps命令可以看到容器成功启动，容器id：<font face="Courier New">daf3ae88fec2，别名为：</font><font face="Courier New">condescending_sammet。</font></p>
<p><font face="Courier New">根据Dockerfile我们知道，容器启动后将执行&quot;/bin/dockerapp1&quot;这个程序，dockerapp1退出，容器即退出。 run命令的&quot;-d&quot;选项表示容器将以daemon的形式运行，我们在前台无法看到容器的输出。那么我们怎么查看容器的输出呢？我们可以通过 docker logs + 容器id的方式查看容器内应用的标准输出或标准错误。我们也可以进入容器来查看。</font></p>
<p><font face="Courier New">进入容器有多种方法，比如用sudo docker attach </font><font face="Courier New"><font face="Courier New">daf3ae88fec2</font>。attach后，就好比将daemon方式运行的容器 拿到了前台，你可以Ctrl + C一下，可以看到如下dockerapp1的输出:</font></p>
<p><font face="Courier New">^Chandle signal: interrupt</font></p>
<p><font face="Courier New">另外一种方式是利用nsenter工具进入我们容器的namespace空间。ubuntu 14.04下可以通过如下方式安装该工具：</font></p>
<p><font face="Courier New">$ wget <a class="moz-txt-link-freetext" href="https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz">https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz</a>; tar xzvf util-linux-2.24.tar.gz<br />
	$ cd util-linux-2.24<br />
	$ ./configure &#8211;without-ncurses &amp;&amp; make nsenter<br />
	$ sudo cp nsenter /usr/local/bin</font></p>
<p>安装后，我们通过如下方式即可进入上面的容器：</p>
<p><font face="Courier New">$ echo $(sudo docker inspect &#8211;format &quot;{{ .State.Pid }}&quot; daf3ae88fec2)<br />
	5494<br />
	$ sudo nsenter &#8211;target 5494 &#8211;mount &#8211;uts &#8211;ipc &#8211;net &#8211;pid<br />
	-bash-4.1# ps -ef<br />
	UID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PID&nbsp; PPID&nbsp; C STIME TTY&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TIME CMD<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:20 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 /bin/dockerapp1<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 16&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 -bash<br />
	root&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 27&nbsp;&nbsp;&nbsp; 16&nbsp; 0 09:32 ?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 00:00:00 ps -ef<br />
	-bash-4.1# </font></p>
<p>进入容器后通过ps命令可以看到正在运行的dockerapp1程序。在容器内，我们可以通过kill来测试dockerapp1的运行情况：</p>
<p><font face="Courier New">-bash-4.1# kill -s SIGINT 1</font></p>
<p>通过前面的attach窗口，我们可以看到dockerapp1输出:</p>
<p><font face="Courier New">handle signal: interrupt</font></p>
<p>如果你发送SIGTERM信号，那么dockerapp1将终止运行，容器也就停止了。</p>
<p><font face="Courier New">-bash-4.1# kill 1</font></p>
<p>attach窗口显示：</p>
<p><font face="Courier New">signal termiate received, app exit normally</font></p>
<p>我们可以看到容器启动后默认执行的时Dockerfile中的CMD命令，如果Dockerfile中有多行CMD命令，Docker在启动容器 时只会执行最后一条CMD命令。如果在docker run中指定了命令，docker则会执行命令行中的命令而不会执行dockerapp1，比如：</p>
<p><font face="Courier New">$ sudo docker run -t -i &quot;test:v1&quot; /bin/bash<br />
	bash-4.1# </font></p>
<p>这里我们看到直接执行的时bash，dockerapp1并未执行。</p>
<p><b>五、docker stop的行为</b></p>
<p>我们先来看看docker stop的manual：</p>
<p><font face="Courier New">$ sudo docker stop &#8211;help<br />
	Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]<br />
	Stop a running container by sending SIGTERM and then SIGKILL after a grace period<br />
	&nbsp; -t, &#8211;time=10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.</font></p>
<p>可以看出当我们执行docker stop时，docker会首先向容器内的当前主程序发送一个SIGTERM信号，用于容器内程序的退出。如果容器在收到SIGTERM后没有马上退出， 那么stop命令会在等待一段时间（默认是10s）后，再向容器发送SIGKILL信号，将容器杀死，变为退出状态。</p>
<p>我们来验证一下docker stop的行为。启动刚才那个容器：</p>
<p><font face="Courier New">$ sudo docker start daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p><font face="Courier New">attach到容器daf3ae88fec2<br />
	$ sudo docker attach daf3ae88fec2</font></p>
<p>新打开一个窗口，执行docker stop命令：<br />
	<font face="Courier New">$ sudo docker stop daf3ae88fec2<br />
	daf3ae88fec2</font></p>
<p>可以看到attach窗口输出：<br />
	<font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>通过docker ps查看，发现容器已经退出。</p>
<p>也许通过上面的例子还不能直观的展示stop命令的<b>两阶段行为</b>，因为dockerapp1收到SIGTERM后直接就退出 了，stop命令无需等待容器慢慢退出，也无需发送SIGKILL。我们改造一下dockerapp1这个程序。</p>
<p>我们复制一下dockerapp1.go为dockerapp2.go，编辑dockerapp2.go，将handler中对SIGTERM的 处理注释掉，其他不变：</p>
<p><font face="Courier New">handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;handle signal: %v\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;signal termiate received, app exit normally\n&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p>我们使用dockerapp2来构建一个新image：test:v2，将Dockerfile中得dockerapp1换成 dockerapp2即可。</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v2&quot; ./<br />
	Sending build context to Docker daemon 9.369 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp2 /bin/<br />
	&nbsp;&#8212;&gt; 27cd613a9bd7<br />
	Removing intermediate container 07c760b6223b<br />
	Step 3 : CMD /bin/dockerapp2<br />
	&nbsp;&#8212;&gt; Running in 1aac086452a7<br />
	&nbsp;&#8212;&gt; 82eb876fefd2<br />
	Removing intermediate container 1aac086452a7<br />
	Successfully built 82eb876fefd2</font></p>
<p>利用image &quot;test:v2&quot;创建一个容器来测试stop。</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v2&quot;<br />
	29f3ec1af3c355458cbbd802a5e8a53da28e9f51a56ce822c7bba2a772edceac</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	29f3ec1af3c3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 7 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 6 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; romantic_feynman&nbsp;</font>&nbsp;&nbsp;</p>
<p>Attach到这个容器并观察，在另外一个窗口stop该container。我们在attach窗口只看到如下输出：</p>
<p><font face="Courier New">handle signal: terminated</font></p>
<p>stop命令的执行没有立即返回，而是等待容器退出。等待10s后，容器退出，stop命令执行结束。从这个例子我们可以明显看出stop的两阶 段行为。</p>
<p>如果我们以<font face="Courier New">sudo docker run -i -t &quot;test:v1&quot; /bin/bash</font>形式启动容器，那stop命令会将SIGTERM发送给bash这个程序，即使你通过nsenter进入容 器，启动了dockerapp1，dockerapp1也不会收到SIGTERM，dockerapp1会随着容器的退出而被强行终止，就像被 kill -9了一样。</p>
<p><b>六、多进程容器服务</b>程序</p>
<p>上面无论是dockerapp1还是dockerapp2，都是一个单进程服务程序。如果我们在容器内执行一个多进程程序，我们该如何优雅退出 呢？我们先来编写一个多进程的服务程序dockerapp3：</p>
<p>在dockerapp1.go的基础上对main和sysSignalHandleDemo进行修改形成dockerapp3.go，修改后这两 个函数的代码如下：</p>
<p><font face="Courier New">//dockerapp3.go<br />
	&#8230; &#8230;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go sysSignalHandleDemo()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pid, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;err fork process, err: %v\n&quot;, err)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if pid == 0 {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am in child process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the child process wait<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;i am parent process, pid = %v\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;fork ok, childpid = %v\n&quot;, pid)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Hour) // make the main goroutine wait!<br />
	}</font></p>
<p><font face="Courier New">func sysSignalHandleDemo() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss := signalSetNew()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; handler := func(s os.Signal, arg interface{}) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: handle signal: %v\n&quot;, syscall.Getpid(), s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == syscall.SIGTERM {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: signal termiate received, app exit normally\n&quot;, syscall.Getpid())<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(0)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGINT, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR1, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGUSR2, handler)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ss.register(syscall.SIGTERM, handler)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan os.Signal)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var sigs []os.Signal<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for sig := range ss.m {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sigs = append(sigs, sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; signal.Notify(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sig := &lt;-c</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; err := ss.handle(sig, nil)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if err != nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%v: unknown signal received: %v, app exit unexpectedly\n&quot;, syscall.Getpid(), sig)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; os.Exit(1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p>dockerapp3利用fork创建了一个子进程，这样dockerapp3实际上是两个进程在运行，各自有自己的signal监听 goroutine，goroutine的处理逻辑是相同的。注意：由于Windows和Mac OS X不具备fork语义，因此在这两个平台上运行dockerapp3不会得到预期结果。</p>
<p>利用dockerapp3，我们创建image &quot;test:v3&quot;:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:v3&quot; ./<br />
	[sudo] password for tonybai:<br />
	Sending build context to Docker daemon 11.24 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai <a class="moz-txt-link-rfc2396E" href="mailto:bigwhite.cn@gmail.com">&lt;bigwhite.cn@gmail.com&gt;</a><br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : COPY ./dockerapp3 /bin/<br />
	&nbsp;&#8212;&gt; 6ccf97065853<br />
	Removing intermediate container 6d85fe241939<br />
	Step 3 : CMD /bin/dockerapp3<br />
	&nbsp;&#8212;&gt; Running in 75d76380992a<br />
	&nbsp;&#8212;&gt; c9e7bf361ed7<br />
	Removing intermediate container 75d76380992a<br />
	Successfully built c9e7bf361ed7</font></p>
<p>启动基于test:v3 image的容器：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:v3&quot;<br />
	781cecb4b3628cb33e1b104ea57e506ad5cb4a44243256ebd1192af86834bae6<br />
	$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	781cecb4b362&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:v3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/sh -c /bin/doc&nbsp;&nbsp; 5 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 4 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; insane_bohr&nbsp;&nbsp;&nbsp;</font>&nbsp;&nbsp;&nbsp;</p>
<p>通过docker logs查看dockerapp3的输出：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13</font></p>
<p>可以看出主进程pid为1，子进程pid为13。我们通过stop停止该容器：</p>
<p><font face="Courier New">$ sudo docker stop 781cecb4b362<br />
	781cecb4b362</font></p>
<p>再次通过docker logs查看：</p>
<p><font face="Courier New">$ sudo docker logs 781cecb4b362<br />
	i am parent process, pid = 1<br />
	fork ok, childpid = 13<br />
	i am in child process, pid = 13<br />
	1: handle signal: terminated<br />
	1: signal termiate received, app exit normally</font></p>
<p>我们可以看到主进程收到了stop发来的SIGTERM并退出，主进程的退出导致容器退出，导致子进程13也无法生存，并且没有优雅退出。而在非 容器状态下，子进程是可以被init进程接管的。</p>
<p>因此对于docker容器内运行的多进程程序，stop命令只会将SIGTERM发送给容器主进程，要想让其他进程也能优雅退出，需要在主进程与 其他进程间建立一种通信机制。在主进程退出前，等待其他子进程退出。待所有其他进程退出后，主进程再退出，容器停止。这样才能保证服务程序的优雅 退出。</p>
<p><b>七、容器内启动多个服务程序</b></p>
<p>虽说docker <a href="https://docs.docker.com/articles/dockerfile_best-practices/">best practice</a>建议一个container内只放置一个服务程序，但对已有的一些遗留系统，在架构没有做出重构之前，很可能会有在一个 container中部署两个以上服务程序的情况和需求。而docker Dockerfile只允许执行一个CMD，这种情况下，我们就需要借助类似supervisor这样的进程监控管理程序来启动和管理container 内的多个程序了。</p>
<p>下面我们来自制作一个基于centos:centos6的安装了supervisord以及两个服务程序的image。我们将dockerapp1拷贝一份，并将拷贝命名为dockerapp1-brother。下面是我们的Dockerfile：</p>
<p><font face="Courier New">FROM centos:centos6<br />
	MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	RUN yum install python-setuptools -y<br />
	RUN easy_install supervisor<br />
	RUN mkdir -p /var/log/supervisor<br />
	COPY ./supervisord.conf /etc/supervisord.conf<br />
	COPY ./dockerapp1 /bin/<br />
	COPY ./dockerapp1-brother /bin/<br />
	CMD ["/usr/bin/supervisord"]</font></p>
<p>supervisord的配置文件supervisord.conf内容如下：</p>
<p><font face="Courier New">; supervisor config file</font></p>
<p><font face="Courier New">[unix_http_server]<br />
	file=/var/run/supervisor.sock&nbsp;&nbsp; ; (the path to the socket file)<br />
	chmod=0700&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; sockef file mode (default 0700)</font></p>
<p><font face="Courier New">[supervisord]<br />
	logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)<br />
	pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)<br />
	childlogdir=/var/log/supervisor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ; (&#39;AUTO&#39; child log dir, default $TEMP)</font></p>
<p><font face="Courier New">[rpcinterface:supervisor]<br />
	supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface</font></p>
<p><font face="Courier New">[supervisorctl]<br />
	serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL&nbsp; for a unix socket</font></p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p><font face="Courier New">[program:dockerapp1]<br />
	command=/bin/dockerapp1<br />
	stdout_logfile=/tmp/dockerapp1.log<br />
	stopsignal=TERM<br />
	stopwaitsecs=10</font></p>
<p><font face="Courier New">[program:dockerapp1-brother]<br />
	command=/bin/dockerapp1-brother<br />
	stdout_logfile=/tmp/dockerapp1-brother.log<br />
	stopsignal=QUIT<br />
	stopwaitsecs=10</font></p>
<p>开始build镜像：<br />
	&nbsp;&nbsp;&nbsp; <font face="Courier New">$&gt; sudo docker build -t=&quot;test:supervisor-v1&quot; ./<br />
	&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; Successfully built d006b9ad10eb</font></p>
<p>基于该镜像，启动一个容器：<br />
	<font face="Courier New">$&gt; sudo docker run -d &quot;test:supervisor-v1&quot;<br />
	05ded2b898c90059d4c9b5c6ccc8603b6848ae767360c42bd9b36ff87fb4b9df</font></p>
<p>执行ps命令查看镜像id：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES</font></p>
<p>怎么回事？Container没有启动起来？</p>
<p><font face="Courier New">$ sudo docker ps -a<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	05ded2b898c9&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v1&nbsp;&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 22 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Exited (0) 21 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hungry_engelbart</font></p>
<p>通过ps -a查看，container启动是成功了，但是成功退出了。于是尝试查看一下log：</p>
<p><font face="Courier New">sudo docker logs 05ded2b898c9<br />
	/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;</font></p>
<p>似乎是supervisord转为daemon程序，容器主进程退出了，容器随之终止了。</p>
<p>看来容器内的supervisord不能以daemon形式运行，应该以前台形式run。修改一下supervisord.conf中得配置：</p>
<p>将<br />
	<font face="Courier New">[supervisord]<br />
	nodaemon=false</font></p>
<p>改为</p>
<p><font face="Courier New">[supervisord]<br />
	nodaemon=true</font></p>
<p>重新制作镜像:</p>
<p><font face="Courier New">$ sudo docker build -t=&quot;test:supervisor-v2&quot; ./<br />
	Sending build context to Docker daemon 13.12 MB<br />
	Sending build context to Docker daemon<br />
	Step 0 : FROM centos:centos6<br />
	&nbsp;&#8212;&gt; 68edf809afe7<br />
	Step 1 : MAINTAINER Tony Bai &lt;bigwhite.cn@gmail.com&gt;<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; c617b456934a<br />
	Step 2 : RUN yum install python-setuptools -y<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; e09c66a1ea8c<br />
	Step 3 : RUN easy_install supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9c8797e8c27e<br />
	Step 4 : RUN mkdir -p /var/log/supervisor<br />
	&nbsp;&#8212;&gt; Using cache<br />
	&nbsp;&#8212;&gt; 9bfc67f8517d<br />
	Step 5 : COPY ./supervisord.conf /etc/supervisord.conf<br />
	&nbsp;&#8212;&gt; 8c514f998363<br />
	Removing intermediate container 4a185856e6ed<br />
	Step 6 : COPY ./dockerapp1 /bin/<br />
	&nbsp;&#8212;&gt; 0317bd4914d3<br />
	Removing intermediate container ac5738380854<br />
	Step 7 : COPY ./dockerapp1-brother /bin/<br />
	&nbsp;&#8212;&gt; d89711888bdf<br />
	Removing intermediate container eadc9444e716<br />
	Step 8 : CMD ["/usr/bin/supervisord"]<br />
	&nbsp;&#8212;&gt; Running in aaa042ac3914<br />
	&nbsp;&#8212;&gt; 9655256bbfed<br />
	Removing intermediate container aaa042ac3914<br />
	Successfully built 9655256bbfed</font></p>
<p>有了前面的铺垫，这次build image瞬间完成。启动容器，查看容器启动状态，查看容器内supervisord的运行日志如下：</p>
<p><font face="Courier New">$ sudo docker run -d &quot;test:supervisor-v2&quot;<br />
	61916f1c82338b28ced101b6bde119e4afb7c7fa349b4332ed51a43a4586b1b9</font></p>
<p><font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	61916f1c8233&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test:supervisor-v2&nbsp;&nbsp; &quot;/usr/bin/supervisor&nbsp;&nbsp; 16 seconds ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 16 seconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; prickly_einstein</font></p>
<p><font face="Courier New">$ sudo docker logs 8eb3e9892e66</font></p>
<p><font face="Courier New">/usr/lib/python2.6/site-packages/supervisor-3.1.2-py2.6.egg/supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a &quot;-c&quot; argument specifying an absolute path to a configuration file for improved security.<br />
	&nbsp; &#39;Supervisord is running as root and it is searching &#39;<br />
	2014-10-09 14:36:02,334 CRIT Supervisor running as root (no user in config file)<br />
	2014-10-09 14:36:02,349 INFO RPC interface &#39;supervisor&#39; initialized<br />
	2014-10-09 14:36:02,349 CRIT Server &#39;unix_http_server&#39; running without any HTTP authentication checking<br />
	2014-10-09 14:36:02,349 INFO supervisord started with pid 1<br />
	2014-10-09 14:36:03,354 INFO spawned: &#39;dockerapp1&#39; with pid 14<br />
	2014-10-09 14:36:03,363 INFO spawned: &#39;dockerapp1-brother&#39; with pid 15<br />
	2014-10-09 14:36:04,368 INFO success: dockerapp1 entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)<br />
	2014-10-09 14:36:04,369 INFO success: dockerapp1-brother entered RUNNING state, process has stayed up for &gt; than 1 seconds (startsecs)</font></p>
<p>可以看到supervisord已经将dockerapp1和dockerapp1-brother启动起来了。</p>
<p>现在我们尝试停止容器，我们预期是supervisord在退出前通知dockerapp1和dockerapp1-brother先退出，我们可以通过 查看容器内的/tmp/dockerapp1.log和/tmp/dockerapp1-brother.log来确认supervisord是否做了通 知。</p>
<p><font face="Courier New">$ sudo docker stop 61916f1c8233<br />
	61916f1c8233</font></p>
<p><font face="Courier New">$ sudo docker logs 61916f1c8233<br />
	&#8230; &#8230;<br />
	2014-10-09 14:37:52,253 WARN received SIGTERM indicating exit request<br />
	2014-10-09 14:37:52,254 INFO waiting for dockerapp1, dockerapp1-brother to die<br />
	2014-10-09 14:37:52,254 INFO stopped: dockerapp1-brother (exit status 0)<br />
	2014-10-09 14:37:52,256 INFO stopped: dockerapp1 (exit status 0)</font></p>
<p>通过容器的log，我们看出supervisord是等待两个程序退出后才退出的，不过我们还是要看看两个程序的输出日志以最终确认。重新启动容器，通过nsenter进入到容器中。</p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p><font face="Courier New">-bash-4.1# vi /tmp/dockerapp1-brother.log</font></p>
<p><font face="Courier New">handle signal: terminated<br />
	signal termiate received, app exit normally</font></p>
<p>两个程序的标准输出日志证实了我们的预期。</p>
<p>BTW，在物理机上测试supervisord以daemon形式运行，当kill掉supervisord时，supervisord是不会通知其监控 和管理的程序退出的。只有在以non-daemon形式运行时，supervisord才会在退出前先通知下面的程序退出。如果在一段时间内下面程序没有 退出，supervisord在退出前会kill -9强制杀死这些程序的进程。</p>
<p>最后要说的时，在验证一些想法时，没有必要build image，我们可以直接将本地文件copy到容器中，下面是一个例子，我们将dockerapp1和dockerapp1-brother拷贝到镜像中：<br />
	<font face="Courier New">$ sudo docker ps<br />
	CONTAINER ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IMAGE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; COMMAND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CREATED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STATUS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PORTS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NAMES<br />
	4d8982bfccc7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; centos:centos6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;/bin/bash&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 26 minutes ago&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Up 26 minutes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sharp_thompson&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	$ sudo docker inspect -f &#39;{{.Id}}&#39; 4d8982bfccc7<br />
	4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4<br />
	$ sudo cp dockerapp1&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1<br />
	$ sudo cp dockerapp1-brother&nbsp; /var/lib/docker/aufs/mnt/4d8982bfccc79dea762b41f8a6f669bda1ec73c8881b6ca76e7a7917c62972c4/bin/dockerapp1-brother</font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/10/09/gracefully-shutdown-app-running-in-docker/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Golang Channel用法简编</title>
		<link>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/</link>
		<comments>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/#comments</comments>
		<pubDate>Mon, 29 Sep 2014 09:02:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Actor]]></category>
		<category><![CDATA[BestPractice]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[Concurrent]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[CSP]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[LXC]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[内存模型]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[布道师]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1551</guid>
		<description><![CDATA[在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&#34;Golang中 国&#34;，即golangtc.com去下载go 1.3.2版本。 Go这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&#8220;docker&#8221;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 Docker &#8211; 从入门到实践》。 据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在这里我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。 在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行，并放出诸多高质量资料，在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。 言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&#8220;舶来品&#8221;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。 一、Golang并发基础理论 Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&#8220;知道&#8221;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。 维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别： Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处： &#160;&#160;&#160; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。 &#160;&#160;&#160; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。 &#160;&#160;&#160; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。 二、Go Channel基本操作语法 Go Channel的基本操作语法如下： [...]]]></description>
			<content:encoded><![CDATA[<p>在进入正式内容前，我这里先顺便转发一则消息，那就是Golang 1.3.2已经正式发布了。国内的<a href="http://golangtc.com">golangtc</a>已经镜像了golang.org的安装包下载页面，国内go程序员与爱好者们可以到&quot;Golang中 国&quot;，即golangtc.com去下载go 1.3.2版本。</p>
<p><a href="http://golang.org">Go</a>这门语言也许你还不甚了解，甚至是完全不知道，这也有情可原，毕竟Go在<a href="http://www.tiobe.com">TIOBE</a>编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用<a href="http://docker.com"> <b>Docker</b></a>你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下，如 果没有涉及到&ldquo;docker&rdquo;字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作：《 <a href="https://www.gitbook.io/book/yeasy/docker_practice">Docker &#8211; 从入门到实践</a>》。</p>
<p>据我了解，目前国内试水Go语言开发后台系统的大公司与初创公司日益增多，比如七牛、京东、小米，盛大，金山，东软，搜狗等，在<a href="https://github.com/qiniu/go/issues/15#issuecomment-55568731">这里</a>我们可以看到一些公司的Go语言应用列表，并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中，不过目前来看多以Go入 门与基础为主题，Go idioms、tips或Best Practice的Share并不多见，想必国内的先行者、布道师们还在韬光养晦，积攒经验，等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台，比如Golang技术大会之类的的平台。</p>
<p>在国外，虽然Go也刚刚起步，但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多，除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外，由 <span style="font-family: Ubuntu, Tahoma, sans-serif; font-size: 14px; line-height: 20px;"><a href="http://gopheracademy.com/">Gopher Academy</a> 发起的</span><a href="http://www.gophercon.com">GopherCon</a> 会议也于今年第一次举行，并放出诸多高质量资料，在<a href="https://github.com/gophercon/2014-talks">这里</a>可以下载。欧洲的Go语言大会<a href="http://www.dotgo.eu/">.dotgo</a>也即将开幕，估计后续这两个大会将撑起Golang技术分享 的旗帜。</p>
<p>言归正传，这里要写的东西并非原创，自己的Go仅仅算是入门级别，工程经验、Best Practice等还谈不上有多少，因此这里主要是针对GopherCon2014上的&ldquo;舶来品&rdquo;的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验，这里针对其分享的内容，记录一些学习体会和理解，并结合一些外延知识，也可以算是一种学习笔记吧，仅供参考。</p>
<p><b>一、Golang并发基础理论</b></p>
<p>Golang在并发设计方面参考了C.A.R Hoare的CSP，即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样，多数Golang程序员或爱好者仅仅停留在&ldquo;知道&rdquo;这一层次，理解CSP理论的并不多，毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从<a href="http://www.usingcsp.com">这里</a>下载到CSP论文的最新版本。</p>
<p><a href="http://en.wikipedia.org/wiki/Communicating_sequential_processes">维基百科</a>中概要罗列了CSP模型与另外一种并发模型Actor模型的区别：</p>
<p>Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言，又有一些根本上的不同之处：<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型处理过程是匿名的，而Actor模型中的Actor则具有身份标识。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP模型的消息传递在收发消息进程间包含了一个交会点，即发送方只能在接收方准备好接收消息时才能发送消息。相反，actor模型中的消息传递是异步 的，即消息的发送和接收无需在同一时间进行，发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下，基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。<br />
	&nbsp;&nbsp;&nbsp; &#8211; CSP使用显式的Channel用于消息传递，而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下，进程可 以从一个实际上拥有身份标识的channel接收消息，而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。</p>
<p><b>二、Go Channel基本操作语法</b></p>
<p>Go Channel的基本操作语法如下：</p>
<p><font face="Courier New">c := make(chan bool) //创建一个无缓冲的bool型Channel <br />
	c &lt;- x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //向一个Channel发送一个值<br />
	&lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从一个Channel中接收一个值<br />
	x = &lt;- c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从Channel c接收一个值并将其存储到x中<br />
	x, ok = &lt;- c&nbsp; //从Channel接收一个值，如果channel关闭了或没有数据，那么ok将被置为false</font></p>
<p><i>不带缓冲的Channel</i>兼具通信和同步两种特性，颇受青睐。</p>
<p><b>三、Channel用作信号(Signal)的场景</b></p>
<p>1、等待一个事件(Event)</p>
<p><font face="Courier New">等待一个事件，有时候通过close一个Channel就足够了。例如：</font></p>
<p><font face="Courier New">//testwaitevent1.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin doing something!&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Doing something&#8230;&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>close(c)</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <i>&lt;-c</i><br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done!&quot;)<br />
	}</font></p>
<p>这里main goroutine通过&quot;<font face="Courier New">&lt;-c</font>&quot;来等待sub goroutine中的&ldquo;完成事件&rdquo;，sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。</p>
<p>关于输出结果：</p>
<p>根据《<a href="http://golang.org/ref/mem">Go memory model</a>》中关于close channel与recv from channel的order的定义：<span style="color: rgb(34, 34, 34); font-family: Helvetica, Arial, sans-serif; font-size: 16px; font-style: italic; line-height: normal;"><font face="Courier New">The closing of a channel happens before a receive that returns a zero value because the channel is closed.</font></span></p>
<p>我们可以很容易判断出上面程序的输出结果：</p>
<p><font face="Courier New">Begin doing something!<br />
	Doing something&#8230;<br />
	Done!</font></p>
<p>如果将<font face="Courier New">close(c)</font>换成<font face="Courier New">c&lt;-true</font>，则根据《Go memory model》中的定义：<font face="Courier New"><span style="color: rgb(34, 34, 34); font-size: 16px; font-style: italic; line-height: normal;">A receive from an unbuffered channel happens before the send on that channel completes.</span></font><br />
	&quot;<font face="Courier New">&lt;-c</font>&quot;要先于&quot;<font face="Courier New">c&lt;-true</font>&quot;完成，但也不影响日志的输出顺序，输出结果仍为上面三行。</p>
<p>2、协同多个Goroutines</p>
<p>同上，close channel还可以用于协同多个Goroutines，比如下面这个例子，我们创建了100个Worker Goroutine，这些Goroutine在被创建出来后都阻塞在&quot;<font face="Courier New">&lt;-start&quot;</font>上，直到我们在main goroutine中给出<u>开工</u>的信号：&quot;<font face="Courier New">close(start)&quot;</font>，这些goroutines才开始真正的并发运行起来。</p>
<p><font face="Courier New">//testwaitevent2.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func worker(start chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-start<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;This is Worker:&quot;, index)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; start := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go worker(start, i)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(start)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {} //deadlock we expected<br />
	}</font></p>
<p>3、Select</p>
<p>【select的基本操作】<br />
	select是Go语言特有的操作，使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。</p>
<p><font face="Courier New">select {<br />
	case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp; // </font><font face="Courier New"><font face="Courier New">检查ok值判断someOtherchan是否已经关闭</font></font></p>
<p><font face="Courier New">case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">default:<br />
	&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	}</font></p>
<p>【惯用法：for/select】</p>
<p>我们在使用select时很少只是对其进行一次evaluation，我们常常将其与for {}结合在一起使用，并选择适当时机从for{}中退出。</p>
<p><font face="Courier New">for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;- somechan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用x进行一些操作</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case y, ok := &lt;- someOtherchan:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 使用y进行一些操作，<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 检查ok值判断someOtherchan是否已经关闭</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case outputChan &lt;- z:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; z值被成功发送到Channel上时</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; 上面case均无法通信时，执行此分支<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>【终结workers】</p>
<p>下面是一个常见的终结sub worker goroutines的方法，每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。</p>
<p><font face="Courier New">//testterminateworker1.go</font><br />
	<font face="Courier New">package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; &quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool, index int) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker:&quot;, index)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; for i := 1; i &lt;= 100; i++ {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; go worker(die, i)<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp; close(die)<br />
	&nbsp;&nbsp;&nbsp; select {} </font><font face="Courier New"><font face="Courier New">//deadlock we expected</font><br />
	}</font></p>
<p>【终结验证】</p>
<p>有时候终结一个worker后，main goroutine想确认worker routine是否真正退出了，可采用下面这种方法：</p>
<p><font face="Courier New">//testterminateworker2.go<br />
	package main</font></p>
<p><font face="Courier New">import (<br />
	&nbsp;&nbsp;&nbsp; &quot;fmt&quot;<br />
	&nbsp;&nbsp;&nbsp; //&quot;time&quot;<br />
	)</font></p>
<p><font face="Courier New">func worker(die chan bool) {<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Begin: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //case xx：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; //做事的分支<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case &lt;-die:<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; fmt.Println(&quot;Done: This is Worker&quot;)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp; die := make(chan bool)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; go worker(die)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp; die &lt;- true<br />
	&nbsp;&nbsp;&nbsp; &lt;-die<br />
	&nbsp;&nbsp;&nbsp; fmt.Println(&quot;Worker goroutine has been terminated&quot;)<br />
	}</font></p>
<p>【关闭的Channel永远不会阻塞】</p>
<p>下面演示在一个已经关闭了的channel上读写的结果：</p>
<p><font face="Courier New">//testoperateonclosedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb := make(chan bool)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(cb)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, x)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x, ok := &lt;-cb<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v %#v\n&quot;, x, ok)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ci := make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(ci)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; y := &lt;-ci<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%#v\n&quot;, y)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cb &lt;- true<br />
	}</font></p>
<p><font face="Courier New">$go run </font><font face="Courier New"><font face="Courier New">testoperateonclosedchannel.go</font><br />
	false<br />
	false false<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看到在一个已经close的unbuffered channel上执行读操作，回返回channel对应类型的零值，比如bool型channel返回false，int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。</p>
<p>【关闭带缓存的channel】</p>
<p>将unbuffered channel换成buffered channel会怎样？我们看下面例子：</p>
<p><font face="Courier New">//testclosedbufferedchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c := make(chan int, 3)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 15<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 34<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 65<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%d\n&quot;, &lt;-c)</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testclosedbufferedchannel.go<br />
	15<br />
	34<br />
	65<br />
	0<br />
	panic: runtime error: send on closed channel</font></p>
<p>可以看出带缓冲的channel略有不同。尽管已经close了，但我们依旧可以从中读出关闭前写入的3个值。第四次读取时，则会返回该channel类型的零值。向这类channel写入操作也会触发panic。</p>
<p>【range】</p>
<p>Golang中的range常常和channel并肩作战，它被用来从channel中读取所有值。下面是一个简单的实例：</p>
<p><font face="Courier New">//testrange.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func generator(strings chan string) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;Five hour&#39;s New York jet lag&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;and Cayce Pollard wakes in Camden Town&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;to the dire and ever-decreasing circles&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings &lt;- &quot;of disrupted circadian rhythm.&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(strings)<br />
	}</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strings := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go generator(strings)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for s := range strings {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;%s\n&quot;, s)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Printf(&quot;\n&quot;)<br />
	}</font></p>
<p><b>四、隐藏状态</b></p>
<p>下面通过一个例子来演示一下channel如何用来隐藏状态：</p>
<p>1、例子：唯一的ID服务</p>
<p><font face="Courier New">//testuniqueid.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;</font></p>
<p><font face="Courier New">func newUniqueIDService() &lt;-chan string {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := make(chan string)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var counter int64 = 0<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id &lt;- fmt.Sprintf(&quot;%x&quot;, counter)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; counter += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return id<br />
	}<br />
	func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id := newUniqueIDService()<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for i := 0; i &lt; 10; i++ {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&lt;-id)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	}</font></p>
<p><font face="Courier New">$ go run testuniqueid.go<br />
	0<br />
	1<br />
	2<br />
	3<br />
	4<br />
	5<br />
	6<br />
	7<br />
	8<br />
	9</font></p>
<p>newUniqueIDService通过一个channel与main goroutine关联，main goroutine无需知道uniqueid实现的细节以及当前状态，只需通过channel获得最新id即可。</p>
<p><b>五、默认情况</b></p>
<p>我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。</p>
<p>1、select&nbsp; for non-blocking receive</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列</font></p>
<p><font face="Courier New">select {<br />
	case b = &lt;-idle:  //尝试从idle队列中读取<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	default:&nbsp; //队列空，分配一个新的buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; makes += 1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b = make([]byte, size)<br />
	}</font></p>
<p>2、select for non-blocking send</p>
<p><font face="Courier New">idle:= make(chan []byte, 5) </font><font face="Courier New"><font face="Courier New">//用一个带缓冲的channel构造一个简单的队列</font></font></p>
<p><font face="Courier New">select {<br />
	case idle &lt;- b: //尝试向队列中插入一个buffer<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230;<br />
	default: //队列满？</font></p>
<p><font face="Courier New">}</font></p>
<p><b>六、Nil Channels</b></p>
<p>1、nil channels阻塞</p>
<p>对一个没有初始化的channel进行读写操作都将发生阻塞，例子如下：</p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;-c<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p><font face="Courier New">package main</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c chan int<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c &lt;- 1<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	fatal error: all goroutines are asleep &#8211; deadlock!</font></p>
<p>2、nil channel在select中很有用</p>
<p>看下面这个例子：</p>
<p><font face="Courier New">//testnilchannel_bad.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p>我们原本期望程序交替输出5和7两个数字，但实际的输出结果却是：</p>
<p><font face="Courier New">5<br />
	0<br />
	0<br />
	0<br />
	&#8230; &#8230; 0死循环</font></p>
<p>再仔细分析代码，原来select每次按case顺序evaluate：<br />
	&nbsp;&nbsp;&nbsp; &#8211; 前5s，select一直阻塞；<br />
	&nbsp;&nbsp;&nbsp; &#8211; 第5s，c1返回一个5后被close了，&ldquo;case x := &lt;-c1&rdquo;这个分支返回，select输出5，并重新select<br />
	&nbsp;&nbsp;&nbsp; &#8211; 下一轮select又从&ldquo;case x := &lt;-c1&rdquo;这个分支开始evaluate，由于c1被close，按照前面的知识，close的channel不会阻塞，我们会读出这个 channel对应类型的零值，这里就是0；select再次输出0；这时即便c2有值返回，程序也不会走到c2这个分支<br />
	&nbsp;&nbsp;&nbsp; &#8211; 依次类推，程序无限循环的输出0</p>
<p>我们利用nil channel来改进这个程序，以实现我们的意图，代码如下：</p>
<p><font face="Courier New">//testnilchannel.go<br />
	package main</font></p>
<p><font face="Courier New">import &quot;fmt&quot;<br />
	import &quot;time&quot;</font></p>
<p><font face="Courier New">func main() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var c1, c2 chan int = make(chan int), make(chan int)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 5)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 &lt;- 5<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c1)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; go func() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; time.Sleep(time.Second * 7)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 &lt;- 7<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close(c2)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }()</font></p>
<p><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c1 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case x, ok := &lt;-c2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if !ok {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c2 = nil<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(x)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c1 == nil &amp;&amp; c2 == nil {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fmt.Println(&quot;over&quot;)<br />
	}</font></p>
<p><font face="Courier New">$go run testnilchannel.go<br />
	5<br />
	7<br />
	over</font></p>
<p>可以看出：通过将已经关闭的channel置为nil，下次select将会阻塞在该channel上，使得select继续下面的分支evaluation。</p>
<p><b>七、Timers</b></p>
<p>1、超时机制Timeout</p>
<p>带超时机制的select是常规的tip，下面是示例代码，实现30s的超时select：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timeout := time.After(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- timeout:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p>2、心跳HeartBeart</p>
<p>与timeout实现类似，下面是一个简单的心跳select实现：</p>
<p><font face="Courier New">func worker(start chan bool) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; heartbeat := time.Tick(30 * time.Second)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; select {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // &#8230; do some stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case &lt;- heartbeat:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&#8230; do heartbeat stuff<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	} </font></p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/09/29/a-channel-compendium-for-golang/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Cocos2d-x集成Amazon内购和GameCircle服务</title>
		<link>https://tonybai.com/2014/08/04/amazon-inapp-purchasing-and-gamecirle-in-cocos2dx/</link>
		<comments>https://tonybai.com/2014/08/04/amazon-inapp-purchasing-and-gamecirle-in-cocos2dx/#comments</comments>
		<pubDate>Mon, 04 Aug 2014 10:30:59 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Amazon]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[AppStore]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Cocos2d-x]]></category>
		<category><![CDATA[Eclipse]]></category>
		<category><![CDATA[Game]]></category>
		<category><![CDATA[GameCircle]]></category>
		<category><![CDATA[GooglePlay]]></category>
		<category><![CDATA[In-App-Purchasing]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[LeaderBoards]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[亚马逊]]></category>
		<category><![CDATA[内购]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[引擎]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[游戏]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1544</guid>
		<description><![CDATA[由于种种原因，这篇文章已经拖延了N多时间了。今天花了些时间把如何在Cocos2d-x(我用的版本是2.2.2)游戏中集成Amazon的内购和GameCircle服务(仅适用于Android版本)整理一下，发出来，作备忘。 之前在做&#8220;手指足球世界杯2014&#8221;时，想给这款小游戏加上内购(In-App Purchasing)和积分榜(ScoreBoard)功能。说到Android手机游戏的内购，人们第一时间想到的就是Google Play，不过悲催的是，Google Play在国内各种无法访问，行货机也不预装，其相关Service的测试十分困难，翻看了一些集成Google Game Service的文章，其过程坎坷之程度让人望而却步。于是我将目光转而投向了Amazon Game Service。亚马逊的游戏服务起步要晚些，成熟性肯定不如Google，但在国内来说也不失为另一个不错的选择，Google虽好，但访问不了有啥 法。但似乎国内同行使用Amazon游戏服务的并不多，度娘上相关中文资料甚少。但从Amazon发布的数据来看，其市场正在逐步扩大，并紧紧跟随 Google Play的脚步。 之前用kindle paperwhite时在amazon.com上注册了一个国际帐号，这次正好用这个。不过你要使用Amazon的Game Service，普通Amazon帐号是不行的。你要升级为Amazon的Developer。申请Developer帐号的过程还是蛮繁琐的，要提交一 堆资料，具体细节我大致忘的差不多了，这里就不说了。按照Amazon网站的提示一步一步做就是了。 有了帐号后，你可以下载Amazon的Game SDK了，这个包有近50M大小，本地解压后可以看到其提供的Android SDK种类： AmazonSDK/Android$ ls Ads&#160; AmazonInsights&#160; DeviceMessaging&#160; GameCircle&#160; InAppPurchasing&#160; LoginWithAmazon&#160; Maps&#160; MobileAssociates&#160; README.txt Ads我之前用的是Google Admob，这里就不再用Amazon的了，我需要的是这里的InAppPurchasing和GameCircle。我们接下来一个一个来说。 * Amazon InAppPurchasing Amazon支持三种内购类型：Consumables、Entitlements和Subscriptions： &#160;&#160;&#160; Consumables就像游戏中的红心、金币等，用户可以多次购买，每次可以买多个，并根据游戏规则，每次消耗若干个以达到某种游戏目的；在哪台设备上购买，就只能在哪台设备上使用。 &#160;&#160;&#160; Entitlements是某种授权协议，一个用户只需购买一次，即可长期使用某种特权功能，并与设备无关，可在多个设备下授权使用。比如鳄鱼洗澡游戏中购买高级关卡等。 &#160;&#160;&#160; Subscriptions有订阅的意思，需要某种Entitlements或某种访问权，在一定时间段内绑定有效，到期后自动renew，比如某种杂志的阅读权等。 我只想给游戏增加一些红心功能，一颗红心，可以让游戏者有一次续命的机会，因此我需要实现Consumables型内购。Amazon SDK中提供了Consumeables类内购的Android范例AmazonSDK/Android/InAppPurchasing/samples/SampleIAPConsumablesApp。我们可以参考这个例子来实现我的&#34;红心内购&#34;。 &#160;&#160;&#160; 1、添加依赖的jar包 &#160;&#160;&#160; 在你的游戏proj中添加内购功能所依赖的Amazon SDK jar包，包括AmazonInsights-android-sdk-2.1.26.jar、in-app-purchasing-1.0.3.jar 和login-with-amazon-sdk.jar。 &#160;&#160;&#160; 2、添加源文件 &#160;&#160;&#160; 参照例子，将AppPurchasingObserver.java、AppPurchasingObserverListener.java和MySKU.java拷贝到你的与XXActivity.java同级目录下。 [...]]]></description>
			<content:encoded><![CDATA[<p>由于种种原因，这篇文章已经拖延了N多时间了。今天花了些时间把如何在<a href="http://cocos2d-x.org/">Cocos2d-x</a>(我用的版本是2.2.2)游戏中集成<a href="https://developer.amazon.com/public">Amazon</a>的<a href="https://developer.amazon.com/public/apis/earn/in-app-purchasing">内购</a>和<a href="https://developer.amazon.com/public/apis/engage/gamecircle">GameCircle</a>服务(仅适用于Android版本)整理一下，发出来，作备忘。</p>
<p>	之前在做&ldquo;<a href="http://iwobi.net">手指足球世界杯2014</a>&rdquo;时，想给这款小游戏加上内购(In-App Purchasing)和积分榜(ScoreBoard)功能。说到Android手机游戏的内购，人们第一时间想到的就是<a href="http://play.google.com">Google Play</a>，不过悲催的是，Google Play在国内各种无法访问，行货机也不预装，其相关Service的测试十分困难，翻看了一些集成Google Game Service的文章，其过程坎坷之程度让人望而却步。于是我将目光转而投向了Amazon Game Service。亚马逊的游戏服务起步要晚些，成熟性肯定不如Google，但在国内来说也不失为另一个不错的选择，Google虽好，但访问不了有啥 法。但似乎国内同行使用Amazon游戏服务的并不多，度娘上相关中文资料甚少。但从Amazon发布的数据来看，其市场正在逐步扩大，并紧紧跟随 Google Play的脚步。</p>
<p>	之前用kindle paperwhite时在amazon.com上注册了一个国际帐号，这次正好用这个。不过你要使用Amazon的Game Service，普通Amazon帐号是不行的。你要升级为Amazon的Developer。申请Developer帐号的过程还是蛮繁琐的，要提交一 堆资料，具体细节我大致忘的差不多了，这里就不说了。按照Amazon网站的提示一步一步做就是了。</p>
<p>	有了帐号后，你可以下载Amazon的Game SDK了，这个包有近50M大小，本地解压后可以看到其提供的Android SDK种类：</p>
<p>	<font face="Courier New">AmazonSDK/Android$ ls<br />
	Ads&nbsp; AmazonInsights&nbsp; DeviceMessaging&nbsp; GameCircle&nbsp; InAppPurchasing&nbsp; LoginWithAmazon&nbsp; Maps&nbsp; MobileAssociates&nbsp; README.txt</font></p>
<p>	Ads我之前用的是Google Admob，这里就不再用Amazon的了，我需要的是这里的InAppPurchasing和GameCircle。我们接下来一个一个来说。</p>
<p>	<b>* Amazon InAppPurchasing</b></p>
<p>	Amazon支持三种内购类型：Consumables、Entitlements和Subscriptions：<br />
	&nbsp;&nbsp;&nbsp; Consumables就像游戏中的红心、金币等，用户可以多次购买，每次可以买多个，并根据游戏规则，每次消耗若干个以达到某种游戏目的；在哪台设备上购买，就只能在哪台设备上使用。<br />
	&nbsp;&nbsp;&nbsp; Entitlements是某种授权协议，一个用户只需购买一次，即可长期使用某种特权功能，并与设备无关，可在多个设备下授权使用。比如鳄鱼洗澡游戏中购买高级关卡等。<br />
	&nbsp;&nbsp;&nbsp; Subscriptions有订阅的意思，需要某种Entitlements或某种访问权，在一定时间段内绑定有效，到期后自动renew，比如某种杂志的阅读权等。</p>
<p>	我只想给游戏增加一些红心功能，一颗红心，可以让游戏者有一次续命的机会，因此我需要实现Consumables型内购。Amazon SDK中提供了Consumeables类内购的Android范例<font face="Courier New">AmazonSDK/Android/InAppPurchasing/samples/SampleIAPConsumablesApp</font>。我们可以参考这个例子来实现我的&quot;红心内购&quot;。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>1、添加依赖的jar包</i><br />
	&nbsp;&nbsp;&nbsp; 在你的游戏proj中添加内购功能所依赖的Amazon SDK jar包，包括AmazonInsights-android-sdk-2.1.26.jar、in-app-purchasing-1.0.3.jar 和login-with-amazon-sdk.jar。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>2、添加源文件</i><br />
	&nbsp;&nbsp;&nbsp; 参照例子，将AppPurchasingObserver.java、AppPurchasingObserverListener.java和MySKU.java拷贝到你的与XXActivity.java同级目录下。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>3、初始化Amazon IAP</i><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 在你的XXActivity类中添加如下方法：</p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; public PurchaseDataStorage purchaseDataStorage;</p>
<p>	&nbsp;&nbsp;&nbsp; private void setupIAPOnCreate() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; purchaseDataStorage = new PurchaseDataStorage(this);</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AppPurchasingObserver purchasingObserver<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = new AppPurchasingObserver(this, purchaseDataStorage);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; purchasingObserver.setListener(this);</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onCreate: registering AppPurchasingObserver&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchasingManager.registerObserver(purchasingObserver);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; protected void onCreate(Bundle savedInstanceState){<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setupIAPOnCreate();<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <font face="Courier New">protected void onResume() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.onResume();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onResume: call initiateGetUserIdRequest&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchasingManager.initiateGetUserIdRequest();</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onResume: call initiateItemDataRequest for skus: &quot;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; + MySKU.getAll());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchasingManager.initiateItemDataRequest(MySKU.getAll());<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp;<i> 4、添加购买方法</i></p>
<p>	&nbsp;&nbsp;&nbsp; 在Cocos2d-x的某个Scene或Layer中实现的购买方法事件的callback，后者通过Jni调用Java静态方法：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">void BuyHeartScene::buyHearts(int number) {<br />
	#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)<br />
	&nbsp;&nbsp;&nbsp; JniMethodInfo t;<br />
	&nbsp;&nbsp;&nbsp; if (JniHelper::getStaticMethodInfo(t, &quot;net/iwobi/game/flickworldcup/FlickWorldCupActivity&quot;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;onBuyHeartClick&quot;, &quot;(I)V&quot;)) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;CallStaticVoidMethod(t.classID, t.methodID, number);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (t.env-&gt;ExceptionOccurred()) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionDescribe();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;ExceptionClear();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; t.env-&gt;DeleteLocalRef(t.classID);<br />
	&nbsp;&nbsp;&nbsp; }<br />
	#endif<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; 该Java方法的实现如下(我这里有五种商品ONEHEART到FIVEHEART)：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public static void onBuyHeartClick(int type) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String requestId;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch (type) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 1:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.ONEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 2:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.TWOHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 3:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.THREEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 4:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.FOURHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case 5:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.FIVEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; requestId = PurchasingManager.initiatePurchaseRequest(MySKU.ONEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; PurchaseData purchaseData = ((FlickWorldCupActivity)context).purchaseDataStorage<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; .newPurchaseData(requestId);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onBuyHeartClick: requestId (&quot; + requestId<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; + &quot;) requestState (&quot; + purchaseData.getRequestState() + &quot;)&quot;);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; <i>5、修改各种回调方法</i></p>
<p>	&nbsp;&nbsp;&nbsp; 将SampleIAPConsumablesApp/src/com/amazon/sample/iap/consumable /MainActivity.java中的onPurchase为前缀名的方法以及onGetUserIdResponseSuccessful挪到你的 Activity源文件中。这些方法绝大部分是不需要修改的，除非你不喜欢例子中日志输出的格式，或是想用toast之类的提示方式改造各种 callback的结果显示方式。</p>
<p>	&nbsp;&nbsp;&nbsp; 这里我主要修改了一个方法：onPurchaseResponseSuccess。该方法在购买成功后被调用，我们在这个事件发生时更新Scene或Layer的显示(updateHeartInScene)。</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">@Override<br />
	&nbsp;&nbsp;&nbsp; public void onPurchaseResponseSuccess(String userId, String sku,<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; String purchaseToken) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onPurchaseResponseSuccess: for userId (&quot; + userId<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; + &quot;) sku (&quot; + sku + &quot;)&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUData skuData = purchaseDataStorage.getSKUData(sku);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (skuData == null)<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return;</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.ONEHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(1);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.TWOHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(2);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.THREEHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(3);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.FOURHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(4);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (MySKU.FIVEHEART.getSku().equals(skuData.getSKU())) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; updateHeartInScene(5);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; <i>6、AndroidManifest.xml和其他Java文件</i></p>
<p>	&nbsp;&nbsp;&nbsp; AppPurchasingObserver.java和AppPurchasingObserverListener.java你可以原封不动的使用。MySKU.java可以根据你的内购项目做改造：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public enum MySKU {</p>
<p>	&nbsp;&nbsp;&nbsp; ONEHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.oneheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; TWOHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.twoheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; THREEHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.threeheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; FOURHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.fourheart&quot;, 1),<br />
	&nbsp;&nbsp;&nbsp; FIVEHEART(&quot;net.iwobi.game.flickworldcup.iap.consumable.fiveheart&quot;, 1);<br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; private String sku;<br />
	&nbsp;&nbsp;&nbsp; private int quantity;</p>
<p>	&nbsp;&nbsp;&nbsp; private MySKU(String sku, int quantity) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.sku = sku;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; this.quantity = quantity;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static MySKU valueForSKU(String sku) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (ONEHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return ONEHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (TWOHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return TWOHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (THREEHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return THREEHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (FOURHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return FOURHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (FIVEHEART.getSku().equals(sku)) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return FIVEHEART;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return null;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public String getSku() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return sku;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public int getQuantity() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return quantity;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; private static Set&lt;String&gt; SKUS = new HashSet&lt;String&gt;();<br />
	&nbsp;&nbsp;&nbsp; static {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(ONEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(TWOHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(THREEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(FOURHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; SKUS.add(FIVEHEART.getSku());<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;&nbsp;&nbsp; public static Set&lt;String&gt; getAll() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return SKUS;<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>	&nbsp;}</p>
<p>	&nbsp;AndroidManifest.xml中在application标签下添加如下配置：<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &lt;receiver android:name=&quot;com.amazon.inapp.purchasing.ResponseReceiver&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.inapp.purchasing.NOTIFY&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:permission=&quot;com.amazon.inapp.purchasing.Permission.NOTIFY&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/receiver&gt;<br />
	&nbsp;有了以上代码，我们的内购就可以运行起来了。</font><b>&nbsp;&nbsp;&nbsp;</p>
<p>	* 内购测试</b></p>
<p>	使用Amazon In-app Purchasing API一个最大好处就是测试简单。Amazon提供一个本地测试程序Amazon App Tester（安装到Android模拟器中），可以模拟内购Server，SDK自动判断当前场景，如果是测试，你的集成了内购SDK的游戏将连接本地 测试程序完成内购流程。通过在本地测试程序中设置模拟不同的内购流程，我们可以轻松完成测试。</p>
<p>	你需要给Amazon App Tester提供一个名为amazon.sdktester.json的文件，这样Amazon App Tester可以知道你的游戏有哪些内购项目，并模拟出这些内购项目。这个json文件可以自行编辑，也可以在Amazon deveoper网站上生成下载。</p>
<p>	我直接将内购项目添加到我的Amazon帐号的游戏应用下面，一共五个，添加成功后，下载json文件。将该文件放在模拟器的/mnt/sdcard下，绝对路径为/mnt/sdcard/amazon.sdktester.json。</p>
<p>	之后，启动App Tester，再启动你的游戏，点击内购项目，看看是否能购买成功。</p>
<p>	<b>* 内购上线</b></p>
<p>	按照Amazon官方说法，SDK会自动区分测试场景和正式场景，因此通过App Tester测试的游戏在发布后，理论上内购是没有问题的。不过我上线后还是遇到了问题，即点击购买某个项目后，游戏没有任何反应，等了若干分钟都是这 样。我将这个问题反馈给Amazon Support，得到的答复居然是游戏代码没有问题，他们测试了若干中机型，都可以打开内购页面，并进行内购。只是有时内购页面打开有些延迟，但都能打 开。看到这里，我猜是否又是大陆网络的问题呢！不管它了，至少通过Amazon Support的回复可以证明我的代码是ok的。只能希望美国人民多多购买我的内购项目了^_^。</p>
<p>	<b>* Amazon游戏圈</b></p>
<p>	想给游戏增加成就榜和成就提交功能，如果自己实现服务端，显然麻烦，工作量大不说，还得维护一个Server。但市面上提供这类服务的游戏平台不多。 Google Play的游戏Service提供这种服务，不过还是上面提到的原因，我与Google的这个服务无缘啊。Amazon Game SDK后期推出了GameCircle服务。</p>
<p>	GameCircle目前提供achievements, leaderboards和Whispersync三种特性：<br />
	&nbsp;&nbsp;&nbsp; achievements就是奖励机制，帮助游戏提高玩家粘性。<br />
	&nbsp;&nbsp;&nbsp; leaderboards类似于积分榜，可以用于提交玩家积分以及显示玩家的全球排名。<br />
	&nbsp;&nbsp;&nbsp; Whispersync是一种数据游戏同步服务，同步玩家进度，保寸玩家个性化数据等。</p>
<p>	这里我要用到的是leaderboards。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>1、建立GameCircle</i><br />
	&nbsp;&nbsp;&nbsp; 使用游戏圈前，你需要在Amazon官方的Amazon Apps &amp; Services Developer Console下创建属于你的Game Circle，然后创建一个LeaderBoard，设置LeaderBoard属性。SDK中提供了GameCircle的Demo：<font face="Courier New">AmazonSDK/Android/GameCircle</font>。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>2、导入jar包，设置AndroidManifest.xml</i><br />
	&nbsp;&nbsp;&nbsp; 要想使用GameCircle，我们需要导入相应的SDK jar包：gamecirclesdk.jar。</p>
<p>	&nbsp;&nbsp;&nbsp; 在AndroidManifest.xml中，需要在application标签下添加以下配置：</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font face="Courier New">&lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.ags.html5.overlay.GameCircleUserInterface&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:hardwareAccelerated=&quot;false&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@style/GCOverlay&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/activity&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.identity.auth.device.authorization.AuthorizationActivity&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:allowTaskReparenting=&quot;true&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:launchMode=&quot;singleTask&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@android:style/Theme.NoDisplay&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; /&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;data<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:host=&quot;net.iwobi.game.flickworldcup&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:scheme=&quot;amzn&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/activity&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;activity<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.ags.html5.overlay.GameCircleAlertUserInterface&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:hardwareAccelerated=&quot;false&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:theme=&quot;@style/GCAlert&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/activity&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;receiver<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:name=&quot;com.amazon.identity.auth.device.authorization.PackageIntentReceiver&quot;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; android:enabled=&quot;true&quot; &gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action android:name=&quot;android.intent.action.PACKAGE_INSTALL&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;action android:name=&quot;android.intent.action.PACKAGE_ADDED&quot; /&gt;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;data android:scheme=&quot;package&quot; /&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/intent-filter&gt;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/receiver&gt;</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 这些配置中需要的res，可以从AmazonSDK/Android/GameCircle/GameCircleSDK/res/中找到并copy到你的project中。</p>
<p>	&nbsp;&nbsp;&nbsp; <i>3、初始化GameCircle</i></p>
<p>	&nbsp;&nbsp;&nbsp; GameCircleSDK这个Demo中没有提供太多源码，src目录下是空的。因此我们只能参考Amazon Developer站点上页面上的说明一步步的添加和调整我们的代码了。</p>
<p>	&nbsp;&nbsp;&nbsp; 在你的XXActivity类中，我们添加如下方法：</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">//reference to the agsClient<br />
	&nbsp;&nbsp;&nbsp; public AmazonGamesClient agsClient;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; AmazonGamesCallback callback = new AmazonGamesCallback() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onServiceNotReady(AmazonGamesStatus status) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; switch (status) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The SDK failed to initialize correctly.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case CANNOT_INITIALIZE:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: CANNOT_INITIALIZE&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;Can not initialize Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The SDK is in the process of initializing.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case INITIALIZING:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: INITIALIZING&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;Initializing Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The device not registered with an account<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case NOT_AUTHENTICATED:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: NOT_AUTHENTICATED&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;The Device does not registered with an account&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;</p>
<p>	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // The game is not authorized to use this service.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; case NOT_AUTHORIZED:<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onServiceNotReady: NOT_AUTHORIZED&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; msg.obj = &quot;Not authorized to use Amazon Game Services&quot;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; break;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //unable to use service<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 21;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void onServiceReady(AmazonGamesClient amazonGamesClient) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; agsClient = amazonGamesClient;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //ready to use GameCircle<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (agsClient != null)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;on AmazonGamesCallback: call onServiceReady, agsClient init ok&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;on AmazonGamesCallback: call onServiceReady, agsClient init failed&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; };<br />
	&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; //list of features your game uses (in this example, achievements and leaderboards)<br />
	&nbsp;&nbsp;&nbsp; EnumSet&lt;AmazonGamesFeature&gt; myGameFeatures = EnumSet.of(<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AmazonGamesFeature.Leaderboards);</font></p>
<p>	<font face="Courier New">&nbsp;&nbsp;&nbsp; protected void onResume() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; super.onResume();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AmazonGamesClient.initialize(this, <b>callback, myGameFeatures</b>);<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; <font face="Courier New">public void onPause() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; super.onPause();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (agsClient != null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; agsClient.release();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; }</font><br />
	&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; <i>4、提交成就积分</i></p>
<p>	&nbsp;&nbsp;&nbsp; 当玩家结束游戏时，可以选择将此次的高分上传到leaderboards上。游戏中应对积分提交的代码也在XXActivity中。</p>
<p>	&nbsp;&nbsp;&nbsp; <font face="Courier New">public static void onSubmitScoreToLeaderBoard(int score) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (((FlickWorldCupActivity)context).agsClient == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 21;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Unable to use Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; LeaderboardsClient lbClient = ((FlickWorldCupActivity)context).agsClient.getLeaderboardsClient();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AGResponseHandle&lt;SubmitScoreResponse&gt; handle = lbClient.submitScore(&quot;<b>FlickWorldCupTopScore</b>&quot;, score);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; // Optional callback to receive notification of success/failure.<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handle.setCallback(new AGResponseCallback&lt;SubmitScoreResponse&gt;() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public void onComplete(SubmitScoreResponse result) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (result.isError()) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Add optional error handling here.&nbsp; Not strictly required<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // since retries and on-device request caching are automatic.<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 22;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Submit Score to LeaderBoard Failed!&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Continue game flow.<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 23;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Submit Score to LeaderBoard OK!&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; });&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp; 如果仅是查看积分排行，可以用下面这个方法：</p>
<p>	&nbsp;&nbsp;&nbsp;<font face="Courier New"> public static void onShowLeaderBoardOverlay() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if (((FlickWorldCupActivity)context).agsClient == null) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Message msg = new Message();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.what = 21;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg.obj = &quot;Unable to use Amazon Game Services&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyHandler.sendMessage(msg);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; LeaderboardsClient lbClient = ((FlickWorldCupActivity)context).agsClient.getLeaderboardsClient();<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; AGResponseHandle&lt;RequestResponse&gt; handle = lbClient.showLeaderboardOverlay(&quot;FlickWorldCupTopScore&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; handle.setCallback(new AGResponseCallback&lt;RequestResponse&gt;() {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; @Override<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; public void onComplete(RequestResponse result) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (result.isError()) {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Add optional error handling here.&nbsp; Not strictly required<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // since retries and on-device request caching are automatic.<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Log.i(TAG, &quot;onShowLeaderBoardOverlay &#8211; onComplete: Show LeaderBoard Request Failed!&quot;);<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; });&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
	&nbsp;&nbsp;&nbsp; }</font></p>
<p>	&nbsp;&nbsp;&nbsp;<br />
	<b>* 游戏圈上线</b></p>
<p>	游戏圈无法在本地进行测试，只能在真实的游戏圈中测试代码是否ok。不过Amazon的游戏圈提供了管理功能，在测试后发布前可将游戏圈 leaderboard的值reset。游戏圈leaderboard发布后，你就可以使用leaderboard了。游戏圈功能在国内访问是没有任何问 题的，查看积分榜，提交分数到积分榜都很顺畅。</p>
<p>	<b>* 小结</b></p>
<p>	Amazon游戏SDK在国内的应用估计比较小众，大家可能更多的选择用Google Play提供的服务或是AppStore的，但Amazon毕竟为游戏开发者提供了一个选择（而且是完全免费的哦），另外Amazon的Support对 提交问题的反馈较为及时(无论是mail还是forum上的提问)，基本24小时内就会有答复。各种设施的发布也比较快，有时候3-4个小时即可生效。</p>
<p>	目前Amazon Game SDK的资料多为英文，且集中在Amazon官方站点以及官方维护的<a href="http://forums.developer.amazon.com/forums/index.jspa">support论坛</a>中。遇到问题，亚马逊的论坛是第一选择。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/08/04/amazon-inapp-purchasing-and-gamecirle-in-cocos2dx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>说说执行力</title>
		<link>https://tonybai.com/2014/03/05/thought-on-executive-power/</link>
		<comments>https://tonybai.com/2014/03/05/thought-on-executive-power/#comments</comments>
		<pubDate>Tue, 04 Mar 2014 17:57:16 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[FaceBook]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[制度]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[团队]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[执行力]]></category>
		<category><![CDATA[流程]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[管理]]></category>
		<category><![CDATA[管理者]]></category>
		<category><![CDATA[纸牌屋]]></category>
		<category><![CDATA[锡恩4R]]></category>
		<category><![CDATA[领导力]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1491</guid>
		<description><![CDATA[You are never to dictate what I can and can not do. The only two words I want to hear from you when I ask you to do something are &#34;Yes&#34; and &#34;Sir&#34;。（我能做什么不能做什么，你管不着。我吩咐你做事的时候，只想听到两个词，&#34;是的&#34;和&#34;先生&#34;。） &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; [...]]]></description>
			<content:encoded><![CDATA[<p><i>You are never to dictate what I can and can not do. The only two words I want to hear from you when I ask you to do something are &quot;Yes&quot; and &quot;Sir&quot;。（我能做什么不能做什么，你管不着。我吩咐你做事的时候，只想听到两个词，&quot;是的&quot;和&quot;先生&quot;。）<br />
	&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8211; 《纸牌屋》第一季</i></p>
<p>想必大家都基本认同：最有执行力的团队莫过于军队。军队有纪律约束，有荣誉感引导。在战时，违抗命令者是可以被直接拉出去枪毙的（影视剧中^_^）。但在 现实中你的团队里，你行么？别说枪毙，说了一句刺耳的批评的话，都可能招来各种不合作。大多数工作并非金饭碗，Google , FB的员工还经常跳来跳去呢，&ldquo;此处不留爷，自有留爷处&rdquo;才是硬道理。</p>
<p>那我们靠什么保证团队执行力呢？</p>
<p>靠NB人士？也许可行。但看看你的荷包，金子够么？NB人士属于金字塔顶端，数量稀少，价格昂贵，且多聚集在国内外知名名企。对于普通企业来说，操作起来有极大困难。<br />
	大把金钱奖励？我们不是土豪，没有挥金如土的气魄，钱不是不可以给，但要用在刀刃上。<br />
	精神鼓励？可以用，但不能一直用，否则下面就要说你是&ldquo;精神病&rdquo;了。</p>
<p>回归本源，执行力是建立在员工的职业操守上的。在这样一个前提下，我们至少要做好三件事来保证执行力。</p>
<p><b>1、明确责任</b><br />
	让员工确定、一定以及肯定的明确这件事/这些事的责任人就是他/她。做好了获奖，做少了、做错了、做砸了，他就是&ldquo;罪魁祸首&rdquo;。</p>
<p><b>2、设立检查点</b><br />
	为了帮助员工工作在正确的方向上，不走偏，不做错，不漏做，我们要帮助员工建立好检查点，明确告知检查点所有内容。在检查点上有针对性的检查也是员工的责任之一。</p>
<p><b>3、频繁反馈</b><br />
	团队负责人应该像老妈子似的不断的催促和获取反馈，以即时把握执行力的真实情况。</p>
<p>锡恩的4R执行力理论还强调了一个&ldquo;即时奖励&rdquo;，<b>强化</b>任务执行与后果之间的关系，让员工第一时间感受到良好执行带来的成果，获得成就感和认同。</p>
<p>接下来就要将以上三件事耐心的变成制度、变成流程，耐心地让员工按流程要求工作而不是按负责人的指令行事。</p>
<p>最后将以上流程自动化。通过系统分配任务，通过任务单将任务相关的信息整合在一起，为执行者提供帮助。通过这样的一个系统排除管理者的人肉提醒、减少在以 上环节中的人为疏漏（比如缺少检查点，忘记反馈），尽可能排除人的惰性、忘性等带来的种种执行问题。通过系统为任务执行作出评价，可作为员工绩效的参考。</p>
<p>通过以上的措施，还可以很好的应对&ldquo;熟人文化&rdquo;：不是按某人的指令，而是&ldquo;制度规程&rdquo;去做事，按照执行力系统的分配、提醒和通知去执行、去反馈。</p>
<p>这样一来，管理者也可以从繁重的&ldquo;人肉管理&rdquo;中脱离出来，既可以通过系统众览全局进度，保证任务的按时正确执行，也可以将精力更多的倾注在其他重要方面的工作中。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/03/05/thought-on-executive-power/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>关于2014团队改善的考量</title>
		<link>https://tonybai.com/2014/03/03/considerations-on-team-improved-in-2014/</link>
		<comments>https://tonybai.com/2014/03/03/considerations-on-team-improved-in-2014/#comments</comments>
		<pubDate>Sun, 02 Mar 2014 21:11:31 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[团队]]></category>
		<category><![CDATA[导演]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[改革]]></category>
		<category><![CDATA[深水区]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[管理]]></category>
		<category><![CDATA[纸牌屋]]></category>
		<category><![CDATA[过程改善]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1483</guid>
		<description><![CDATA[一个人的品行，不取决于这人如何享受胜利，而在于这人如何忍受失败。 &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160;&#160; &#8212; 《纸牌屋》第一季 团队改善，不是那种很快见到成果或者效益的活儿。 但这件事你做不做呢？坦诚的说，今年我在这方面的&#8220;热情&#8221;真的不是那么高，肯定是不如前两年了，因为是时候更多地为自己的&#8220;前途&#8221;考虑考虑了。团队改善这 种活儿做好了还行，做坏了，那就成为&#8220;把柄&#8221;，成为劣迹。投入了资源，却不见成果。因为领导层可能从来都没有让你去做什么改变，也不关心你要做什么改变。 只要把领导认为要做的事情做好即可。做团队改善这事儿，纯属自己给自己加的&#8220;私活儿&#8221;：长路漫漫，遍地荆棘，费力还不一定讨好。 但身在其位，团队没有任何改善或进步不是我的风格，因此2014年，这事儿还要继续做，并且更难做。用现在一个时髦的词来形容，好比进入了&#8220;深水区&#8221;。 改善的初衷是总是好的，但在实际操作中也有可能带来负面的影响，比如因流程变化或工具切换导致的一时的效率下降。人都是不习惯于变化的，并且改变可能会触 动某些人的利益，因此阻力可想而知。从全局来看，这又是必须去做的事情。总之改善的道路坎坷，顶住压力是必须的。有些时候压力更多是来自于上面。当领导问 如果失败了谁负责，这时你应该毫不犹豫的说：我负责。没有别的选择，这就是改变带来的代价，&#8221;我不入地狱谁入地狱呢&#8220;。 我理想中的团队应该是一部精密的机器，开机后不用管的，自运行的，并生产出正确让人满意的成果。我的目标就是搭建出这样一部机器，在生产过程中尽量降低人 的因素的影响：比如惰性、忘性、马虎、态度不端正等对成果物质量的影响。我们还要在&#8220;关键环节&#8221;加入&#8220;自动&#8221;地检查，就像传统工厂的质检环节那样，这些 &#8220;检查&#8221;环节让低质量的成果无法通过。通过这些环节，你还可以全盘掌握产品的生产和质量情况。 而我就是幕后的那个&#8220;导演&#8221;。我发现自己越来越喜欢&#8220;导演&#8221;这一角色了。策划着这一切，看到一切都按照你的思路一步一步的进行下去的。导演决定了是否能拍出好片子，即便演员不一定都是大腕儿。 有同事建议我能针对一些改善措施和想法做些宣讲。我回绝了。因为大家都是那种喜欢看到结果的，在结果未出来之前，还是多做少说。这样也可以避免外界干扰你 的做事思路。另外没有两个团队面对的情况是一模一样的，也就没有一致的改善的方法，有些事情不能代劳。自己发现的问题才真实，才接地气。 坚持你认为正确的事情，坚定的做下去就是了，其他的都抛到脑后。 &#169; 2014, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p><i>一个人的品行，不取决于这人如何享受胜利，而在于这人如何忍受失败。<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &#8212; 《纸牌屋》第一季</i></p>
<p>团队改善，不是那种很快见到成果或者效益的活儿。</p>
<p>但这件事你做不做呢？坦诚的说，今年我在这方面的&ldquo;热情&rdquo;真的不是那么高，肯定是不如前两年了，因为是时候更多地为自己的&ldquo;前途&rdquo;考虑考虑了。团队改善这 种活儿做好了还行，做坏了，那就成为&ldquo;把柄&rdquo;，成为劣迹。投入了资源，却不见成果。因为领导层可能从来都没有让你去做什么改变，也不关心你要做什么改变。 只要把领导认为要做的事情做好即可。做团队改善这事儿，纯属自己给自己加的&ldquo;私活儿&rdquo;：长路漫漫，遍地荆棘，费力还不一定讨好。</p>
<p>但身在其位，团队没有任何改善或进步不是我的风格，因此2014年，这事儿还要继续做，并且更难做。用现在一个时髦的词来形容，好比进入了&ldquo;深水区&rdquo;。</p>
<p>改善的初衷是总是好的，但在实际操作中也有可能带来负面的影响，比如因流程变化或工具切换导致的一时的效率下降。人都是不习惯于变化的，并且改变可能会触 动某些人的利益，因此阻力可想而知。从全局来看，这又是必须去做的事情。总之改善的道路坎坷，顶住压力是必须的。有些时候压力更多是来自于上面。当领导问 如果失败了谁负责，这时你应该毫不犹豫的说：我负责。没有别的选择，这就是改变带来的代价，&rdquo;我不入地狱谁入地狱呢&ldquo;。</p>
<p>我理想中的团队应该是一部精密的机器，开机后不用管的，自运行的，并生产出正确让人满意的成果。我的目标就是搭建出这样一部机器，在生产过程中尽量降低人 的因素的影响：比如惰性、忘性、马虎、态度不端正等对成果物质量的影响。我们还要在&ldquo;关键环节&rdquo;加入&ldquo;自动&rdquo;地检查，就像传统工厂的质检环节那样，这些 &ldquo;检查&rdquo;环节让低质量的成果无法通过。通过这些环节，你还可以全盘掌握产品的生产和质量情况。</p>
<p>而我就是幕后的那个&ldquo;导演&rdquo;。我发现自己越来越喜欢&ldquo;导演&rdquo;这一角色了。策划着这一切，看到一切都按照你的思路一步一步的进行下去的。导演决定了是否能拍出好片子，即便演员不一定都是大腕儿。</p>
<p>有同事建议我能针对一些改善措施和想法做些宣讲。我回绝了。因为大家都是那种喜欢看到结果的，在结果未出来之前，还是多做少说。这样也可以避免外界干扰你 的做事思路。另外没有两个团队面对的情况是一模一样的，也就没有一致的改善的方法，有些事情不能代劳。自己发现的问题才真实，才接地气。</p>
<p>坚持你认为正确的事情，坚定的做下去就是了，其他的都抛到脑后。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/03/03/considerations-on-team-improved-in-2014/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>厨房里的领导课</title>
		<link>https://tonybai.com/2014/02/18/mentoring-in-the-kitchen/</link>
		<comments>https://tonybai.com/2014/02/18/mentoring-in-the-kitchen/#comments</comments>
		<pubDate>Tue, 18 Feb 2014 11:02:32 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[思考控]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[创新]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[厨艺]]></category>
		<category><![CDATA[团队]]></category>
		<category><![CDATA[基础设施]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[管理]]></category>
		<category><![CDATA[项目管理]]></category>
		<category><![CDATA[领导力]]></category>
		<category><![CDATA[领导课]]></category>
		<category><![CDATA[食材]]></category>
		<category><![CDATA[饮食]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1480</guid>
		<description><![CDATA[生活中永远不缺少大道理，缺的是一颗善于思考和发现它们的心。 &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8211; Tony Bai 晚上回到家，家人端上来热腾腾的饭菜。吃了几口，感觉味道较为普通。盘子里那些被加工过的食材是昨天刚刚买到的，又好又新鲜。顿然一种可惜的赶脚油然而 生。为什么这么上好新鲜的食材经过家人的烹制就变得这么普通了呢，仅仅是变成了充饥之用。而这些食材在大厨手下却能妙笔生花，做出让人流连忘返的精美菜 肴。我不是很懂厨艺，但总觉的大厨烹制菜肴的过程与领导团队做一个项目或开发一款产品有着相似的内涵。小小厨房中蕴含着某些大道理，值得我在这里深思一番。 * 角色定义 既然将大厨烹制菜肴比作领导团队做事，那么我们先来熟悉一下厨房里的各个角色： &#160;&#160;&#160; 厨房 &#8211; 工作间 &#160;&#160;&#160; 厨子 &#8211; Leader &#160;&#160;&#160; 食材、调料 &#8211; 团队成员 &#160;&#160;&#160; 厨房用品 &#8211; 基础设施 &#160;&#160;&#160; 食客&#160; &#8211; 使用者，客户 &#160;&#160;&#160; 营养、口感、档次 &#8211; 主要Feature &#160;&#160;&#160; 味道 &#8211; 用户体验 &#160;&#160;&#160; 任务目标 &#8211; 制作一道色香味俱佳的菜肴。 &#160; * 好厨善选材 选材是作出上好菜肴的关键。好厨都是善选材的，就好比好领导知道应该选择什么样的组员加入团队。 主食材（团队主力）决定菜肴的基本品质，比如：档次、外观、营养、口味等。 选择食材要主次分明，相辅相成。有红花争艳，也要有绿叶陪衬，否则烧成的菜品就会内斗严重，主次不明，类别不清，让人迷惑。 选择食材切忌相声相克。一旦这样，很可能会彻底毁掉这道菜。即便做成，也可能给食客带来损害。 调料，好比美工、前端设计师，专攻用户体验。虽然主食材实现了菜肴的核心营养和口感（核心Feature），但如果没有调料的作用，就缺少了味道这一决定性的用户体验。没有好的用户体验，结果一样是失败，至少产品或结果算不上一流。 * 火候儿甚重要 大厨的另外一个特长就是对烹饪过程中火候的把握极其到位。对于不同菜肴，不同食材，大厨会选择不同的火候烹制，让食材保持最佳状态，适当吸收汤汁味道。用 错了火候，将是灾难性的。本该用文火慢炖入味的，却用了旺火，结果食不入味，对食客来说毫无吸引力；本来用旺火快速炒制的，却用了文武火，导致菜肴制作时 [...]]]></description>
			<content:encoded><![CDATA[<p><i>生活中永远不缺少大道理，缺的是一颗善于思考和发现它们的心。<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; Tony Bai</i></p>
<p><span style="line-height: 1.6em;">晚上回到家，家人端上来热腾腾的饭菜。吃了几口，感觉味道较为普通。盘子里那些被加工过的食材是昨天刚刚买到的，又好又新鲜。顿然一种可惜的赶脚油然而 生。为什么这么上好新鲜的食材经过家人的烹制就变得这么普通了呢，仅仅是变成了充饥之用。而这些食材在大厨手下却能妙笔生花，做出让人流连忘返的精美菜 肴。我不是很懂厨艺，但总觉的大厨烹制菜肴的过程与</span><b style="line-height: 1.6em;">领导</b><span style="line-height: 1.6em;">团队做一个项目或开发一款产品有着相似的内涵。小小厨房中蕴含着某些大道理，值得我在这里深思一番。</span></p>
<p><b>* 角色定义</b></p>
<p>既然将大厨烹制菜肴比作领导团队做事，那么我们先来熟悉一下厨房里的各个角色：</p>
<p>&nbsp;&nbsp;&nbsp; 厨房 &#8211; 工作间<br />
	&nbsp;&nbsp;&nbsp; 厨子 &#8211; Leader<br />
	&nbsp;&nbsp;&nbsp; 食材、调料 &#8211; 团队成员<br />
	&nbsp;&nbsp;&nbsp; 厨房用品 &#8211; 基础设施<br />
	&nbsp;&nbsp;&nbsp; 食客&nbsp; &#8211; 使用者，客户<br />
	&nbsp;&nbsp;&nbsp; 营养、口感、档次 &#8211; 主要Feature<br />
	&nbsp;&nbsp;&nbsp; 味道 &#8211; 用户体验</p>
<p>&nbsp;&nbsp;&nbsp; 任务目标 &#8211; 制作一道色香味俱佳的菜肴。<br />
	&nbsp;</p>
<p><b>* 好厨善选材</b></p>
<p>选材是作出上好菜肴的关键。好厨都是善选材的，就好比好领导知道应该选择什么样的组员加入团队。</p>
<p>主食材（团队主力）决定菜肴的基本品质，比如：<b>档次</b>、外观、营养、口味等。</p>
<p>选择食材要主次分明，相辅相成。有红花争艳，也要有绿叶陪衬，否则烧成的菜品就会内斗严重，主次不明，类别不清，让人迷惑。</p>
<p>选择食材切忌相声相克。一旦这样，很可能会彻底毁掉这道菜。即便做成，也可能给食客带来损害。</p>
<p>调料，好比美工、前端设计师，专攻用户体验。虽然主食材实现了菜肴的核心营养和口感（核心Feature），但如果没有调料的作用，就缺少了味道这一决定性的<b>用户体验</b>。没有好的用户体验，结果一样是失败，至少产品或结果算不上一流。</p>
<p><b>* 火候儿甚重要</b></p>
<p>大厨的另外一个特长就是对烹饪过程中火候的把握极其到位。对于不同菜肴，不同食材，大厨会选择不同的火候烹制，让食材保持最佳状态，适当吸收汤汁味道。用 错了火候，将是灾难性的。本该用文火慢炖入味的，却用了旺火，结果食不入味，对食客来说毫无吸引力；本来用旺火快速炒制的，却用了文武火，导致菜肴制作时 间较长，营养成分流失。滥用火候甚至可能将食材烧焦烧糊，使得菜肴制作彻底失败。</p>
<p>这就好比团队Leader对团队工作节奏、进度以及压力的控制，要让团队成员在适宜的环境下发挥出最大的潜力、进行最高效的工作。过大的压力，过紧的进度都可能会压跨团队，无法达成团队目标。</p>
<p><b>* 装备，装备，事半功倍</b>！</p>
<p>厨子烹制佳肴离不开厨房用具，虽说厨房用具的好坏对于菜肴的最终烹制结果不起决定性的作用，但精良全面的厨房用具会使得大厨烹制菜肴的过程事半功倍。</p>
<p>从古至今均有美酒佳肴，但非要论一下到底什么时候的菜更好吃，相信没人能给出结论，也许古代人做的菜更好吃也不一定。但能够确定的是现代大厨所使用的厨具 肯定要比古代人更加齐全和精良，这使得现代人可以快速制作出大量精美的菜肴，可以在一定程度上缩短了菜肴制作的周期。通过这些现代化的厨具，可以更加精确 量化菜肴的营养成分，也可以将菜肴的制作工序程式化，以供复用。</p>
<p>另外不同的厨师团队使用的装备各有不同，无所谓新老，找到最适宜的才是最好。</p>
<p><b>* 厨房里的创新</b></p>
<p>中华饮食文化源远流长，创新菜品层出不穷。从厨师的角度来看，这些创新无非是创新的食材、创新的调料以及创新的烹制方法。映射的团队管理来看，领导者应善于引进创新的人才，并敢于进行组织、管理以及过程方法创新，这样才能有创新的产品、创新的文化和氛围。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/02/18/mentoring-in-the-kitchen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>2013小结</title>
		<link>https://tonybai.com/2014/01/04/my-summary-of-2013/</link>
		<comments>https://tonybai.com/2014/01/04/my-summary-of-2013/#comments</comments>
		<pubDate>Fri, 03 Jan 2014 23:54:46 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[生活簿]]></category>
		<category><![CDATA[2013]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Kindle]]></category>
		<category><![CDATA[Memcached]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[ZooKeeper]]></category>
		<category><![CDATA[个人总结]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[咨询]]></category>
		<category><![CDATA[女儿]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[工作总结]]></category>
		<category><![CDATA[年终总结]]></category>
		<category><![CDATA[思考]]></category>
		<category><![CDATA[感悟]]></category>
		<category><![CDATA[生活]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[读书]]></category>
		<category><![CDATA[豆瓣]]></category>
		<category><![CDATA[跑步]]></category>
		<category><![CDATA[转型]]></category>
		<category><![CDATA[香港]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1470</guid>
		<description><![CDATA[2013年的个人年终总结比以往来得晚了一些，至于原因，我也说不清楚，拖延症也罢，其他原因也罢，总之是晚了。 写年终小结已经有小几年了，风格一直如一，无非是老三样：工作得失、生活酸甜以及新年展望，今年也不利外。 * 工作篇 我们部门在所在行业里已经摸爬滚打了10多年了，经 历和见证了这个行业从诞生、增长、成熟到如今的衰退的整个过程。也正是由于处于行业的衰退期，2013年部门的运营十分艰难。十年对于任何一个行业来说， 可能都已经过了其巅峰期，真心不能再期望这个行业还能会有下一个高峰了，对于个人来说也是如此。转型、业务突破变成了领导常挂在嘴边的词汇，但做起来又何 其艰难。 2013年，我们的业务转型依旧是围绕着我们的&#8220;金主&#8221;，虽然他们的业务营收也受到了微信等OTT业务的极大影响，传统业务投资也在缩减。对于个人而言， 除了负责传统产品线，转型、业务突破也成了我的绩效目标的一部分。于是在2013年，写文档比写代码多了一点，出差比常年多了一点，周六周日的连续加班也 多出了一点。在这些尝试中，以5月份某运营商某信的重构项目最为让人印象深刻。为了这个合同额几个亿的项目，我们近30人连续奋战了一个多月编写技术建议 书和投标方案，过程辛苦但却颇感充实，最终我们拿到了两个第二、两个第三的成绩。也许这个结果对于公司来说算是一种失败，但对于我个人来说，我获得了些许 转型的信心，以至于在后续的几次投标资料编写过程中，面对较新的领域，我也可以镇定自若。 掐指算来，这一年我以咨询顾问的临时角色参与了8个大大小小项目的前期交流以及投标支持工作，其中六个标以失败或不了了之而告终，还有两个标尚未有最终结 果。对于这样的结果，我也只能表示无奈。虽然我心里也十分清楚，对于国内这类解决方案项目的投标，技术往往不是最重要的，况且对于这些新领域，我们的技术 储备还不够系统，积累较为浅薄，落地的也的确较少。但面对这样的局面，我们还能怎么做呢？我也期待新一年能得到一个新的答案。 当然2013的工作中不全是遗憾，年末之前新系统的上线算是为我的2013划上了一个还算不错的句号，毕竟这是我两年来为之付出最多，也是最重要的一个工作目标。另外2013年继续整理和总结自己的一些管理经验和工作原则。在过程方面继续深入改善，尤其在代码质量方面。 在技术精深方面，今年没有太多进步。年初的时候曾探讨过如何在现有项目中使用一些成熟的开源技术和产品，比如memcached、zookeeper等， 为了保持手热，还尝试做些算法类的编码，这个在experiments库中有体现。在其他方面，可谓是&#8220;三无状态&#8221;：无技术书籍翻译、无技术杂志投稿、无 新开源项目发起。另外今年没有尝试去学习什么新语言，理由在此。 在年末的绩效评审时，观察到一些现象：那些绩效最末尾的人，往往并非是自身不够努力，而是领导赋予的目标不明确，这会给下属带来更多的不安，多数下属也会因为工作目标的不明确，而表现出更为糟糕的绩效。 * 生活篇 我个人十分注重工作和生活的平衡，不知道这种理念对于一个革命尚未成功的人来说算不算正确。 今年写了56篇博文，只完成了计划值的3/4，算是可接受范围，博文质量有所提升，访问次数和评论反馈也多了许多。文章以技术理解偏多，深入的偏少。技术攻关还是留给年轻人去吧。另外就是经验总结和感悟偏多，这也许与工作年头多有关系吧。 读书方面，据豆瓣不完全统计一共读了61本，这照比去年要多出不少，想必是有了Kindle PaperWhite的缘故吧，使得碎片时间得到充分利用。技术、商业书籍依旧占较大比例，小说尤其是科幻小说也不少。同样是因为电子书，今年纸质书籍购 买减少了（痛定思痛后的决定），双十一、双十二以及圣诞促销均没有出手。不过豆瓣上想读的书单依旧还有上百本^_^，任重道远啊。 今年爱上了跑步，坚持到11月末，因出差和天气转深寒等原因，决定暂停一段时间，等春节后气温回升时再拾起这个好习惯，相信不是大问题。跑步的确让我的身体状况大为好转，至少感冒次数大为下降。 &#160; &#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160; &#160;&#160;&#160; 今年的一些家庭目标也多已实现，比如和老婆一起去香港、带孩子去海边玩等。数码装备也更新了一圈。 果果这个小家伙那叫一个茁壮成长啊。年中给她换了一个大的幼儿园，她也变得十分喜欢和小朋友在一起玩了，有时候还觉得在家里没有意思。每周果果还要上一节 她最喜欢的舞蹈课，我们的初衷就是让她多与小朋友老师接触，也不指望她能学出什么样子来，不过她学得倒是有模有样，十分认真。现在的果果简直就是一个小大 人，每天从早到晚说个不停，精力那叫一个充沛，有时候不得不强迫她去睡觉^_^。 * 新年展望 感觉这一年的进步有些差强人意，心底真心感觉自己的努力还是太少了，于是立下了&#8220;少睡觉，多干活&#8221;的目标。 新的一年，无论是个人还是工作，都要更多的思考如何将知识、技能和经验转化为更多价值，如何将业务经验、技术积累转化为合同。 新的一年，要主动适应转型，无论是工作上的还是个人方向上的，争取在这一年里能找到正确的方向，并成功入门。最好给自己做一个三年到五年的布局。 新的一年，尝试继续保持生活与工作的平衡，也许这将变成一种奢侈的期望。 新的一年，还有什么比全家健康快乐更重要的呢。 &#169; 2014, bigwhite. 版权所有.]]></description>
			<content:encoded><![CDATA[<p>2013年的个人年终总结比以往来得晚了一些，至于原因，我也说不清楚，拖延症也罢，其他原因也罢，总之是晚了。</p>
<p>写年终小结已经有小几年了，风格一直如一，无非是老三样：工作得失、生活酸甜以及新年展望，今年也不利外。</p>
<p><b>* 工作篇</b></p>
<p>我们部门在所在行业里已经摸爬滚打了10多年了，经 历和见证了这个行业从诞生、增长、成熟到如今的衰退的整个过程。也正是由于处于行业的衰退期，2013年部门的运营十分艰难。十年对于任何一个行业来说， 可能都已经过了其巅峰期，真心不能再期望这个行业还能会有下一个高峰了，对于个人来说也是如此。转型、业务突破变成了领导常挂在嘴边的词汇，但做起来又何 其艰难。</p>
<p>2013年，我们的业务转型依旧是围绕着我们的&ldquo;金主&rdquo;，虽然他们的业务营收也受到了微信等OTT业务的极大影响，传统业务投资也在缩减。对于个人而言， 除了负责传统产品线，转型、业务突破也成了我的绩效目标的一部分。于是在2013年，写文档比写代码多了一点，出差比常年多了一点，周六周日的连续加班也 多出了一点。在这些尝试中，以5月份某运营商某信的重构项目最为让人印象深刻。为了这个合同额几个亿的项目，我们近30人连续奋战了一个多月编写技术建议 书和投标方案，过程辛苦但却颇感充实，最终我们拿到了两个第二、两个第三的成绩。也许这个结果对于公司来说算是一种失败，但对于我个人来说，我获得了些许 转型的信心，以至于在后续的几次投标资料编写过程中，面对较新的领域，我也可以镇定自若。</p>
<p>掐指算来，这一年我以咨询顾问的临时角色参与了8个大大小小项目的前期交流以及投标支持工作，其中六个标以失败或不了了之而告终，还有两个标尚未有最终结 果。对于这样的结果，我也只能表示无奈。虽然我心里也十分清楚，对于国内这类解决方案项目的投标，技术往往不是最重要的，况且对于这些新领域，我们的技术 储备还不够系统，积累较为浅薄，落地的也的确较少。但面对这样的局面，我们还能怎么做呢？我也期待新一年能得到一个新的答案。</p>
<p>当然2013的工作中不全是遗憾，年末之前<a href="http://tonybai.com/2013/12/26/just-for-being-relieved/">新系统的上线</a>算是为我的2013划上了一个还算不错的句号，毕竟这是我两年来为之付出最多，也是最重要的一个工作目标。另外2013年继续整理和总结自己的一些<a href="http://tonybai.com/2013/08/04/more-thoughts-on-improving-efficiency/">管理经验</a>和<a href="http://tonybai.com/2013/08/19/my-personal-work-principles/">工作原则</a>。在过程方面继续深入改善，尤其在<a href="http://tonybai.com/2013/07/08/code-review-from-rule-of-man-to-rule-of-law/">代码质量</a>方面。</p>
<p>在技术精深方面，今年没有太多进步。年初的时候曾探讨过如何在现有项目中使用一些成熟的开源技术和产品，比如<a href="http://tonybai.com/2013/11/01/a-case-of-applying-memcached-cas/">memcached</a>、<a href="http://tonybai.com/2013/08/28/implement-config-sync-for-distributed-system-with-zookeeper-services/">zookeeper</a>等， 为了保持手热，还尝试做些算法类的编码，这个在<a href="https://github.com/bigwhite/experiments">experiments库</a>中有体现。在其他方面，可谓是&ldquo;三无状态&rdquo;：无技术书籍翻译、无技术杂志投稿、无 新开源项目发起。另外今年没有尝试去学习什么新语言，理由<a href="http://tonybai.com/2013/10/22/some-experience-about-learning-programming-language/">在此</a>。</p>
<p>在年末的绩效评审时，观察到一些现象：那些绩效最末尾的人，往往并非是自身不够努力，而是领导赋予的目标不明确，这会给下属带来更多的不安，多数下属也会因为工作目标的不明确，而表现出更为糟糕的绩效。</p>
<p><b>* 生活篇</b></p>
<p>我个人十分注重工作和生活的平衡，不知道这种理念对于一个革命尚未成功的人来说算不算正确。</p>
<p>今年写了56篇博文，只完成了计划值的3/4，算是可接受范围，博文质量有所提升，访问次数和评论反馈也多了许多。文章以技术理解偏多，深入的偏少。技术攻关还是留给年轻人去吧。另外就是经验总结和感悟偏多，这也许与工作年头多有关系吧。</p>
<p>读书方面，据豆瓣不完全统计一共读了61本，这照比去年要多出不少，想必是有了Kindle PaperWhite的缘故吧，使得碎片时间得到充分利用。技术、商业书籍依旧占较大比例，小说尤其是科幻小说也不少。同样是因为电子书，今年纸质书籍购 买减少了（痛定思痛后的决定），双十一、双十二以及圣诞促销均没有出手。不过豆瓣上想读的书单依旧还有上百本^_^，任重道远啊。</p>
<p>今年<a href="http://tonybai.com/2013/10/09/love-running/">爱上了跑步</a>，坚持到11月末，因出差和天气转深寒等原因，决定暂停一段时间，等春节后气温回升时再拾起这个好习惯，相信不是大问题。跑步的确让我的身体状况大为好转，至少感冒次数大为下降。 &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;</p>
<p>今年的一些家庭目标也多已实现，比如<a href="http://tonybai.com/2013/06/18/a-hongkong-macau-trip/">和老婆一起去香港</a>、带孩子去海边玩等。数码装备也更新了一圈。</p>
<p>果果这个小家伙那叫一个茁壮成长啊。年中给她换了一个大的幼儿园，她也变得十分喜欢和小朋友在一起玩了，有时候还觉得在家里没有意思。每周果果还要上一节 她最喜欢的舞蹈课，我们的初衷就是让她多与小朋友老师接触，也不指望她能学出什么样子来，不过她学得倒是有模有样，十分认真。现在的果果简直就是一个小大 人，每天从早到晚说个不停，精力那叫一个充沛，有时候不得不强迫她去睡觉^_^。</p>
<p><b>* 新年展望</b></p>
<p>感觉这一年的进步有些差强人意，心底真心感觉自己的努力还是太少了，于是立下了&ldquo;少睡觉，多干活&rdquo;的目标。</p>
<p>新的一年，无论是个人还是工作，都要更多的思考如何将知识、技能和经验转化为更多价值，如何将业务经验、技术积累转化为合同。</p>
<p>新的一年，要主动适应转型，无论是工作上的还是个人方向上的，争取在这一年里能找到正确的方向，并成功入门。最好给自己做一个三年到五年的布局。</p>
<p>新的一年，尝试继续保持生活与工作的平衡，也许这将变成一种奢侈的期望。</p>
<p>新的一年，还有什么比全家健康快乐更重要的呢。</p>
<p style='text-align:left'>&copy; 2014, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2014/01/04/my-summary-of-2013/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
