年初“每周读书”同步到微信公众号,从 4 月开始基本上保持了每周一更的频率,至今已经介绍了 33 本书。当时在 71 期介绍的小说《The Martian》改编的电影《火星救援》最近也上映了,口碑非常不错。在电影院里看着 Watney 在荒无人烟的红色星球上艰难挣扎,还不忘搞笑,想起 2015 年就这样过去了,有些感慨。
这一年里在“每周读书”介绍的书目必须满足两个要求:
其实这一年里还读过几本不值得介绍的书,比如斯蒂芬金的烂作《手机》,以及科普类的《三百幅不可不知的世界名画》之流。我个人读书有个习惯,就是不好看的书看了个开头就放下了,所以基本上会在公众号里发出来的都是比较有看点的书。
以下是“枫言枫语·每周读书” 2015 年介绍过的书里,按照分类做个简单的 Top 排行。
小说类 科幻的《三体》和《死者代言人》都非常不错,推理类东野圭吾的《恶意》质量很高,阿加莎的小说第一次接触但是非常好读,不愧是推理小说的女王。日本战后文学的代表中,太宰治、三岛由纪夫的作品都相当深刻。当然还少不了今年大热的《火星救援》原作——《The Martian》,比起浓缩过的电影来说,小说里解释的东西要多得多。畅销书《岛上书店》讲述了一个很感人的故事,虽然有比较重的畅销书技巧的痕迹,但仍不失为感动读者的好书。
理论类 的书里面,《Being Mortal》(中译名《最好的告别》)以揪心的事例把如何面临衰老与死亡这个艰难的问题摆在读者面前,读来心情沉重,但是人生自古谁无死,无法避免死亡的情况下,怎样让自己能够有尊严地,开心地面对死亡才是最重要的课题。互联网业界的《增长黑客》把 Growth Hacking 这个概念介绍了一番,属于科普好书,Marty Cagan 2008 年写的《启示录》介绍了如何胜任产品经理这个职业技能要求很高却常常被人误解的职位,这么多年过去了,里面的核心内容仍然非常有价值。
2014 年来自以色列的尤瓦尔·赫拉利的《人类简史》出了英文版,于是开始风靡全球,14 年底中译本出版,这是一本刷新三观,超然物外,处处上帝视角的书。
文学类 中,《中国文学史》可以帮助读者整理本来就纷繁复杂的中国文学故事,文学体裁,以及发展的轨迹,虽然成书较早,但是对于中国古代的文学史点评到位,令人茅塞顿开。“创意写作书系”里James Scott Bell 的《冲突与悬念》讲述了畅销小说的写作技巧,将之映照于大部分畅销小说之上,发现的确如此,有助于读者对小说的优劣作出品评。诗歌类中余秀华的诗集因其朴素的乡村气息以及真实的个人情感,超然于虚伪浮夸的现代诗坛之外,只可惜外界大部分人对余秀华的传闻只限于对《穿过大半个中国去睡你》的揶揄与围观。海子由于卧轨而成名于那个特殊的时代,大多数人知道海子却源于对《面朝大海,春暖花开》的误解读。《海子诗全集》并不都是好诗,比如海子一直在坚持写作的《太阳》史诗,读起来就很不以为然,但是海子的短诗却是才华横溢的,读来颇感振奋。
增长黑客一词是 Growth Hackers 的中译名,指的是做 Growth Hacking 的人。Growth Hacking 主要是指创业团队通过数据分析,利用社交网络,创造性地设想出各种营销手段从而让名不见经传的小产品迅速成长为明星产品的过程。所以 Growth 是用户增长,Hacking 则有点黑科技的意思。可能非 IT 业界的读者一直以为黑客 Hacker 是一个贬义词,其实黑客是比较中性的,主要指技术特别牛逼的 geek,把技术用在不好的地方并不是黑客本来的含义(Steven Levy 的《Hackers》一书就讲述了黑客的来由)。所以总的来说,Growth Hacking 可以理解为用黑科技以低成本的方式获得超级快的用户增长。
其实 Growth Hacking 这个词早在几年前就已经在硅谷流行起来了,Growth Hackers Conference 也举办过好几届了,下一届将在旧金山湾区举办。但是说来惭愧,枫影还是最近才在IT 公论的播客上听到这个词,于是赶紧买了本《增长黑客》压压惊。
整本书看下来给我感觉就三个字:故事会。
作者范冰 @XDash 曾经是 WiFi 万能钥匙的产品经理,写过代码做过运营,各方面涉猎挺广,积累了不少互联网的故事,于是作者在这本书里面堆砌了大量的案例,从国外的 Hotmail, Twitter, Airbnb, 到国内的QQ,微信,美图秀秀,唱吧等应用,这里有许多非常有趣的故事,令人深受启发。
总体来说这本书的发散很好,案例真实可靠,但是理论沉淀不足,可以说是一本不错的故事会,但不要指望书里能给你什么方法论。这方面 Marty Cagan 的《Inspired: How To Create Products Customers Love》(中文版由七印部落翻译为:启示录)这本书要沉稳得多。一本纯理论的书会让读者打瞌睡,所以作者都会用实例来帮助论证自己的观点,即使是《自私的基因》这样类似学术论文的书,也会使用大量的模型以讲故事的方式对自己的观点进行论证,所以简单地说,讲故事是保证理论能够更容易地被读者接受的一种手段。从这个角度上讲,《增长黑客》这本书还是很有趣的。
近几年美国硅谷也好,国内中关村也好,互联网创业热不亚于当年旧金山淘金热。大量的创业小公司如雨后春笋般出现,每一个创业公司都打着“改变世界”的旗号在勇往直前。但是创业公司资源少是一个短板,人力也好经验也好金钱也好,都无法和拥有海量用户的巨头相提并论。于是如何顶着创业失败率高达四分之三的压力,以四两拨千斤,用最低的成本获取最迅速的用户成长就成了创业团队最为关切的问题。毕竟对那个经典的问题“你做的这个东西如果腾讯做了你咋办?”只有一个答案:天下武功,唯快不破。创业团队的优势就是快,只有利用好这一优势,在大公司进入新领域之前先把市场占了,才能获得一线生机。于是这种人人创业的热潮直接催生了行业对增长黑客的需求。
增长黑客涉及开发、设计、产品以及市场运营,简直就像万金油,而万金油一般都不太受待见。于是 Andy Johns, 这位曾带领 Facebook, Twitter, Quora 等大公司增长黑客团队的领袖人物,通过数据分析,改善用户体验细节,提出各种颇具创意的想法,从而帮助这些公司获得瞩目的成绩。他的成功让业界看到增长黑客不是海市蜃楼,不是挂羊头卖狗肉,真正的增长黑客是可以帮助公司获得显著的用户增长的。
但是这个行业还比较新,还没有人能够发展出系统的理论来。《增长黑客》这本中文书的出现在一个很好的时机——一个人人创业的时代,作者在书前的推荐序里面也贯彻了自己书中提到的理论:如果先渗透到那些具有社交影响力的人群当中,我们就能以此为基础向其他人扩散开来。于是作者在推荐序里面罗列了一大堆互联网大小V 的简评。推荐序这种东西大家都知道很水,很多人其实没看过书也会应邀写上一两句,虽然在部分读者看来这种做法有点滑稽,但是不可否认的是,这种营销手段是有效的——至少这本书在 z.cn 的排名不低。
要知道营销的对象不是互联网的从业人员这种小众群体,而是绝大部分会买单的普通人。有一句不雅的话叫“得屌丝者得天下”,我还能记得数年前因为 @Fenng 的一句话就跑去买凯文凯利的《失控》用来装逼,事实上我并没能看完这本书,也并不觉得这本书真的有趣到可以让我读完它。所以我个人的读书习惯是上来就读正文,跳过序和前言,读完全书才回过头来,看看这些水分里面能否发现些干货。
这本书的推荐序里没什么干货,但是书中的案例都是颇具启发的故事。所以对渴望获得用户增长的团队也好,个人开发这也好,甚至只是想了解互联网公司八卦的人也好,这本书都算值得一读的了。
15.12.09/中午
于 T.i.T
1905 年爱因斯坦提出狭义相对论之后无疑给科幻小说世界带来一大福音,从相对性原理和光速不变原理我们可以得出移动速度越快的物体,它所过的时间越慢这个颠覆性的结论。这个原理给科幻小说带来许多幻想空间,诞生了许多不错的作品。今天我们要介绍的是 1987 年包揽了科幻文学两大最高奖项的作品——《死者代言人》,这部作品就巧妙地利用了相对论的“时间膨胀”原理。
有些读者可能以为相对论还只是处于理论研究阶段,事实上我们的 GPS 卫星由于绕地飞行的速度很快,每天都需要做一次时间校正,这就是现实中相对论应用的例子。但是这种差距还比较小,如果我们的宇宙飞船航行速度快到可以接近光速,当我们坐在飞船上去宇宙旅行的时候,可能只是出去玩了一天,回到家自己的兄弟姐妹都已经变成老人家了。
这种有趣而新颖的体验会给我们人类社会带来什么样的冲击是非常有趣的事情,于是Orson Scott Card,《安德的游戏》的作者,就以这种方式把虫族终结者安德 (Ender) 带到三千年之后,扔进了续作《死者代言人》(Speaker for the Dead)一书中。
在第 99 期的每周读书中我们曾介绍过,作者先是想写《死者代言人》,然后顺便把《安德的游戏》给写了的。其实这个说法并不严密,早在 1977 年,作者就已经写了短篇版的《安德的游戏》,故事从安德在战斗学校组成飞龙战队开始,一直到虫族战争结束。这个短篇故事非常完整,没有姐姐华伦帝,没有哥哥彼得,也没有战争结束后的事情,情节紧凑,故事悬念丛生,一路推到最终幕,非常流畅,是一篇不错的作品。
后来作者从葬礼获得了灵感,觉得大家对逝者的致辞都是自己的伪饰,逝者已矣,大家只是单纯地想把他幻化为更容易被世人接受的形象罢了。于是作者觉得应当写一部真正给死者代言的书,哪怕真相惊天动地,也要一字不差地说出来。
这时作者的一个朋友问他为什么不写一个安德的续集,于是作者就开始构思怎么才能把安德带到这本书里去。就在作者写完提纲准备开始《死者代言人》的创作的时候,他忽然发现开头根本写不下去。因为安德不会莫名其妙突然就变成死者代言人,要解释这一段故事就必须写一个超级长的开头。于是作者灵机一动:不如把《安德的游戏》从短篇扩充成长篇吧!
把短篇小说扩充成长篇这件事情,很好地解答了我们在 99 期中遇到的几个疑问:一是小说开头进展缓慢有些无趣:这是由于短篇版没有这段开头,为了解释安德为什么要去战斗学校以及三兄妹的羁绊,才补充了个超级长的开头;二是华伦帝和彼得这两兄妹联手比正品的主角还要厉害,但是作者又非常吝惜笔墨:这当然是因为这两个人本来就不存在,存在的意义只是为了解释安德为什么要到另一个星球去。因为地球有彼得安德不想去,刚好华伦帝想去虫族星球于是安德就顺理成章一起去了。三则是结局冗长,其实虫族战争结束后小说应该就可以结束了:短篇里是在虫族战争后就结束了的,但是为了解释续集,才有了超级长的结局,唉。
当然这部《安德的游戏》也拿下了雨果奖和星云奖,不过基于上期我们说的,我觉得事实上这部扩充后的前传水平一般,反而短篇版本非常不错。
尽管作者很努力想要把《死者代言人》这部续作写成一部独立的小说,读者们即使没看过前作也能够理解、沉入故事体验,但是我个人认为如果没有前作的铺垫,安德的形象会相对薄弱一点,说服力可能就没那么强。
不过即便如此,这部续作也是比前作好得多的作品,是一部真正担得起雨果奖与星云奖的作品。在前作中,安德只是一个处处受限的小孩,完全没法发挥最高统帅的能力,所以人物魅力就打了个折扣。另外前作的场景却主要集中在战斗学校狭窄的室内,对于一部科幻小说,宇宙空间的广袤对故事场景的帮辅作用巨大,这点上基本无用了。最后是科幻小说中时间的变化,《三体》是通过冬眠这一手段来穿越时间,在《安德的游戏中》则是让上一次打赢虫族的人类最高统帅马泽坐在光速飞船里,利用相对论来实现穿越时空。然而也只是提了一下而已,有点小气。
直到《死者代言人》中,作者终于得以把以上三点发挥得淋漓尽致。在打赢虫族战争之后安德已经是全世界人类的最高统帅,他享有的权力是无人能及的,经过多年的光速旅行之后,时间已经过去了三千年,而安德也已经三十多岁。这时候,天才步入中年,多了隐忍与稳重。虫族战争是人类世界的重大事件,安德由此蜚声四海,成为一代大魔王。再加上安塞波网络中诞生的“人工智能”——简的辅助,安德已经是三千年后的超人,人格魅力达到顶峰,与当年的小屁孩已不可同日而语。
光是这个人物设定就已经令人兴奋,而三千年后的人类,足迹已经踏入宇宙,除了虫族、人工智能简之外,还有另一个外星异族——猪仔(Pigges)也站上了舞台。猪仔是一个幻想的新种族,他们的语言是怎样的,社会结构是怎样的,人类与猪仔的相互影响是怎样的,这里面充满了无限可能,作者很机智地选择了外星人类学家的视角,一点一滴的探索都是令人惊喜的发现。
本作中有频繁的时空旅行,有藐视光速的即时通讯手段安塞波(?ansible),人生巅峰的主角安德,有未知的落后外星种族猪仔,所有的高级素材已经齐备,就看作者如何下笔了。
《死者代言人》的悬念设计不再像前作一样简单直白有如少年漫画,而是含蓄了隐约,在故事的推进过程中埋下盲点,像推理小说,撩动读者的心弦却迟迟不揭露真相,吊足读者胃口。
由于有了时间旅行这一利器,作者可以轻易操纵几十年的人生,用于铺垫一个悲剧,一个真相,一个死者一辈子不曾道出的故事。这本书的英文名是 Speaker for the Dead,所以本作的主要目的是解开死者的谜团,不是群众想象的美好,也不是当事人刻意的谎言。为了解开这个真相,需要有一个于世独立的第三者来挥下利剑。
于是作者在故事一开始就在猪仔的星球上安排了一场谋杀,死者是研究猪仔的外星人类学家皮波。这场冲突直接把人类与外星文明直接对立起来,把死者代言人安德推入这场混乱之中。
由于时间旅行的特性,作者有足够的时间来安排安德旅行期间猪仔星球上故事的变化,尽管人类社会最终判定猪仔这个落后文明不是蓄意谋杀皮波,但是人类与猪仔之间毕竟留下了裂缝。在安德赶往这个地方期间,事态有了很不一样的变化。这些变化出人意料,又在情理之中,给安德留下了许多难解的问题。
我觉得安德到了这个地方之后就像柯南一样,一点一点地用侦探之眼解开故事的迷云。这点上,较之前作只是蒙着头往前冲杀,安德要成熟了许多。在《死者代言人》一作中,行事特立的安德更容易吸引读者的喜爱,古板腐化的教会与政府更容易令读者产生对立心态,二者的冲突就是读者内心的冲突,人类与猪仔的复杂情感就是读者内心的复杂情感,达到这样的阅读体验很不容易,但能够做到的通常都是好作品。
小说之所以能令人沉浸其中是因为他基于我们的现实社会,却充满幻想的体验,我们可以代入到角色当中,去感受我们平时可望而不可及的事情。随着安德做星际穿越,以安德视角去看待外星文明,站在安德身边与僵化的政府怒目横对,我们的内心充满了各种渴望,各种梦想。
现实是局限的,现实是孤独而平凡的。我们有一颗不甘于平凡的心,却身处于这个平凡的世界。所以我们阅读,我们在阅读中体验到灿烂的幻想与精彩。科幻小说是基于科学的幻想,也是我们基于现实的梦。
15.11.28/下午
于自宅
2022-08-20 原《每周读书》系列更名为《枫影夜读》
为什么基于原著改编的电影电视剧总是无法满足书迷读者?为什么看习惯了漫画之后第一次看到动画觉得配音演员的声线好奇怪?当我们阅读小说的时候,我们倾慕于小龙女的清丽脱俗,我们沉湎于黄蓉的古怪精灵,我们惊叹于洛神的柔情绰态,然而我们真的看清了这些倾城美女的容颜了吗?
来自美国 Alfred A. Knopf 出版社的艺术副总监 Peter Mendelsund 通过 What We See When We Read 一书回答了这一问题。?Mendelsund 是一名书籍封面设计师,这部作品的装帧也同样出自其手,在我看来,这本设计多于文字内容的书之所以能在豆瓣拿下 8.1 的高分,在 Good Reads 拿下四星,一部分是出于其设计多于内容的独特,另一部分则是 Mendelsund 的个人魅力所致。
Mendelsund 并不是科班出身的设计师,他原是一名钢琴演奏家,日子过得并不宽裕。后来他因缘际会找到企鹅出版社的封面设计师 Chip Kidd,带着自己设计的音乐专辑封面。没想到 Chip Kidd 很欣赏 Mendelsund,于是他就忽然从一个音乐家转行成为设计师了。他擅长于使用简单的文字、几何图形、明丽的色块来做封面设计,这种独特的风格也给书籍装帧带来一股清丽之风。
至今 Mendelsund 已经为许多书籍做过封面设计,我们可以在 http://covers.petermendelsund.com/ 这里找到他的设计,里面甚至有余华的 China in Ten Words。
Mendelsund 有一个非常重要的原则:封面设计要忠于文字。Mendelsund 喜欢阅读,他会阅读每一本将由他设计封面的书籍,从中找出具有代表性的,能够视觉化表达的东西,然后融入他的涉及。
Reading with a mind to designing a jacket is very different from just reading. When I’m reading for work, I’m looking for something described in the book that will be reproducible visually and that will serve as an emblem for the entire book—a character, or an object, or a scene, or a setting. That’s not the way one reads when one is simply immersed in a book.
于是他在积累设计经验的同时,也阅读了大量的作品。2014 年 Mendelsund 出版了两本书,一本是关于书籍装帧设计的 Covers,另一本就是关于阅读的 What We See When We Read。前者谈设计,后者谈阅读。二者其实有相互印证之处,比如作者喜欢用的抽象图形,为什么不使用真实的人物照片来作为封面设计呢?我记得我第一次看到《孤儿列车》的封面时,我一方面为靠着火车窗的小女孩忧伤的眼神为感染,一方面又为封面把人物的形象过于具体地展现在我面前而感到不舒服。作者认为,我们在阅读的过程中虽然自认为能够认清人物的形象,但是事实上都无法清楚地描绘出人物的体貌,你能够记得人物的性格,人物的动作,以及事件,但是不管小龙女也好黄蓉也好,在你的脑海里她们都是模糊的。而这一点,正是阅读的精彩之处,是阅读提供给读者自由想象的空间,那些以人物照片为封面的设计,反而剥夺了读者想象的权利。
由于阅读的不确定性,作者在给本书设计封面的时候,也采用了简单的文字搭配钥匙孔的图案,仿佛要往钥匙孔中窥视什么。在书中,作者试图以一种新颖的手法,用插画的视觉呈现来表达文字本身无法明确的事情,很有意思。
但是作者是一个优秀的设计师不代表就一定是个优秀的作家,老实说这本书的文字部分有点无聊,有点啰嗦,所以单就内容来说,这本书比较一般,但是设计给这本书加分不少,可以说这本书的重点在设计,而不在内容。
2015.11.21/下午
于自宅
本文译自:http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1。Github 上有一篇此文 Objective-C 版本的翻译,看完没有什么印象,所以干脆把 Swift 版本的翻译一遍。
尽管 Grand Central Dispatch(或简称 GCD)已经出来好一阵子了,但还是有些开发者没能完全理解 GCD 的原理。这也是可以理解的,并发本来就是开发中比较难懂的部分,GCD 的 API 看起来就像锋利的尖角扎入 Swift 平滑的世界中。
本文将分为两部分,深入浅出地讲解 GCD。第一部分会讲解 GCD 能用来做什么,包括一些基础的用法。第二部分会讲解更多 GCD 的高级用法。
GCD 是 libdispatch 的别称,是 Apple 提供的用于在多核 iOS/OS X 机器上执行并发代码的库。GCD 将带来几个好处:
在了解 GCD 之前,你需要确保完全理解多线程和并发相关的几个概念。这些概念可能比较模糊或者区别不大,容易搞混,所以请花点时间复习一下这几个概念。
这两个词描述的是任务执行时彼此间的关系。串行执行的任务,每次只有一个任务在执行。并发执行的任务则有可能同时执行多个任务。
在本文中你可以把一个任务当作是一个闭包(Closure)。不过实际上在 GCD 里你是可以用函数指针来执行任务的,不过大多数情况下要麻烦的多。直接用闭包就简单多了!
不清楚 Swift 中的闭包是什么?闭包是是指自给的,可以被调用、存储和传递的代码块(Closures are self-contained, callable blocks of code that can be stored and passed around. )。当一个闭包被调用的时候,它们看上去就是个普通的函数,可以传参数进去,也可以有返回值。另外,闭包作用域外的变量如果被闭包使用到了,就会被闭包“捕获”——就是说在闭包内可以看到外部变量并记住外部变量的值。
Swift 的闭包跟 Objective-C 的 block 很像,并且两者是可以互相转换的。唯一的限制就是,ObjC 代码不能使用 Swift 闭包中暴露的 Swift-Only 的特性,比如元组(Tuple)。但是在 Swift 中使用 ObjC 的 block 是没有任何限制的,所以你在本文中看到 ObjC 的 block 的时候,你可以完全可以直接替换成 Swift 闭包。
这两个概念描述的是一个函数何时会把控制权返回(Return)给调用者,当交还的时候,有多少工作已经完成了。
一个同步函数会在它执行的任务完成之后返回。
一个异步函数则不会一直等到任务完成,而是立刻返回,同时执行任务直到结束。因此,异步函数不会阻塞当前线程。
请留意,当你看到同步函数会“阻塞” block 当前线程的时候,请不要和名词 block 混淆。(译者注:中文无此问题,这里省略 100 字…)。
临界区值得是不能被并发执行的代码块,也即不能被两个及以上的线程同时访问。这通常是因为这段代码访问了一份共享资源,而且这个资源在被访问的过程中不能中断。
当软件系统的行为依赖于无法控制的事件的执行顺序时(比如程序并发任务的的执行顺序),我们称这种情况为竞争条件。竞争条件会产生无法预测的结果,通常代码走查也没法发现明显的问题。
如果两个(有时候多个)线程都在等着对方完成任务才能执行自己的任务时,这种情况我们成为死锁。第一个线程要等到第二个线程执行完才能执行,而第二个线程则在等着第一个线程,所以两个都永远无法执行。
线程安全的代码可以被多个线程或者并发任务安全地调用而不会引发任何问题(比如数据损坏,崩溃之类的问题)。没有线程安全的代码同一时间内只能被在一个上下文中运行。举一个线程安全的例子:
这个数组是只读的,所以你可以在多个线程中同时使用而不会有任何问题。相反的,如果你声明一个可变数组:
则这个数组就不是线程安全的,它可以同时被多个线程访问和修改,造成不可预知的后果。可变的变量和数据结构在同一时间只能被一个线程访问。
一次上下文切换是指在一个单核处理器中,保存和恢复不同线程的执行状态的过程。在编写多任务应用的时候,这个切换是很常见的,但是这种切换也会带来额外的开销。
并发和并行经常一起被提及,所以简单解释一下这两者的区别还是很有必要的。
独立的并发代码块是可以被“同时”执行的,但是如何实现“同时”却是由系统决定的——甚至根本就不是“同时”执行也是有可能的。
在多核设备上,多线程可以在多个 CPU 上并行执行;单核设备为了实现并发,就得先运行一个线程,然后做上下文切换(Context Switch),从而实现线程和进程切换。这种切换一般进行得非常快,让用户感觉像是同时在运行多个线程一样。详见下图:
所以,虽然你可以使用 GCD 的接口来编写并发代码,但是 GCD 才是真正决定是否使用并行实现的人。并行要求并发,但是并发并不一定能够保证并行。
更深一层地说,并发设计其实是结构的设计。如果你带着 GCD 的思维去编写代码,你就得小心地设计代码的结构,暴露的接口要考虑可以同时执行和不可以同时执行的代码。如果你想更深入地探究这个课题,你可以参考 Vimeo 上 Rob Pike 的这个演讲。
GCD 提供了多个 dispatch queue (分发队列,由于这里跟代码同名,直接使用英文更容易理解,下文遇到此名词都使用英文) 来处理提交的任务;这些队列负责管理使用 GCD 接口提交的任务,并以先入先出(FIFO)的顺序执行。这就保证了最早加入队列的任务会第一个被执行,第二个添加的则第二个被执行,以此类推。
每一个 dispatch queue 都是线程安全的,也就是说你可以同时在多个线程操作同一个队列。当你理解 dispatch queue 是如何给你自己的代码提供线程安全的时候,GCD 的好处就显而易见了。这里线程安全的关键在于选择正确类型的 dispatch queue,以及分发函数(dispatching function)。
串行队列中的任务每次执行一个,只有等上一个任务执行完了才会开始执行下一个。而且,你并不知道两个任务结束与开始之间的间隔时间你是不知道的,如下图所示:
这些任务什么时候开始执行完全是由 GCD 内部控制的,你唯一能知道的事情就是,GCD 保证了这些任务会按照进队列的顺序执行,而且每次只执行一个。
由于顺序队列里面不可能有两个任务并发执行,所以这个队列的任务就不会有同时访问临界区的风险;这样的实现保证了在这些任务中不会有由于竞争条件产生的临界区问题。所以如果临界区只会被队列中的这些任务访问,你就可以说这个临界区是安全的。
并发队列保证任务开始执行的顺序跟它进入队列的顺序相同……而且,这就是并发队列唯一能保证的事情!所有的任务可以以任意顺序结束执行,而且你完全不知道开始下一个任务之前的时间间隔是多少,甚至不知道任意时刻到底有多少任务在同时运行。所以,这又是一个完全取决于 GCD 的事情。
下图的例子展示了一个由 GCD 执行的有4 个任务的并发队列:
注意到上图中 Task 1 要等到 Task 0 开始执行了好一会儿才开始执行,但是 Task 1, 2 和 3 之间的间隔却很短,一个紧接着另一个。还有,Task 3 在 Task 2 之后才开始执行,但是却比 Task 2 更早结束。
任务什么时候开始执行是完全取决与 GCD 的。当两个任务的执行时间有重叠的时候,GCD 会决定这两个任务是应该分开两个核执行(如果有空闲的 CPU)还是使用上下文切换在一个核中执行两个不同的任务。
有意思的是,GCD 给顺序和并发类型的队列各提供了至少五种特殊的队列供你选择。
首先,系统提供了一个特殊的顺序队列:main queue。跟所有的顺序队列一样,该队列中同一时间只执行一个任务。不同的是这个队列中所有的任务都保证会在主线程中执行,主线程也是唯一一个可以更新 UI 的线程。这个队列也是用来给 UIView 对象发消息(send message,这里的 message 应该理解为 ObjC 的 message),或者发送通知(notifications,这里的通知应该指 NSNotification)。
系统还提供了几个并发队列。这些队列都有他们自己的 QoS 类(Quality of Service)。QoS 类主要用来描述任务的执行目的以便于 GCD 决定这些任务的优先级。以下是 QoS 类:
这里要注意苹果的 API 也会使用这些全局的 dispatch queue,所以你提交的任务并不是这些队列的唯一任务。
最后,你也可以创建你自己的顺序队列和并发队列。也就是说,你至少需要处理五个队列:主队列,其他的四个全局队列,以及你自己创建的自定义队列。
以上就是 dispatch queue 基本的样子。
使用 GCD 的艺术在于如何针对你提交的任务选择合适的队列以及分发函数。而学习这项技能最好的办法就是跟着本教程过一遍接下来的示例项目,我们也会在这个过程中提供一些通用的建议。
由于本教程的目的主要是如何优化以及如何安全地使用 GCD 编写多线程代码,我们的示例教程就由一个现成的项目开始,这个项目叫 GooglyPuff。
GooglyPuff 是一个还没经过优化而且没实现线程安全的应用,这个应用使用 Core Image 的人脸识别 API 把漫画眼睛贴到照片里眼睛的位置上。图片可以从系统相册选择,或者从预设好的 URL 下载。
整个项目可以从这里下载:GooglyPuff_Swift_Start_1
下载解压后,在 Xcode 中打开并运行,你会看到如下界面:
我们注意到点击“Le Intenet”去下载图片的时候,一个 UIAlertController 立刻就弹出一个警告框,后面我们将一起修复这个问题。
这个项目有四个主要的类:
现在让我们回到应用,选择从系统相册或者 Le Internet 下载图片。
留意一下从你在 PhotoCollectionViewController 中点击一个 UICollectionViewCell 到弹出一个新的 PhotoDetailViewController 需要多久;这里我们应该可以注意到有明显的延迟,尤其是当你查看一张很大的图片或者使用的是很慢的机器的时候。
UIViewController 的 viewDidLoad 方法是最容易堆积一大堆代码的地方,经常导致 view controller 需要等很久才能出现。所以我们要尽可能把不重要的任务放到后台去完成。
这听起来是 dispatch_async 可以做的事情!
打开 PhotoDetailViewController,把 viewDidLoad 方法修改成以下实现:
// Resize if neccessary to ensure it's not pixelated
if image.size.height <= photoImageView.bounds.size.height &&
image.size.width <= photoImageView.bounds.size.width {
photoImageView.contentMode = .Center
}
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { // 1
let overlayImage = self.faceOverlayImageFromImage(self.image)
dispatch_async(dispatch_get_main_queue()) { // 2
self.fadeInNewImage(overlayImage) // 3
}
}
}
[/sourcecode]
上面的代码到底干了什么呢:
这里要注意我们用了 Swift 的尾随闭包语法(Trailing Closure Syntax),给 dispatch_async 方法传参的时候可以在括号后面直接写闭包的代码,这种写法可以让你的代码看上去更简洁一些。
现在重新编译运行这个工程,选择一张图片,你会发现view controller 展示比之前快多了,过了一会才把动画眼睛加上去。这带来一种很好的体验,你可以感受到照片被修改前后的对比带来的冲击。而且,如果你尝试加载一张特别大的图片,应用也不会在加载 view controller 的时候被卡住,这大大增加了应用的扩展性。
我们上面也提到了,dispatch_async 会把闭包里的任务加到队列里面,然后函数立刻返回。GCD 会决定什么时候执行这个任务。所以对于网络请求或者耗CPU 的任务你应该用 dispatch_async,这样才不会卡住当前线程。
以下是几条关于在使用 dispatch_async 函数时应该选择什么类型队列的建议:
你可能会发现每次使用 dispatch_get_global_queue 还得传一个 QoS 类作为参数来获取对应的队列,这种写法有点累赘。这是因为 qos_class_t 被定义成一个结构体,而且 value 属性是 UInt32 类型,传参的时候还得转成 Int 类型。所以通过给 Utils.swift 文件添加几个 helper 变量我们可以更方便地获取这些全局队列:
var GlobalUserInteractiveQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
}
var GlobalUserInitiatedQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
}
var GlobalUtilityQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)
}
var GlobalBackgroundQueue: dispatch_queue_t {
return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)
}
[/sourcecode]
回到 PhotoDetailViewController 类的 viewDidLoad 方法,把 dispatch_get_global_queue 和 dispatch_get_main_queue 换成我们刚加的 helper 变量:
这样修改后的代码可读性更好,更容易看出是使用了什么类型的队列。
让我们回想一下这个 App 的用户体验,有可能用户刚打开这个 App 的时候并不知道该怎么操作对吧?所以,如果 PhotoManager 类里面还没有照片的时候,我们可以先展示一个提示页面。而且这个提示不能展示得太快。
延迟一秒钟再展示这个提示页面应该可以满足我们的需求。
把以下代码添加到 PhotoCollectionViewController.swift 文件中:
当 UICollectionView Reload 的时候,viewDidLoad 方法会被触发,从而调用 showOrHideNavPrompt 方法。
编译运行一下,我们将会看到延迟一下之后这个提示页面就弹出来了。
dispatch_after 方法就是一个可以设置延迟调用的 dispatch_async 方法,你始终无法精确地控制真正的调用时间,dispatch_after 函数返回后你也无法取消这次调用。
那么什么情况下用 dispatch_after 方法比较合适呢?
单例,让人又爱又恨的东西,它们就像网上的猫咪一样充斥整个世界 :]
单例最常令人担心的一点就是:它们经常是非线程安全的。而单例又常常是被多个 controller 同时访问的。PhotoManager 就是一个单例,所以我们还得考虑给它加上线程安全的代码。
这里主要有两个时机是需要我们保证线程安全的,一是单例在初始化的时候,另一个就是对单例进行读写操作的时候。
我们先考虑第一个问题。因为 Swift 在全局作用域中初始化变量时的特性,第一点变得很容易解决。在 Swift 中,全局变量是在第一次被访问的时候进行初始化的,而且是保证初始化过程是原子的。就是说,初始化代码是被当做临界区对待的,知道这段代码执行完了,其他的线程才能访问这个变量。Swift 是怎么做到这一点的呢?在我们看不到的代码里面,Swift 是使用 GCD 的 dispath_once 方法来实现的。有兴趣的同学可以看看这篇博客:https://developer.apple.com/swift/blog/?id=7。
dispatch_once 会以线程安全的方式执行闭包,而且只会执行一次。如果有一个线程正在执行临界区代码——这个任务在 dispatch_once 方法中——那么其他线程就会暂时被 block,直到临界区执行完毕。一旦这个闭包执行完毕,其他线程将不会再次执行这个代码。再加上用 let 把单例的实例变量声明成一个全局常量,我们就可以保证在以后的代码中这个实例不会被修改。从这个角度上看,所有的 Swift 全局常量实际上都是单例,而且自带线程安全的初始化方法。
但是我们还是要考虑读写操作的线程安全。Swift 虽然可以用 dispatch_once 保证初始化代码是线程安全的,但是这个方法没法保证它的成员变量都是线程安全的。举个例子,如果一个全局变量是一个类的实例,当这个类在内部操作这个变量的时候,你还是有可能遇到临界区问题。所以我们需要用别的方法来保证这些操作是线程安全的,比如说同步数据访问,下面几个章节我们将会讲到。
实例的线程安全问题并不是只有单例才需要关心。如果一个单例的属性是一个可变的对象,比如说 PhotoManager 的 photos 数组,那么你就得考虑这个对象本身是否线程安全的。
在 Swift 中,所有用 let 关键字声明的变量都是只读的,而且线程安全的。var 关键字声明的则表示可变而且除非这个数据类型的设计本来就是线程安全的,否则都是非线程安全的。Swift 的集合类型如 Array 和 Dictionary,如果声明是可变的就是非线程安全的。那么基础的容器类型呢?比如说 NSArray 这种类型?他们是线程安全的吗?答案是——“恐怕并不是”!Apple 官方维护了一个线程安全与非线程安全类型的列表大家可以瞧瞧。
虽然说多个线程同时读一个 Array 变量是安全的,但是如果有一个线程正在对它做写操作就悲剧了。现在回到我们的示例工程,这里面我们的单例都还没有做这样的保护。
比如我们来看 PhotoManager.swift 中 addPhoto 方法:
因为这个方法修改了一个可变数组,所以这是一个写方法。
然后我们在看 photos 这个属性:
这个属性的 getter 方法反悔了一个可变数组,所以是一个读方法。调用这个方法的人会得到一个 _photos 数组的拷贝,以防被别人不小心修改了,但是如果有一个线程正在修改这个数组,另一个线程正在读这个数据就悲剧了。
注意:为啥上面这段代码里调用者会得到一个 _photos 数组的拷贝呢?因为在 Swift 里面,函数的返回类型要嘛是一个引用要嘛就是一个数值。返回引用就跟 ObjC 里面返回指针是一样的,你可以对原来的对象做任何操作。但是返回一个数值则会得到一个原对象的拷贝,对这个返回值的修改不会印象到原对象。默认情况下,Swift 的类实例会以引用方式或者结构体的值的方式传递与返回。
Swift 的内置数据类型,比如 Array 和 Dictionary,都是以 Struct 结构体的形式实现的,所以当你在代码里面传递这些值的时候,你传递的是一大堆结构体的拷贝。不过你不需要担心由此引起的内存消耗,Swift 对集合类型做了优化,只有在必要的时候才会真的做拷贝。例如,只有当一个数组被当做参数会返回值传递的过程中,第一次被修改的时候,才会做一次拷贝。
这是在软件开发中一个经典的读者写者问题,GCD 提供了一个优雅的解决方案——使用 dispatch barriers 创建读写锁。
Dispatch barriers 是一组方法,它们的表现就像在并发队列遇到串行队列的性能瓶颈一样。使用 GCD 的 barrier API 可以确保提交进队列的闭包代码在被执行的时候只有一个任务在跑。也就是说,所有比 diaptch barrier 先进队列的任务要优先被执行完,然后再会执行这一个闭包。
轮到这个闭包执行的时候,GCD 会确保在执行过程中有且只有这一个任务在跑,直到它执行结束,才会恢复队列原来的实现。GCD 还提供了同步和异步的 barrier 方法。
下图展示了 barrier 方法在异步队列中的表现:
注意到通常情况下这个队列跟普通的并发队列没有两样,但是当 barrier 代码被执行的时候,这个队列看上去就像一个串行队列。换句话说,barrier 是当下唯一被执行的代码。当这个 barrier 结束了之后,队列就又会回到一个普通的并发队列的执行方式。
那你啥时候改用 barrier 方法,啥时候不该用呢?
看起来上面说的唯一正确的选择是自定义并发队列了,所以我们要创建一个自己的并发队列,用来处理 barrier 方法,从而是读写操作分离。这个并发队列将会允许多个读操作同时执行。
打开 PhotoManager.swift 文件,添加以下属性:
这句代码使用 dispatch_queue_create 把 concurrentPhotoQueue 初始化为一个并发队列。第一个参数是一个反向域名风格的命名,确保这个命名的含义有助于我们以后调试。第二个参数用来表示这个队列是串行的还是并行的。
注意: 如果你在网上搜索 dispatch_queue_create 的示例代码,你经常会看到大家用 0 或者 NULL 作为第二个参数。这实际上是用来创建串行队列的一种过时的方法,更好的做法还是把这个类型写清楚。
找到 addPhoto 方法,替换成以下代码:
这个修改解决了写操作问题,接下来我们来看看读操作的问题。
为了确保读写操作都是线程安全的,我们需要把读操作也放进 concurrentPhotoQueue 这个队列里面。但是因为你需要函数的返回值,所以这个地方我们不能用异步调用的形式来写。在这里我们使用 dispatch_sync 方法。
dispatch_sync 会同步地提交当前的任务并且一直等到这个任务执行完毕才把函数返回。当你需要确保这些任务能够与 dispatch barrier 合作时,或者当你需要等待闭包的处理结果,然后才把函数返回时,你可以用 dispatch_sync。
但是使用这个函数要非常小心。试想你把 dispatch_sync 用在一个并发队列上,当这个队列跑起来的时候你就会遇到死锁问题。因为这个同步调用会一直等到前一个闭包执行结束才会返回,但是当前的queue 却在等待 dispatch_sync 的完成才能执行闭包代码,于是造成死锁。这就要求你一定要清楚地知道你是在哪一个队列发起的调用,同时也要搞清楚你是塞进去了哪一个队列。
以下是 dispatch_sync 使用时的概览:
自定义串行队列 Custom Serial Queue: 在这种队列上使用要非常小心。如果你在同一个队列的任务里面使用 dispatch_sync 到同一个队列,那你绝对会遇到死锁问题。
主队列 Main Queue (Serial): 跟上面那个一样要非常小心,也是很有可能造成死锁。
并发队列 Concurrent Queue: 在这种队列中,如果你想要通过 dispatch barrier 来同步任务,dispatch_sync 是一个不错的选择。
下面我们把 PhotoManager.swift 中的 photos 方法修改一下:
恭喜——你的 PhotoManager 单例现在完全线程安全了。不管你是在何时去读或者写,你都能很有自信地说这些操作都是安全的了。
可能大家还没有 100% 地清楚 GCD 的牛逼之处?首先确保一下你已经能够自如地使用 GCD 的基础API 来实现简单的多线程逻辑,最好多用断点和 NSLog 来确保你清楚正在发生什么。
下面我们提供了两个 GIF 动画来帮助你理解 dispatch_async 和 dispatch_sync。请留意 GIF 展示的每一步断点与各个队列的关系。
dispatch_sync(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog("First Log")
}
NSLog("Second Log")
}
[/sourcecode]
dispatch_sync 会把任务加入到队列中并且一直等待直到任务执行结束。dispatch_async 也做了一样的事情,唯一的不同时它不会等待任务结束而是立刻就返回。
dispatch_async(dispatch_get_global_queue(
Int(QOS_CLASS_USER_INTERACTIVE.value), 0)) {
NSLog("First Log")
}
NSLog("Second Log")
}
[/sourcecode]
在这个教程中,你已经学会了如何让你的代码实现线程安全,也知道怎么把 CPU 密集的任务从主线程挪出来。
你可以在这里下载这个项目的代码,这份代码是包含了以上所有的优化的:GooglyPuff Project 。本教程的第二部分将告诉你如何持续优化这个项目。
如果你有计划要优化你自己的应用,你需要学会怎么使用 Instruments 里的Time Profile 来测试你的代码。不过如何使用这个工具就不在本教程的讲述范围内了,你可以参考这篇文章 如何使用 Instrcments。
然后记得要用真实的设备来进行测试,用模拟器测出来的结果与真机相差甚远。
在下一篇教程中你将更加深入地了解 GCD 的API,我们将介绍更为强大的工具。
2015.11.22/夜
于自宅
译者注:由于译者太懒,此文花了许多时间来才翻译完,也许内容有些过时,不过 GCD 提供的 API 至今仍然是 iOS/Mac 上使用多线程的首选。
2022-08-20 原《每周读书》系列更名为《枫影夜读》
开始读《安德的游戏》时感觉告诉我这是一部水平很次的作品。作者一开始就直接表明了主角安德是一个很特殊的人,天才中的天才,进度到 10%+ 更直接说明安德就是他们要培养的未来的最高统帅,是要和虫族打仗拯救人类的人。于是乎,小说前半部分就沦为毫无悬念的铺垫,读之甚是乏味。
悬念是小说的驱动力,没有悬念的小说读者是不会有兴趣的。但是后来我知道这部作品写于 1985 年,于是心理预期稍微降了一点,与此同时故事进入战斗学校部分,安德作为一个天才少年,在太空校园里被刻意安排孤立,被迫进行不公平的比赛,于是前期枯燥的铺垫到了战斗学校之后开始慢慢展开,面对教官严苛的要求,安德是否能够顺利晋级?面对其他同学的孤立与排斥,安德怎样去结交好友突破困境?面对不公平的比赛,安德如何扭转劣势拿下胜利?还有最重要的,面对与日俱增的心理压力,安德能否在精神崩溃前成功毕业?
读者是否有一种似曾相识的感觉?没错,就是少年热血漫画的套路。读者如果一路读到了这一部分,很容易就会被代入,对人物的情感投资在安德一次次拿下胜利时得到回报,又在下一次面对更加苛刻的条件时惴惴不安。到了这种地步,读者已经欲罢不能,小说也就成功了一半。
然而驱使我嚼下枯燥的前半部分的动力却不是安德,反而是安德的哥哥与姐姐。这三个小孩都是天才儿童,哥哥彼得性格残暴,姐姐华伦帝则过于柔弱,只有安德兼具二者优点,所以最终是安德被送进战斗学校。对我来说,我反而更喜欢这两兄妹的组合,深觉这两兄妹加一起要远胜于安德。最让我感到震惊的是两兄妹利用互联网(Nets)隐藏自己的年龄,在网上散步自己的言论,逐步建立起各自的追随者,形成巨大的舆论影响力,最后两人政见合一,把彼得推上地球统治者这一壮举。要知道 1985 年互联网还处于理论研究阶段,公众连想到想不到的事情,更不用说准确预测到今日互联网舆论的影响力以及如何操纵网络言论方向了。可惜的是故事的主角始终是安德,作者没有在这两兄妹身上花费太多笔墨。
说回主线,安德在战斗学校的故事就是一般热血漫画的路线:遇到 BOSS,要死了要死了要死了,完了千方百计打赢了 BOSS,获得新技能和一大帮小弟,然后继续下一个数量级增长的 BOSS,不断循环,直到毕业。这一段作者的布置比较合理,故事发展非常流畅。最后以安德终于精神崩溃为契机,让安德毕业,小说推入最终幕大决战。
由于有了前面战斗学校不公平待遇的铺垫,最终幕的展开很自然,结局的反转也出人意料,是个亮点。由于是热血少年故事,安德最后肯定是大团圆结局打败了虫族,还殖民到虫族的星球上去。在那里他听到了来自虫族的遗言,于是著书《Speaker for the Dead》,中译名为《死者代言人》或《安德的代言人》。我一直觉得结局大可以到虫族消灭就结束了,后半段其实有些累赘,直到我翻到《Speaker for the Dead》的序,才晓得原来作者早期是写了《安德的游戏》的短篇,后来他想写《Speaker for the Dead》了,于是就开始构思,结果就构思出了长篇的《安德的游戏》,所以这后半部分就变得必不可少了。
1985 年《安德的游戏》出版后,第二年《死者代言人》就出版了,两部作品连着拿下了雨果奖和星云奖,作者也成为唯一一个连续两年包揽两座大奖的作家。今天再看《安德的游戏》,我觉得算是一部不错的热血小说,不过毕竟有些年头,写的也是近未来的事情,像互联网这样的想象已经成了现实大家不觉新鲜,而写作技巧上过多第三方角色的全知描述也令人乏味。所以此作可以说是不错的休闲读物吧,谈不上上佳之作。
2015.11.18/中午
于 T.i.T
2022-08-20 原《每周读书》系列更名为《枫影夜读》
一直以来救死扶伤都是医生的天职,我们病了就上医院,医生就会把我们治好,大家都认为这是理所当然的事情。但是我们又都清楚有一些病是治不好的,包括绝症,也包括衰老。
但即使罹患绝症,到了医院也还是以“积极治疗”为默认选项,大家仿佛觉得现代的医学这么发达,有什么是治不好的呢?即使是绝症,大不了化疗,以为能再活个十年,二十年。《Being Mortal》的作者 Atul Gawande 是一名医生,他道出了大家也许不知道也许视而不见的真相:绝症的治疗并不一定能把病治好,有时候能帮你延长性命,但多数时候你不但要承受治疗的痛苦,还很可能恶化你的病情。
作者从医生受教育时被灌输的观念开始说起,仿佛全世界都认为医生就是应该要积极地把病治好,无论可能性有多小,都要去努力,仿佛努力了就是好的,而忽略了努力背后的痛苦。比如说化疗,你要躺在一个完全契合你身体的模子里一动不动,眼睛看不见,耳朵旁边就是轰隆隆的放射机器在旋转,移动的方寸不过几毫米,每天要躺上一个小时。比如说吃药,你需要吃各种各样奇怪的药,药都有副作用,你克制了头痛可能就导致呕吐,你控制了呕吐又可能导致昏昏沉沉。再比如治疗的费用,美国已经是一个医疗非常发达的国家,高额的治疗费还是会让人透不过气来,更不用说我们中国,动不动就是倾家荡产的事情。假使这些都不是问题,还有更严重的,你接受了这些对身体伤害特别大的治疗之后,很可能并没能帮你控制病情,你会继续恶化,而且恶化得更快,你会生活不能自理,无法自己上厕所自己洗澡,你会开始感受到作为一个人的基本的尊严已经丧失殆尽。
所有的人都忽视了一个非常重要的问题:如何面对死亡?
一旦你罹患绝症,你就一定要全身上下插满管子躺在病床上痛苦不堪地走完人生的最后阶段吗?“积极治疗”仅仅是医生提供给病人的一个默认选项,也是大家一上来就知道可以选择的一个选项,但是大家有没有想过这个选项是否一定就是最好的呢?很可能大家仅仅是没有考虑过其他的选项,因为太沉重,根本没法去想。即使在开放的美国,《Being Mortal》提到的“姑息治疗”、“善终服务”也仅是刚刚起步。
这样的服务将对病人的病因不做处理,不会提供痛苦的治疗,只是在病人生命的最后阶段,提供各种必要的生活护理服务,以及最重要的——麻醉药与镇痛药。与其在 ICU 里插满管子痛苦地死去,病人更愿意选择在自己的家里,在自己生命的最后时刻与自己的家人一同度过,做着自己还能做的事情,有尊严地死去。
不仅仅是绝症患者,老年人也会遇到同样的事情。老年人的身体由于各种各样的原因会开始日渐无法自理。在现代医疗尚未普及的时代,我们的生命曲线都是非常陡峭的,老年人能活过 50 岁的很少。但是现代医疗发达了起来,我们的曲线开始缓和延长,每次我们开始走陡峭的下坡路的时候,医院总是可以把我们拉回来,于是我们仅仅是离死亡近了一些,却不再会突然之间死去。老年人的问题于是也成为这个还没有准备好的社会急需解决的一个问题。
经过多年的发展我们产生了疗养院这样的解决方案。但疗养院是一个仿如监狱的地方,统一时间睡觉,统一时间起床,不允许某些病人吃饼干巧克力,不允许吃什么什么,要求病人一定要坐轮椅以防摔倒等等。在疗养院一个人感受不到做人的感觉,感受不到自己的价值。因为疗养院的要求是保证“安全”,除此之外都是次要的。这跟大多数人对绝症患者的要求是一样的:活下去,无论多么痛苦多么没有尊严。
人并不总是以活下去为底线的。人还需要活得有尊严,活得开心。假使我们以第三者的身份去考虑事不关己的病人,放弃积极的治疗,让病人能够有尊严,开心地走完最后的日子可能会很容易接受。但是一旦这种事情发生在自己身上的时候,我们肯定还是会手足无措。
作者采访了很多老人与病人之后,终于他自己也面临了这个艰难的问题:他的父亲罹患绝症,是继续治疗还是选择“善终服务”?
现代医学虽然发达,但是也博大精深,作者自己是医生,父母双亲也都是医生,但是面对父亲的绝症,他们依然手足无措。最后作者问父亲:什么才是最重要的?他的父亲表示:如果要我选择治疗并且瘫痪,我宁愿死。于是在手术的关键时刻出现了复杂的情况,医生问作者是否要继续手术的时候,作者第一句话就是:继续与停止,哪一种对瘫痪的可能性更大?最终选择了更具生命风险但是更不会造成瘫痪的——继续。
所以在这些至关重要的节点上,承受病痛的患者本身当然是主角,但是家人在其中扮演的角色也至关重要。如果中止手术而父亲将瘫痪度日,只怕自尊心强的父亲会觉得生不如死。
今天美国的养老与医疗制度非常发达,我们国内的条件不如美国,但是不代表我们就不应该考虑这些问题。事实上无论条件怎么样,我们都会面临这样的问题,我们总有一天会衰老,我们总有一天会面临无能为力的时刻。
在阅读这本书的过程中我一直心惊肉跳战战兢兢地,我曾有两次严重的胃病发作,病痛体虚之外连精神也变得软弱,累及我的家人朋友辛苦照顾。如果可以我当然不愿意再经历一次,但是谁知道呢?人都会老,终有一死。看着书中详述的种种,映照自己,深感恐慌。我庆幸在还算年轻的时候看到这本书,它让我了解到生命除了默认选项之外,还有别的可能。
等我老了,我希望我不至于手忙脚乱。
15.11.13/中午
于 T.i.T
2022-08-20 原《每周读书》系列更名为《枫影夜读》
有许多朋友一提到“文学”两个字就头疼,这其实源自于对“文学”的不理解,今天“文艺”两个字也被许多一知半解装忧郁的小朋友折腾到臭名昭著的地步,有点可惜,文艺本是中性的,指文学与艺术。欣赏“文艺”需要具备一定的背景知识与审美能力,这种能力以我们目前的学校所教授的课程与教授的方式来说很难培养出来。就好像要欣赏一个开源项目的代码,光会写代码还不足够。
许多人觉得“文艺”令人乏味,可能还是由于没有具备足够的鉴赏能力。比如《诗经》,一直以来教科书必备的四书五经之一,由于年代久远,文体也不够成熟,大家读起来都会感到吃力。除了类似《蒹葭》这样的寥寥数作,我觉得大多数也都挺无趣的。这时候老师的讲解就变得十分重要,师者,传道授业解惑也,然而我回忆自己在课堂上听到的《诗经》的讲解,却并没回忆出多少有趣的片段。其实语文这一门课很特别,它是讲授文学的,文学是没有办法和历史、社会、艺术分割的,但是往往教授语文的老师不会多讲历史,教授历史的老师不会多讲文学,于是我们断章取义了许多年,片面理解诗歌了许多年。后来我读到郑振铎版本的《中国文学史》([每周读书 八十五] 郑振铎——《中国文学史》),总算把历史社会背景与文学作品本身稍微联系上了,但是不够。
本周我们要介绍的是蒋勋老师的《蒋勋说文学:从诗经到陶渊明》,蒋老师在书中以独到的见解,平和的语言,结合历史社会背景以及个人的故事经历,把文艺作品讲述得十分生动。蒋老师摒弃一般人讲诗时做注解的方式,转而以审美的目光来分析,比之于严肃文学的解读,蒋老师的讲解要容易接受得多。蒋老师写过一系列讲解中国文学的书籍,本书为《蒋勋说文学》系列中的上册,下册为《从唐代散文到现代文学》。
无论是《中国文学史》还是《蒋勋说文学》,都是从上古的《诗经》开始说起。这不是偶然,《诗经》以前,我们的文字是记录在龟甲上的,多为祷文,卜文,并没有十分成体系的文学作品,《诗经》算是最早的中国文学作品集。所以《诗经》有它特殊的意义在。但是我们都怕读《诗经》,毕竟是两千年前的文字,晦涩难懂,毕竟是两千年前的文明,难以想象,而且课堂中动不动就是翻译、提炼思想,烦不胜烦。
其实《诗经》是无法翻译的,它是可歌的,是民歌的合集,它就是今天的《浏阳河》,就是《康定情歌》,是古人们情之所动而唱出来的东西。但是自从《诗经》被列入四书五经之中成了经典,她就被过分解读了,就好比今天的《圣经》、《红楼梦》,其实哪来那么多密码可以解读。
老师们在讲解的时候往往会为每句诗做注解,然而诗是“情动于中,而形于言”,情才是诗歌中最重要的东西。我自己也写过诗,写诗的时候你会很注重形式,行文会随着情感的流动而跳跃,所以诗歌会朦胧,会不解,可解的是理性逻辑的部分,不可解的是神秘感性的部分,于是在可解与不可解之间产生了美。如果我们非要把诗歌解成一个个清楚的句子,诗歌就失去了它的魅力。
蒋勋老师在讲解《诗经》的时候就不这样,他试图以一个审美者的角度,向读者讲述什么是美,诗经为什么美,美在哪里,又有哪些地方是不美的。比如蒋老师以《国殇》为例,讲解了诗歌上半部无头亡魂对生命的悲叹,又指出下半部分过于政治严肃而不讨喜。
大家上学的时候都会被要求背诗,我当时就在想,为什么《古诗十九首》没有署名?为什么李商隐李白他们风格差那么远?为什么曹操陶渊明的诗看起来都不够温庭筠的词精致但是又被人推崇?其实这些问题都源于对历史背景的不了解。蒋勋老师在讲述《古诗十九首》也好,讲述曹氏父子的诗歌也好,都会试图通过社会环境对于当时人的思想的影响来讲述,品评一个作品的好坏,是一定要放回当时的历史去看的,你不能拿十几年前的 PC 跟今天的手机做比较,十几年前的技术就应该放在当时的环境来做评价。所以要理解“魏晋风骨”要理解“竹林七贤”你就得理解当时的历史背景。
蒋勋老师在讲解的过程中,除了讲述历史背景之外,还常常结合他自身的故事来做比较。我们说《氓》这首诗里的女子常被解释为弃妇,但是蒋老师就不这么认为。他觉得一个人受伤的时候她不是一整个过程都受伤,他们的爱情也有过甜蜜的时候,受伤的是一个时刻,诗歌反映的是那个时刻的心情。所以《氓》里虽然以女子的角度在吐露自己内心的伤感,但是如果以弃妇幽怨的视角去读这首诗,这首诗就狭窄了,如果把它当做是女子一生的倾诉,那就变成了一个美丽而哀婉的故事。蒋老师提到有朋友夫妻俩吵架了来找他诉苦,说对方怎么怎么不好,其实这都是很常见的情感。诉苦的过程中朋友还会清算自己为对方做了多少事情,像每天洗碗,扫地,做家务,指责对方什么都没做。其实是不是这样呢?《氓》里面写到女子每天勤勤勉勉做了很多事情,其实跟今天我们的想法是没有什么两样的。
以这样的方式来讲述《诗经》,是不是比我们刻苦地去背诵每一个艰涩的单字要容易得多了呢?我们说文学体裁是会变化的,词汇语法是会变化的,但是人的情感是永恒的。(至于农业社会的情感基础与现代社会的差别我们暂且不论。)当我们回过头去解读古代文学的时候,我们一定不能以纯粹理性的视角,去为每一个字做注解,我们要做的反而是以轻松的姿态,以审美的角度,去试图理解这些字句之间的美。只有当你理解这些文字的美了,才不会对“文学”二字有畏惧之心。
我们的恐惧来自于未知,但凡因未知而产生的烦忧,我们都可以先静下心来,好好理解清楚问题是什么,理解清楚了问题,我们总有对策。
2015.11.05 / 中午
于 T.i.T
过去八期“每周读书”介绍的书目以小说为主,这堆作品里面东野的《恶意》与马里奥的《教父》比较经典,《人间失格》则相对另类,《吴哥之美》让我第一次认识到蒋勋老师的文笔,不过吴哥真正的美却不在书里,还得亲自去一趟才能体会。
[每周读书 八十九] 东野圭吾——《虚无的十字架》
要求杀人凶手自我惩戒,根本就是虚无的十字架。然而,即使是这种虚无的十字架,也必须让凶手在监狱中背负着。
[每周读书 九十] 东野圭吾——《恶意》
《恶意》以其机巧的反转设计,特别的叙事结构,多元的人物塑造,以及时不时穿插的揶揄讽刺,深深地吸引了读者。从此作我们可以看出东野已跳脱本格派推理的“凶手是谁”的悬念,转而在凶手动机的追查上巧设机簧。
[每周读书 九十一] 伍绮诗——《无声告白》
前半部分很拖沓后半部分很精彩。虽然中国人看来可能就一普通的中国家庭的故事(当然除去严重事件)但美国人应该会为种族歧视、性别歧视等话题感到兴奋吧。还有普通的中国小孩是被寄予全家希望的这种事情。
[每周读书 九十二] Daniel Keyes——《24个比利》
1977 年,比利·米利根美国俄亥俄州立大学发生了三起抢劫强奸案,却被诊断为患有多重人格分裂症而获无罪释放。读完这本书,我感受到了深刻的震撼,不是因为作者的写作技巧与故事情节,而是这个真实的事件令人瞠目结舌。
[每周读书 九十三] Christina Baker Kline——《孤儿列车》
《孤儿列车》是和《无声告白》、《24个比利》一起看的,一口气读了这么多畅销书,深刻地感受到这些套路的定式,不过我还是很喜欢《孤儿列车》里讲故事的老婆婆薇薇安这个角色,同时这部作品的写作技巧也是三部之中最为娴熟的一部。
[每周读书 九十四] Mario Puzo——《教父》
小说中权势斗争的你来我往,人物内心的微妙变化,以及作家时不时冒出来的一两句警语,教父的语录都令读者兴奋不已。
[每周读书 九十五] 太宰治——《人间失格》
读这部小说,最令人震撼的不是主人公诡谲的视角抑或令人切齿的软弱,而是看似不可思议实则被我们熟视无睹的现实,其洞穿世事的目光有如利剑,深深扎进读者胸膛。
[每周读书 九十六] 蒋勋——《吴哥之美》
读《吴哥之美》,我们可以在曾几璀璨的文明与今日废墟的原始之间感受文化冲击的美,也可以跟随蒋老师的文笔去思考生命与时间的意义,去思考一个人的成、住、坏、空,去思考这个千百年前的古老文明在时间长河中的今时往日。感谢蒋老师,给我们带来如此美的体验
2022-08-20 原《每周读书》系列更名为《枫影夜读》
因为各种各样的原因,在国内并没有太多人了解吴哥。这几年随着吴哥旅游的日益发展,越来越多的游客涌入当地,去感受这座千年废墟的美。这是一件很值得高兴的事情,有更多的人去关注她,就意味着有更多的学者去研究她,修复她,人类历史上一个曾经耀眼的文明不至于沉埋在荒烟蔓草之里。
国内对于吴哥王朝的资料不多,其中来自台湾的艺术工作者蒋勋蒋老师的介绍应当是最有名的。他曾十四次造访吴哥,并把自己当时的书信集整理成书,是为《吴哥之美》。蒋老师还在台湾大爱电视台做了一系列介绍吴哥的节目,节目的名字也叫《吴哥之美》。
蒋老师曾经学过一点声乐,他讲话的声音非常好听,亲切而柔和。他讲过美术史,讲过文学,也讲过建筑,他把这些东西揉合在一起,为我们介绍了一个非常全面的吴哥。蒋老师身上还有一种非常难得的谦和,一个人的学识到了他这样的境界还保持着谦逊的态度是很难得的,他讲话的时候是在跟观众、跟读者、跟学生交谈,你完全感受不到来自“授课”的压力,他的谈吐仿佛一股暖风,款款拂入你的胸膛。
关于《吴哥之美》这本书,大家能够阅读的部分也不过纸上的字句,所以我们本周要介绍的重点其实在文字之外,在遥远的吴哥文明里。《吴哥之美》是根据蒋勋在吴哥旅行期间与台湾编舞家林怀民的书信整理而成,他们二位是伴侣的关系,书中没有明确点破这点,所以我开始看的时候还有点困惑。这些书信是写给爱人的,读起来非常温暖平静,蒋老师在书信中给当时在柬埔寨金边教小孩舞蹈的林怀民介绍了吴哥的历史文化,以及吴哥建筑的美丽所在,同时也谈到了个人的体悟与思考。
蒋老师对于许多文艺作品都有自己独到的理解,他在谈《诗经》、《楚辞》的时候就提出过自己的见解,深入人心,同时林怀民也是一位很厉害的艺术家,所以蒋老师可以用高雅的文字书写壮美的风景,镌刻废墟的宁静,舒展心中的感悟,畅谈深奥的哲理,这种精神层面的交流令人无比艳羡。
读《吴哥之美》,我们可以在曾几璀璨的文明与今日废墟的原始之间感受文化冲击的美,也可以跟随蒋老师的文笔去思考生命与时间的意义,去思考一个人的成、住、坏、空,去思考这个千百年前的古老文明在时间长河中的今时往日。感谢蒋老师,给我们带来如此美的体验。
然而有些事情不身临其境是无法体会的。
我们看照片只能看到冰山一角,体会不到吴哥强盛的王朝气度;
我们听故事只能听到只言片语,领悟不到吴哥虔诚的宗教信仰;
我们读文字只能读到井中天地,感受不到吴哥颓美的断壁残垣。
只有当我们真正地走进吴哥,在荷池前静坐等待黎明,在壁画前驻足直到斜阳,在塔顶上俯瞰一览天下,在石寺中静心感受微笑,我们会听到丛林中未名的鸟鸣,看到乡路间好奇的猴子,吹着从赤道来鼓鼓的热风,只有在这种时候,我们的五官全开,我们攀爬陡峻的吴哥寺的高塔,踩上窄窄的石阶战战兢兢,只有在这种时候,我们的心中充满敬畏,只有真正踏上吴哥的土地,我们才真正靠近这个失落了千年的文明。
所以在读《吴哥之美》以外,在看蒋勋老师的电视节目以外,我们还很有必要亲自去一趟柬埔寨,去走一回吴哥,去看看这座千年前金碧辉煌今日已巨木丛生的废墟,王朝有王朝的气度,废墟有废墟的颓美,即使是被世人遗忘多年的破落旧城,在今天看来依然有其雄伟,有其端庄。
如果要在吴哥窟众多建筑之中选择一座令人感受最深的一座,我想还应该是有“高棉的微笑”之称的巴扬寺。
到吴哥旅行的人一定会去的地方有两个,一个是最有名最具代表性的建筑吴哥寺,也称小吴哥,小吴哥荷花池前的日出亦幻亦真,令人迷醉,小吴哥的浮雕壁画体量巨大,令人惊叹,小吴哥的高塔陡峻异常,令人望而生畏。现在你只要在网上搜索吴哥,十有八九都是吴哥的日出。
但是因为她太有名,我反而不太想去谈她,当我徜徉在小吴哥的回廊中,当我凝望着五塔上冉冉的红日,当我一次又一次路过映照蓝天白云的护城河,小吴哥给我的感受是祥和。而拥有 54 座佛塔,216 个巨佛微笑的巴扬寺,却更容易穿越时空投影在游人的心底。
巴扬寺是阇耶跋摩七世修建的,他是一个挺特别的国王,他做的最大的一件事情就是让吴哥从以前的印度教改信了大乘佛教。所以在他之前吴哥的建筑都是印度教建筑,在他之后才是佛教建筑,也是他增修了吴哥城几个城楼上的四面佛像。同时在阇耶跋摩七世在世期间,吴哥王朝的国势也达到了巅峰,往后吴哥就开始衰落,最终灭于暹罗人(泰国人)的入侵,被迫迁徙到今日柬埔寨的首都金边。吴哥城也由此沉埋了几百年直到后来被法国人发现。
站在巴扬寺,面对一座座巨佛的神秘的微笑,那种感觉是很特别的。你知道在几百年前这座石塔被称为“金塔”(记于元朝使者周达观的《真腊风土记》,真腊即吴哥,是现存最完整的吴哥王朝的资料),它的墙壁很可能是铺上了一层金箔的,金碧辉煌,构成佛像的巨石也都是严丝合缝的。但是今天金箔早已不在,石头也都裂开了。完璧有完璧的美,废墟有废墟的美。这些歪斜的石头反而给了微笑各不相同的角度,这些裸露的岩石反而给了佛像原始的质感。
我觉得巴扬寺最美的地方在于,他大胆地采用了佛像的脸这样的形式,来表达佛教的思想。我们说金碧辉煌会褪去,石塔也会倒塌,但是佛像的微笑留下的神秘的感觉,似蒙娜丽莎,它所传达给人类的情感体验是超越时间的。几百年前她以金身俯瞰众生,几百年后她以石面笑对世人。存在只是一种形式,高棉的微笑只是走过了一个轮回,以不同的形式面对同样的世界。
吴哥有太多太多值得书写的东西,它的曾经强盛的历史,它的美的高度,它的今昔分明的冲击,无不萦绕在耳畔久久呢喃。但是这些故事也好,这些品评也好,都不如亲自走一趟吴哥,亲自去看一眼荷池前亦幻亦真的日出,亲自去仰望佛塔上祥和的微笑,我觉得这些亲自的体验比起它人絮絮叨叨的描述要深刻得多。
最后援引蒋老师的话,结束这一期感叹比书写要多的每周读书。
美无法掠夺,美无法霸占,美只是愈来愈淡的夕阳余光里一片历史的废墟。帝国和我们自己,有一天都一样要成为废墟,吴哥使每一个人走到废墟的现场,看到了存在的荒谬。
15.10.28 / 夜
于自宅