0%

最近时常觉得,自己变得越来越无法自发地产生某种情绪了

第一次发现这件事情,是在前不久和朋友开玩笑:“你看大学那会儿的我,一天到晚屁话多的要死,恨不得把所有碎碎念都丢到朋友圈里面去……”方才察觉,原来已经这么久没有在朋友圈发过什么正儿八经的心里话了;同样不知道从什么时候开始,博客和小作文也淡出了生活,并非不想写,而是已经找不到什么值得写的东西了——愤怒、悲伤、惆怅,这些文章的养料已经很久没有出现在我贫瘠的心田了

“那你一定最近生活过的很滋润吧。”

也是谈不上的,毕竟生活还是那个生活,没好到能摆脱自己这个小小的房间,也没坏到去大街上流浪

曾经走在路上我会胡思乱想各种各样的问题,小到自己的社交圈、中午吃什么,大到社会矛盾、宗教文学,会因为别人插队感到不快,也会因为短暂晒到户后的阳光而偷偷乐呵。这种最纯粹的感受,现在已然不复存在了

“你在悲伤什么呢?为什么我感受不到如同你那般怆绝的悲伤?”

“你在愤怒什么呢?此时我应该用同等的愤怒来回应你湍流般的不满吗?”

于是对话陷入沉默

因为不知道应该用什么态度、什么感情来回应他人

于是我开始模仿

用同样的情绪填补自己空洞的内心

我依然能从音乐中感受情绪的宣泄,从小说的字里行间共情角色的境遇——我只是单纯的感受不到自己的情绪了,就像在冰天雪地中失温的旅人一样感受不到自己的体温一样。我仰卧着望向星河,感受身体被情绪的大海托举,自己却无法融入其中,于是轻轻拍打着水面,溅起几朵浪花试图证明自己的存在;我偶尔在睡前忧心忡忡,惧怕自己会在朝着空心人蜕变的道路上深陷而不自觉,转念一想——

或许就连这样的恐惧也是伪装出来的

“哎你的那个高中同学,现在这个形势还要出国留学啊?留在国内多好……”

“读研留学,最终为的是什么?不都为了找个好工作嘛……”

“这是没有「意义」的事情……”

没有意义就是最大的意义

当周围的人质疑你的行动,嘲讽的你的规划,不解你的思考

你终于明白肩上负担的是独属于自己的行囊,脑海盘旋着的是世间无二的音符

他们知道这没有意义,你知道这没有意义,所以这就是意义

人们为世界上的一切事物、人类的一切言行冠以「意义」,因为那是面临虚无的唯一解药

于是「意义」出现在了各种场合,被人从背后推着,不分青红皂白地为台上的丑角正名

“我的生命是多么的出彩,我的人生是多么的令人眼红,和随处可见的杂草完全不同!”

人类不在乎,银河不在乎,宇宙不在乎

这个世界并没有一本厚厚的日记本来记录下每个人一生的轨迹

等他死了钉上棺材板,便开始向所有人歌颂他的丰功伟绩直到宇宙寂灭

你说

“我能让所有人长生不老”

他说

“我能让全人类衣食无忧”

多么伟大,然后呢?

在那之后出生的人们

无非是将这翻天覆地之后的世界

视作上帝理所应当的造物

八九十年代的上海有一种说不清道不明的魅力,改革开放的风刚吹起不久,在这片临海的土地上与旧时代的海派文化一拍即合,复古和新潮这两个截然相反的名字毅然不计前嫌地将根茎缠绕、扎根,汲取着黄浦江的养分开始野蛮生长。水一灌,褪去隐忍的种子发了芽;风一吹,野草拨开泥土发疯似的向无穷远处的天空攀登。

当然,我的父亲并没有加入这片野草的队伍。在89年前后,出于某些原因,刚拿到了高中毕业证的他放弃了求学的道路,转而去日本另谋机会。时至今日当他追忆往昔,这个决策总会以其他参赛选手望尘莫及的水平,在他的“错误选择排行榜”高居榜首,其程度之深,甚至可以将日后的大部分失败挫折归咎于此;尽管后悔,不可否认的是那时的他尚且被幸运女神眷顾,乘上了日本经济增长的最后一个高峰,挣了一笔不小的钱,其数量之多,甚至足以为他日后的大部分失败挫折兜底。

相比起父亲,母亲更符合当时人们对于一位青年的评价标准:老实本份地念完义务教育,进入一家规模不小的国企工作,每天朝九晚五工作稳定。尽管挣得不算多,但所幸工作与她的爱好一致,舞蹈出身的她甚至有时间在下班之后私底下接一些串场演出的活,用现在的话来说就是把自己养的很好。

90年代初,刚回国的父亲遇到了母亲,带着自由回归的人遇到了生性向往自由的人,爱情的火花一触即发。他们曾经是坚定的丁克族,在八九十年代的中国,的确算是个十分新潮的思想。虽然不知道他们有没有和家里人谈过这个问题,但既然今天你能够看到我的这段絮叨的文字,至少说明丁克族前面的修饰词需要加一个双引号。此外,时至今日我能够从他们回忆往昔的只言片语中隐约摸索出来:这两个人的丁克思想其实很大程度建立在年轻贪玩的基础上,一个刚刚出生的婴儿无疑是这个基础最有力的破坏者。

他们结婚后不久,母亲从工作单位分到了一套房,就在周家嘴路通北路口的宝钢又一村,于是这里就成了我童年时期不可或缺的一方小天地。推开曾经那处住所的房门,最为吸引人的莫过于采光极好的客厅、柔软的沙发以及略显老气的酒柜,午后的阳光偶尔会从朝南开的窗户闯入,映出些许空中的浮尘。连同牵着金龟子的绳子、打在木质地板上的玩具的影子、被茶几磕破的膝盖以及自己的抽泣声一道投影在记忆的剧场里。不过在我印象中最俱存在感的莫过于左手边略显逼仄的衣帽间,我在以前写过一篇名为《自我剖析报告》的小作文里面提到过,进入小学之前是外公外婆照顾的我,呆在市光二村的时间远比呆在自己家的时间长久;再加上我很害怕当时的父亲,一套“怕屋及乌”的组合拳下来,难免对老房子本身也蒙上了一层灰色的滤镜。每当我犯下什么错,这个衣帽间就成了关押我反省的囹圄,又几乎每一次我哭个精疲力尽之后,都会在四周衣服味道的裹挟下昏昏睡去。对于幼年的我来说,这个小小的借不到光的空间是悲伤的牢笼,也是黑暗中给予我抚慰的摇篮,即使在稍微大一点之后,父母不再用关小房间的方式来惩罚我的淘气,我也会不时抱着自己的玩具躲进去玩上一下午。

我不懂车,对车也没有太大的兴趣——对内燃机的了解仅仅停留在初高中的物理知识;很多耳熟能详的牌子我都分辨不清;即使大三勉强考出了驾照,至今也没有上路开过。人有路痴、音痴,这样的说法借鉴到我身上,或许可以称呼我为车痴。这样的车痴却对桑塔纳情有独钟,只是因为曾几何时,家里也有过一台黑白相间的桑塔纳。印象里这辆车总是被随意得停在路边,一副唾手可得的样子(可能宝钢又一村压根儿就没有地下停车场),一家三口出门的时候我总是跟着母亲下楼,走过小区的一个拐角就能够看到那辆桑塔纳,车里坐着先一步出门暖车的父亲。在他们的罗曼史和回忆录中,这辆车的出场率总是高得离谱:今天嘴巴馋了,遂驱车到阳澄湖买两个大闸蟹带回来吃;明天客人要回老家了,索性开着车送他一程,顺便还能路上吃吃喝喝兜兜风……诸如此类。再后来,不知是因为这辆车该寿终正寝了,也不知是家里经济的缘故,黑白桑塔纳从我的世界里消失了,尽管我对此的记忆已经模糊,尽管我对桑塔纳的喜爱远超其他的燃油车。于是我无数次路过小区的那个拐角,再也没见到安静地等待着一家三口的忠实仆人,发动机的轰鸣声从我尚未出生的时光里驶向我的身边,又带着同样的轰鸣声朝着不知去向的无穷远处奔袭。

小学四年级的时候,我们卖掉了这间房。那天晚上我躺在床上听着父母在客厅里的窃窃私语,我不知道发生了什么,只是隐约认为他们聊的并不是什么值得高兴的事情。朦胧之中母亲悄悄推开门来到床边,我感受着额头被轻抚的温柔,耳边传来略带哭腔的声音:“要是我们从这个家搬走了,你会怎么想?”人在脑子乱作一团的时候是没有办法思考这种问题的,在大脑控制嘴唇做出反应之前,泪水已经抢先一涌而出,代替言语给出了回答。房间很安静,我和母亲都明白对方想说什么,却只是无语凝噎。那天晚上的很多事情我都已经记不清了,在一个轻柔的吻和眼角悲伤的湿润中,我睡的很沉很沉,就好像是为了忘掉那个夜晚发生的一切。再过没多久,我们从这个家搬走了。

时至今日,每当我听到周家嘴路这四个字、看到桑塔纳方方正正的外壳,都会心里一抽,思绪不受控制地被拉回无数个曾经阳光明媚的下午;那段修了快一百年都没竣工的路段,飞扬的尘土中似乎藏着父亲书房里压抑而安心的木头香味;在周家嘴路通北路那个大的要死的十字路口等红灯,身边总好像会路过一个考试成绩不理想的小孩,手上捏着只能玩贪吃蛇的诺基亚,背对夕阳步履蹒跚,害怕即将到家面对的狂风暴雨。如果要现在的我来选择一套房子,我一定不会选宝钢又一村——这里最近的地铁站也要走上十五分钟,小区里有个幼儿园每天都能听到楼下传来小孩子尖锐的喊叫,附近没有商场也没有好吃的苍蝇馆子,美食荒漠的程度堪称杨浦小杭州——就像现在的我买车完全不会选择大众桑塔纳一样,只是一味地怀念。

记忆是台老式放映机,胶片上布满划痕却仍固执地转动。当我在高架桥的轰鸣声里辨认出周家嘴路的路牌时,恍惚看见1998年的桑塔纳正碾过满地梧桐碎影。后视镜里倒退的宝钢又一村逐渐失焦,像被泪水浸泡过的显影液里浮起的底片——幼儿园的尖叫成了默片,父亲书房的木香凝结成胶卷齿孔间的灰尘,衣帽间房门铰链开合的钝响,原来与枯叶碎裂是同一种频率的心跳。此刻导航提示“前方施工路段”,二十年前未竣工的马路突然穿透时间柏油,将我的车轮卡在童年与成年的裂缝之间。仪表盘上闪烁的充电标志突然幻化成黑白桑塔纳的转速表,后座传来孩童的抽泣与诺基亚的蜂鸣,后视镜里没有车,只有千禧年的夕阳固执的追赶。

大概每个人小时候都会被长辈们问到的一个问题:“爸爸和妈妈你更喜欢谁”,相对应的回答也大多是犹豫再三之后丢出的一句“都喜欢”。有些长辈只是为了逗逗小孩,见目的达成,也就打个哈哈过去了,可偶尔也有长辈觉得这种程度的调侃不够过瘾,于是假装打破砂锅问到底地非要讨到个结果

于是我开始意识到,这世界上有太多的选择题值得纠结,这选择题并非如同考试的单选一般非对即错;亦不像多选题一般可以照单全收。可谁又能说这样的选择题一定是让人避之不及的灾难呢?只不过是因为贪婪的本性让人想要两头兼顾,而现实却又告诉自己绝无这样的希望,只能眼睁睁看着名为“失望”的达摩克利斯之剑一点点落向自己的脖颈

所以我不喜欢做选择题,面对难以两全齐美的抉择,我感到惶恐

令我惶恐的是站在十字路口必须决定接下来前行的方向,而我并没有做好准备

令我惶恐的是作出了决定之后,我必须接受另一种可能性必然的消失

令我惶恐的是我不得不将属于自己的部分一个个拆分开来,置于天平的两端来比较利弊——这边加一点“兴趣”和“社交”,那边加一点“工资”和“前途”

明明这些本就都属于我生命的一部分,为何此时不得不因为我的选择而犹如碎石一般,从我身上剥离?

我一直以来都这么认为,在人每一次做出选择的时候,无非是舍弃了某些东西,来获取另一些东西,认为世界运转的逻辑建立在等价交换的基础上,于是对此感到安心

殊不知世界并非只正襟危坐于帘幕之后观察世间一切交易的观众,而是一位善于伪装且精打细算的掮客,于无尽的选择与交易之中,祂不断偷偷收取双方的一些东西作为手续费——譬如时间、精力、热爱之类的东西,可能那些过于廉价的东西也入不了祂的眼——直到一个生命完成了谢幕为止。祂的存在感是如此的淡薄,以至于我时至今日才在胡思乱想之中找到了让我郁郁寡欢的罪魁祸首

在高中的时候,我对博尔赫斯的作品赞不绝口,对他写下的《小径分叉的花园》印象尤为深刻,可如今我发现,博尔赫斯所设想的宇宙——那个小径分叉永不停歇的花园——从科幻的角度或许有迹可循,可站在一个渺小的人类的视角来看,实在过于慷慨。人类的生命说到底是有限的,不管是否做出选择,时间的流逝总是推动着自己滑向死亡的终点;与此同时社会的结构和个人的命运又进一步限制了一个人先天的条件和后天有所作为的方向。站在个人的视角来看,宇宙、世界、耶和华、佛祖、上天……这些存在实在过于庞大而无法接近,即便祂们真的许诺过给予人类永恒,落在个人的肩上,也一定是重如泰山的引力;祂们许诺的天堂也好来时也好,或许也不过是用来榨取个体的话术

我为自己花了21年光阴才切身理解这个浅显道理的后知后觉感到惭愧,又对无法从这条绝对正确的公理之中抽身感到绝望

所以人活一世,本质上是在做减法,绝非加法:我无时无刻不在杀死自己,杀死无数个时空的自己,只为让当下这个自己苟且偷生。在我打下这段话的时候,我在杀死正在旅行的自己,我在杀死演奏音乐的自己,我在杀死正在写一本名为《余烬》的小说的自己,我在杀死骑在马背上肆意的自己,我在杀死21岁踌躇满志的自己,空余一个充满了嫉妒和失望和迷茫和扭曲的希望的躯壳留在世上;而到了下一秒,这个苟活于世的自己又开始做着同样的恶事

这么来看,当抉择的时刻临近,我能留给自己的只剩下了叹息

我们这一代人,可能包括我们下一代人,缺乏想象力——你知道吗?很多还是学生的孩子都自杀了,我认为他们无法想象出一个充满乐趣的未来——说的直白一点,他们父母的样子应该为其未来提供动力,但他们的父母看起来并没有在享受乐趣。从根本上说,孩子们无法想象出成为大人的乐趣,与孩子沟通的大人通常是他们的父母和老师,或许在观察他们的时候,孩子们看不到未来会变得快乐的希望。我认为那并不是父母的过失,而是想象力的匮乏。

这是1997年几原邦彦接受关于自己的作品《少女革命》的采访时说的一段话,巧的是今天早上上班途中我偶然看见了,巧的是今天mentor找我谈了谈关于日后工作的事,巧的是今天下班之后我的某位朋友给我分享了他那令我咋舌的加班情况,巧的是回到家与父母讨论关于自己日后的安排后发现自己与他们意见的分歧

于是不禁回想起了上午瞥过的这段话。尽管这只是几原邦彦从少女革命这部作品本身所衍生出来的一段感想,可结合今天的种种经历,实在让我没法对此产生一笑了之的想法

“那么你怎么考虑自己将来的发展呢?”

“不想去大厂上班,因为我讨厌加班和高压的氛围,一天八小时的工作已经是自己的极限。”

“那就待在现在的公司上班吧,至少不容易被裁。”

“我又觉得工资低,没有办法满足我想要的生活。”(事实上我连自己想要的生活究竟是什么样子都并没有一个明确的答案,只知道钱这种东西永远不嫌多)

“那么创业呢?”

“如果人的创造力果真是一种能够量化的存在的话,或许我在这方面的数值已经归零。”

“那就在现在这个公司继续呆着吧,好歹骑驴找马,你说是也不是。”

“嗯……大概是这个道理吧。”

于是我找到了答案,但这不是决策,而是妥协,是无奈之举,中庸的产物。当我思考自己日后会成为什么样的人,要做什么样的事,自己其实根本无从下手,依然没办法讲清楚如果自己继续这样每天像机械一样地上下班究竟能得到什么、是否成为了小时候幻想中长大后的自己,只不过劝说自己放下心里对于远低于互联网大厂的工资的芥蒂,用安逸和稳定聊以自慰,强迫自己接受打工一辈子也大概率碌碌无为地过完这一生的事实

我问朋友明明只是实习,明明五点半六点钟就能走,没有人强迫你加班,加了班也得不到其他好处,所谓的锻炼自己只不过是一个幌子,本质上只不过是根赛博螺丝钉,为什么还要加班呢?

他匆匆给我发了几句话,简单来说就是deadline的压迫辅以同事们的等待,最终调制出了名为愧疚的苦涩药剂让他不得不一口服下,并且在下班之后给我发了两段各自长达50秒含妈量极高的语音诉说自己加班两三个小时的愤怒——然后该上班接着上班,第二天该加班接着加班

多么无趣呀,你也好我也罢,似乎命中注定被世界束缚而无法脱身,又去哪里能够找到梦幻一般的未来呢?可我想无趣的不是世界而是人类本身。因为懒惰而选择平庸,因为愧疚而折磨自己,因为利益而欺骗他人,因为软弱而沉默不语,因为自卑而否定一切,因为傲慢而言辞无知。无论站在什么样的起点,有过什么样的经历,这世界上的数亿人类最终殊途同归成为了大致相同的货色,连带着从井底看到的整个世界都失去了色彩,可这并不是你我的过错——钢铁的森林用你我的血肉筑起坚不可摧的身体,用你我的精力当作木材点燃永不熄灭的炉火,用规训将每个不听指挥个体打成异端,用霓虹广告吸引着未经世事的新芽

多么无趣呀,不管是这看不到尽头的循环,还是如灰烬一般的你我

其实这段话早在高中就应该已经有所头绪了,但出乎意料的是这样的想法被最近几年的安逸掩盖过去,直到最近两天才重新浮于心头。

事情的起因是一场不足为道的家庭矛盾,主角是我和父亲,导火索是我的一句语气中带着不耐烦的回应,结局是劈头盖脸的沉重的教训。尽管我一再强调认识到了自己的错误并且进行了反思,也无法平息他的怒气。

当然,我并非因此而怒不可遏,以至于恨不得用小作文来记录这些重量级的言语,我知道造成这场矛盾、这个局面的根本原因在于我自己;而促使我动手写下这段话的直接理由是:人类无论多么理性、多么有智慧,无论平时多么善于思考、张弛有度,其心理防线的崩塌也只不过是一瞬间的事情——于是愤怒接管大脑,无差别地对现实进行自我意识的扭曲篡改,以此作为武器来攻击对方。而对于东亚的家长来说,最不可忍耐的、罪不可赦的莫过于家庭中比自己地位低的人对自己的忤逆(我自认为我们家算是典型的东亚强权家庭,所以这里仅从「我自己的家庭」出发作分析,范围也只限定东亚家长,但我相信即便这个范围拓宽到全人类也不为过)

好了,写了这么多话,我到底想表达什么观点呢?典型东亚家庭建立的感情基础是倾轧——即,强权者从弱权者甚至无权者的服从获取快感,相对应的,如果强权者无法获得快感,或者其威信受到了挑战,那么大战便一触即发。这里的强权、弱权和无权的区分指的是经济控制权,而非话语权,因为「一般」来说,谁掌控了经济,谁就掌握了话语权。此外这里我本来想用“剥削”,因为这种现象的确是一种人类情感上的剥削,但这会造成歧义,所以我会用倾轧来指代这种现象(其实“倾轧”也有歧义,但是能看懂就行)

为什么我说这是典型东亚家庭建立的基础。首先是家庭中强权者的存在,这几乎是必然的,无论在生活中是否有表现出来、是否有刻意塑造自己的权威形象,在组建家庭的初期,家庭的经济重心一定会往某个人身上靠,这个人可以是父母双方的任何一人,而身为重心的这个人就会顺理成章成为强权者。这个角色的分配不是一蹴而就的,而是随着率先对对方展开倾轧的一方发动攻势,日积月累导致的。而后随着孩子——这个天生的无权者的诞生,强权、弱权的格局就会敲定下来。除非强权者做了极大的错事例如出轨、犯罪等越界行为,否则不会有什么变化。回到这一段的开头,为什么倾轧是典型东亚家庭建立的基础,因为你会发现,倾轧的出现是被动的,因为财富天平的倾斜是必然的,即使是前面提到的所谓“率先对对方展开倾轧的一方发动攻势”,在家庭初期也可能只是其下意识的举动——举个简单的例子。

我现在和路人甲谈了恋爱,双方都需要情感的往来,甜甜蜜蜜恩恩爱爱,两个人相互从对方对自己的依赖中获得快感;此时由于金钱和社会地位还没有彻底成为两人关系的重要一环,两个人的身份地位几乎是平等的,没有到失去对方意味着极大变故的程度,所以还没有到“倾轧”的地步(特殊关系这里我们暂不讨论)。

后来我们结了婚组建了家庭,此时我们就需要考虑:现在咱俩结婚了,有夫妻共同财产了,那么谁来管钱?假设我的收入比路人甲要略高,那么为了平衡家庭地位,我们可能会选择让路人甲来管钱。此时,家庭经济的掌控权看似在路人甲身上,实则我们相互是独立的,即便没有对方,至少自己还有收入,倾轧的效果也就不明显。

后来有了孩子,由于我们无法兼顾工作和家庭,于是路人甲放弃了自己的工作成为了全职主妇。这个时候倾轧就显现出来了,在这个阶段我实质上掌握了经济的绝对主动权,虽然挣到的钱全部上交给路人甲,但是没有了我,这个家庭就会面临崩溃的威胁。

现在我们换一个世界线,在这个世界线中路人甲没有放弃工作,我们暂时把小孩交给爷爷奶奶或者外公外婆照顾。现在我们双方都有收入,但是钱还是交给路人甲来管,那么时间长了,路人甲掌管的钱越来越多,手上会掌握家里经济的绝对控制权,我为了不让家庭崩溃,只能在情感上听从路人甲,同样会出现倾轧。

现在我们再换一个世界线,在这个世界线中路人甲没有放弃工作,我们暂时把小孩交给爷爷奶奶或者外公外婆照顾,但是钱我们两个人自己管好自己的,这样似乎倾轧的可能性变得很小,但与此同时,我们双方都过着各自的生活,仿佛有一堵看不见的墙壁阻拦了我们二人,我们各自的收入足够养活自己,不必为了钱而麻烦对方;我们不需要为了生活而迁就对方,大不了我用自己的钱过自己想过的日子——那么婚姻也名存实亡。

如何?尽管这几个例子有些过于简单和理想化,但足够说明为什么我认为倾轧的道理对于人类社会大部分都是成立的。但是在典型东亚的家庭环境下,一个三口之家的父亲/母亲会在某一个时间点选择放弃工作,担任全职主夫/主妇,几乎断掉了自身的经济来源,只能用「情感」来制衡对方,也就给了强权者在情感上剥削弱权者的绝好机会,那么这样来看,倾轧几乎从一个家庭的诞生之初就存在,其存在感也必然会在某一个时间点达到顶峰,所以说它是一个家庭的基础也的确不为过。

强权者享受着家庭中绝对的话语权,当弱权者和无权者向其观点发起了挑战,即便明知双方意见达成了一致没有争吵的必要,甚至自己意识到自己是理亏的一方,强权者也不会主动示弱——因为一旦示弱,那么在家庭中自己的地位就会受到实质上的威胁,而对于弱权者和无权者来说,这样的威胁能有一次就能有无数次,久而久之,强权者的地位破产,手头上仅剩经济控制权作为威胁来让弱权者和无权者服软:

“我是你爹/妈,你竟然敢顶我的嘴?!”

“日子过不过了,不过就离婚!”

“我挣钱多不容易,你怎么还好意思和我搞七搞八?!”

身为人类,没有野兽般强健的身躯,于是诞生了氏族,用血缘将我们捆绑在一起

身为人类,无法像三体人那样直截了当获取对方内心的想法,于是阴谋算计让我们不得不对外设防,氏族的范围逐渐缩小,最终出现了现在的家庭

可尽管血脉相连,剥削却依然存在,压迫从未消失,只不过对象从国家财富到生产资料和剩余价值,最终变成了感情和教育。家庭这个社会的最小单位中没有生产资料的纠葛,但倾轧本身未必会比剥削好上多少

如果倾轧有存在的必要,那么在社会的结构发生极大变动之前,我们似乎注定需要忍受这样的压迫

如果倾轧没有存在的必要,那么家庭是否也就没有了存在的必要?随着性别对立、阶级矛盾的日益加剧,婚姻家庭在将来可能会慢慢淡出人们的视野,这个问题的答案也似乎已经有迹可循——当生产力足够发达,社会化抚养或许会打破当下普世的伦理观念,成为新的社会模式

可当我们没有了家庭,成了一座座孤岛,人类又能够依靠什么来和他人建立长久的联系,由此获得精神上的寄托?或许我们会愈发需要诗词歌赋音乐艺术这样的兴趣爱好来充实自己的内心,可仅靠这些外在的动力,真的足以让一个个体产生“自己已经在这个世界立足”的实感吗?这样一来,人类是否又要再一次将内心世界献给那些模糊不清的概念呢?

我好像能看到,在那不知远近的未来,宗教带着它的信徒卷土重来…

大三最后一个学期的考试前一周
中午排了三刻钟的队才带回寝室的麦当劳
花上半个晚上的时间和室友一起玩的大乱斗
下楼倒垃圾时抬头从树叶间隙窥见皎洁的月
赶路时打趣的只言片语

从自己开始找实习、他们开始准备考研以来,已经很久没有度过这样无忧无虑纯粹享受快乐的夜晚了
没有作业,没有实验,没有考研,没有实习,仿佛单纯地从分针与秒针之间无限逼近的距离中扣出一小块,然后无限地拉长,又在下一个瞬间回归了正轨

常言道人生苦短,光影似箭
可往往只有如此这般肆意挥霍时间的片刻才能够成为支撑起一段人生经历的快照
如今我马上就不住在宿舍,这般玩乐的机会在将来也只会越来越少,一想到这里,纵使平时有再多的不满,此刻也都显得无关紧要
人们终究会如同行的旅伴一样分道扬镳,踏上各自的前程,也总要学会和过去一个阶段的自己做个告别,但往往时间如暗流般淌过,而自己却无法察觉;当不得不面对现实时,才发现为时已晚
我不知道将来的自己会成为什么样的人、结交什么样的人,在生活的重压下能否抽出如今夜般的片刻来和某人一道用欢笑声短暂地驱散愁绪
我只知道当下及过去这样仿佛永恒的热闹的日子,的确是马上就要结束了

一个人急眼的时候,大多是被别人点出了自己不想承认的事实

如果说有什么是比「被大厂拒绝实习」更让人感到痛苦的,那莫过于「被小厂拒绝实习」了,前者姑且还能够用“世上大佬云集,我只是没有名列头部”这样的理由舔舐自己的伤口,后者几乎完全将自己虚伪的表皮剥了个一干二净。虽说「实际工作所需要用到的工具和知识,与学校传授的内容堪称云泥之别」这个事实我早就已经心安理得地接受了,但是最近接连不断受挫的笔试和面试让我不得不抱着失望的目光审视自己过去这两年半的学习经历

说实话,我其实打心底觉得自己是没什么学习计算机的天赋的,我很难像认识的一些大佬一样接连几个小时地coding并且乐此不疲,也很难想方设法地去创造有闪光点的idea然后用代码去实现——我并不是一个富有创造力的人,也没有坚持迎难而上的主观能动性。计算机是一个拥有「明确且理性的答案」的学科,因为机器总是正确的,为对不可知的事物抱有强烈恐惧的我很喜欢这一点,但也正因如此,我喜欢尽可能地使用已经存在于世界上的方式去理解、解决问题,而非另辟蹊径将自己的头脑风暴发挥到极致——后者对于coding来说往往更加重要,这实在是一件令人失望的事实

大一结束的时候我决定转到计科,一方面是对机械所涉及到的力学和材料实在提不起兴趣,另一方面是抱着对计算机的憧憬。说是为了「自身钱途」考虑也好,说是为了圆高中那个胎死腹中的coding梦也好,我义无反顾地冲进这片看不到边际的汪洋大海,以为自己能够闯出一片自己的小天地
然而事实是残酷的:我自认为在过去的两年半中并没有荒废自己的学业,始终尝试着学习新的技术,可当我立足于当下去回顾这段时间的经历,对自己的评价并没有比漫无目的地原地绕圈的无头苍蝇好多少,这就是另一件令人失望的事实——没有规划和明确目标的学习本质上是无用功,归根结底,与荒废两年半的时间殊途同归,在最终的效果上是一致的。也直到最近开始海投实习,我才对自己究竟应该做些什么、学些什么有一个大致的方向,但可惜已经太晚了:我那被分流拖垮的大一时光和身处迷茫四处撞墙的大二大三导致了如今一事无成的情景

ICPC的遗憾打铁,ASC的没有后文,以及寻找实习的失败,我想这大多都可以归咎于自己装模作样的假努力,不长也不短的coding生涯始终笼罩在失败的阴影下让我开始怀疑自己究竟是不是真的不太适合学计算机

可如果让我再回到大一做一次选择,我还是会头也不回地选择这个专业,我依然会回忆起初中那个被计算机和代码的力量震撼到的那个下午,和高中抱着向往却没有入坑的遗憾

可我没能够做到用未来的时光和努力来弥补过去的遗憾,如果前方终究只是一条用梦泡点缀着的看不到光亮道路,我究竟该如何说服自己在这条路上继续走下去呢……

GMP的原理与调度

单进程

在早期的操作系统中,一个程序就是一个进程,只有当一个进程运行完,下一个进程才能够启动,一切都是串行的。这样的系统面临两个问题:

  1. 执行流程单一
  2. 进程阻塞会导致CPU时间的浪费

因此,后来OS加入了多进程并发:当一个进程阻塞,就会切换到其他就绪的进程,以避免浪费CPU时间

线程和协程

多线程就是指一个进程中同时有多个线程正在执行

线程是一个基本的cpu执行单元,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,可以与同属于一个进程的其他线程共享进程所拥有的全部资源。引入线程之后,进程只作为除cpu以外系统资源的分配单元,线程则作为处理机的分配单元

而一个线程的实现又可以被划分为用户线程内核线程,一个 “用户态线程” 必须要绑定一个 “内核态线程”,但是 CPU 并不知道有 “用户态线程” 的存在,它只知道它运行的是一个 “内核态线程”(Linux 的 PCB 进程控制块)。因此,我们只需再做个分类,将内核态线程依然称为“线程”,而用户态线程就被称为“协程”

这也就涉及到了线程与协程之间的绑定关系:

  • N:1 关系

    N 个协程绑定 1 个线程,优点就是协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速。但也有很大的缺点,1 个进程的所有协程都绑定在 1 个线程上

    缺点:

    • 某个程序用不了硬件的多核加速能力
    • 一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,根本就没有并发的能力了
  • 1:1 关系

    1 个协程绑定 1 个线程,这种最容易实现。协程的调度都由 CPU 完成了,不存在 N:1 缺点,

    缺点:

    • 协程的创建、删除和切换的代价都由 CPU 完成,有点略显昂贵了
  • M:N 关系

    M 个协程绑定 1 个线程,是 N:1 和 1:1 类型的结合,克服了以上 2 种模型的缺点,但实现起来最为复杂

协程跟线程是有区别的,线程由 CPU 调度是抢占式的,协程由用户态调度是协作式的,一个协程让出 CPU 后,才执行下一个协程

goroutine

Go 为了提供更容易使用的并发方法,使用了 goroutine 和 channel。goroutine 来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被 runtime 调度,转移到其他可运行的线程上。最关键的是,程序员看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发

Go 中,协程被称为 goroutine,它非常轻量,一个 goroutine 只占几 KB,并且这几 KB 就足够 goroutine 运行完,这就能在有限的内存空间内支持大量 goroutine,支持了更多的并发。虽然一个 goroutine 的栈只占几 KB,但实际是可伸缩的,如果需要更多内容,runtime 会自动为 goroutine 分配

Goroutine 特点:

  • 占用内存更小(几 kb)
  • 调度更灵活 (runtime 调度)

GMP模型

事实上goroutine的调度器因为开销、锁竞争、局部性等原因,曾经一度被废除过,而重新设计过后的就是现在的GMP模型

在新调度器中,出列 M (thread) 和 G (goroutine),又引进了 P (processor处理器)

在 Go 中,线程是运行 goroutine 的实体,调度器的功能是把可运行的 goroutine 分配到工作线程上,其中涉及到几个部分:

  • 全局队列(Global Queue):存放等待运行的 G
  • P 的本地队列:同全局队列类似,存放的也是等待运行的 G,存的数量有限,不超过 256 个。新建 G’时,G’优先加入到 P 的本地队列,如果队列满了,则会把本地队列中一半的 G 移动到全局队列
  • P 列表:所有的 P 都在程序启动时创建,并保存在数组中,最多有 GOMAXPROCS(可配置) 个
  • M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去

Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行

关于M和P的数量

P 的数量:

  • 由启动时环境变量 $GOMAXPROCS 或者是由 runtime 的方法 GOMAXPROCS() 决定。这意味着在程序执行的任意时刻都只有 $​GOMAXPROCS 个 goroutine 在同时运行

2、M 的数量:

  • go 语言本身的限制:go 程序启动时,会设置 M 的最大数量,默认 10000. 但是内核很难支持这么多的线程数,所以这个限制可以忽略
  • runtime/debug 中的 SetMaxThreads 函数,设置 M 的最大数量
  • 一个 M 阻塞了,会创建新的 M

M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来

关于M和P的创建时机

P 何时创建:在确定了 P 的最大数量 n 后,运行时系统会根据这个数量创建 n 个 P

2、M 何时创建:没有足够的 M 来关联 P 并运行其中的可运行的 G。比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M

调度器设计策略

为了减少线程的频繁创建和销毁,对于闲置的线程,GMP的策略是对其复用

  1. work stealing机制:

    当本线程无可运行的 G 时,尝试从其他线程绑定的 P 偷取 G,而不是销毁线程

  2. hand off机制:

    当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行

利用并行:GOMAXPROCS 设置 P 的数量,最多有 GOMAXPROCS 个线程分布在多个 CPU 上同时运行。GOMAXPROCS 也限制了并发的程度,比如 GOMAXPROCS = 核数/2,则最多利用了一半的 CPU 核进行并行

抢占:在 coroutine 中要等待一个协程主动让出 CPU 才执行下一个协程,在 Go 中,一个 goroutine 最多占用 CPU 10ms,防止其他 goroutine 被饿死,这就是 goroutine 不同于 coroutine 的一个地方

全局 G 队列:在新的调度器中依然有全局 G 队列,但功能已经被弱化了,当 M 执行 work stealing 从其他 P 偷不到 G 时,它可以从全局 G 队列获取 G

go func()的调用流程

  1. 我们通过 go func () 来创建一个 goroutine
  2. 有两个存储 G 的队列,一个是局部调度器 P 的本地队列、一个是全局 G 队列。新创建的 G 会先保存在 P 的本地队列中,如果 P 的本地队列已经满了就会保存在全局的队列中
  3. G 只能运行在 M 中,一个 M 必须持有一个 P,M 与 P 是 1:1 的关系。M 会从 P 的本地队列弹出一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会想其他的 MP 组合偷取一个可执行的 G 来执行
  4. 一个 M 调度 G 执行的过程是一个循环机制
  5. 当 M 执行某一个 G 时候如果发生了 syscall 或则其余阻塞操作,M 会阻塞,如果当前有一些 G 在执行,runtime 会把这个线程 M 从 P 中摘除 (detach),然后再创建一个新的操作系统的线程 (如果有空闲的线程可用就复用空闲线程) 来服务于这个 P
  6. 当 M 系统调用结束时候,这个 G 会尝试获取一个空闲的 P 执行,并放入到这个 P 的本地队列。如果获取不到 P,那么这个线程 M 变成休眠状态, 加入到空闲线程中,然后这个 G 会被放入全局队列中

特殊的M0和G0

  • M0

    M0是启动程序后的编号为 0 的主线程,这个M对应的实例会在全局变量runtime.m0中,不需要在heap上分配,M0负责执行初始化操作和启动第一个G,在之后M0就和其他的M一样了

  • G0

    G0是每次启动一个M都会第一个创建的gourtine,G0仅用于负责调度的G,G0不指向任何可执行的函数,每个 M 都会有一个自己的G0。在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0

Lecture 16: Two-Phase Locking

本章主要介绍了数据库事务处理中的2-PL协议

上节课介绍了如何使用WW、WR、RW三种冲突来判断事物之间是否是串行化的,但是在现实中,这种方法是不切实际的,因为我们不可能提前预知所有事物到底什么时候到达,以及事务具体要做什么,而在现实生活中,事务有可能会源源不断地发送到数据库,这就意味着数据库每时每刻都需要将新的事务纳入串行化的判断范畴

因此需要一种协议来确保DBMS在不知道事务的具体内容、具体到达时间的情况下,依然能够作出正确的判断,2-PL协议就是一种通过合理地加锁来达到这种目的的协议

Lock Type

首先需要再一次强调一下数据库中两种类型锁的区别

Locks Latches
Separate User transactions Threads
Protect Database Contents In-Memory Data Structures
During Entire Transactions Critical Sections
Modes Shared, Exclusive, Update, Intention Read, Write
Handle deadlock by Detection & Resolution Waits-for, Timeout, Aborts Avoidance Coding Discipline
Kept in Lock Manager Protected Data Structure

本章关注的是Locks,其有两种基本类型:

  • S-Lock:共享锁(读锁)
  • X-Lock:互斥锁(写锁)

两者的兼容矩阵如下所示:

S-LOCK (shared) X-LOCK (exclusive)
S-LOCK (shared)
X-LOCK (exclusive)

从上节课我们可以知道,WW、WR、RW都可能导致事务冲突,因此只有RR的时候,两把锁是可以兼容的

DBMS有一个名为Lock Manager的模块,专门负责分配、回收事务的锁。每当事务申请加锁或者升级锁的时候,都需要向其发送请求,而Lock Manager内部还维护着一个Lock Table,记录每一个事务所持有的锁(在实验中以哈希表的形式呈现),Lock Manager会根据Lock Table上面的记录来判断是给该事务分配锁,还是拒绝请求,以此确保事务排列的正确性和并发性

Two-Phase Locking

2-PL帮助数据库在运行过程中决定某个事务是否可以访问某条数据,并且 2PL 的正常工作并不需要提前知道所有事务的执行内容,仅仅依靠已知的信息即可

Growing & Shrinking

这个协议定义了两个阶段:Growing 和 Shrinking

  • 在growing阶段,事务可以按需申请获取锁,Lock Manager可以决定分配与否
  • 在shrinking阶段,事务就只能够释放锁,而不能够获取新的锁或者升级已有的锁,也就是说,一旦事务释放了锁,那么它就再也无法获得锁了

这样的协议保证了事物之间一定不会出现有向环路,也就是一定是可串行化的(具体证明可以参考[这篇文章](Transaction management:两阶段锁(two-phase locking) - 知乎 (zhihu.com)))

Cascading Aborts

尽管2-PL能够确保事务之间的串行性,但其可能会引发一种新的问题:联级中止

如果T1产生了Abort,那么已经读取了T1写入数据的T2就变成了脏读,这种情况下,DBMS需要将有过脏读的事务也反馈为Abort,而这些中止可能进而使得其它正在进行的事务级联地中止,这个过程就是所谓的级联中止

Strong Strict 2-PL

为了解决联级中止的问题,我们可以将2-PL进行改进,也被称为Rigorous 2-PL:

  • growing阶段和2-PL相同
  • shrinking阶段,每一个事务只有在自身结束之后才能够释放所有的锁,无论最终该事务是提交还是中止

通过比较Non-2PL、2PL和Rigorous-2PL,可以看出,三者的并发程度越来越低,但是安全性是越来越高的(其中2PL和Rigourous-2PL的安全性是一致的,但是2PL可能会引发联级中止)

Deadlock Detection & Prevention

2PL协议无法解决死锁问题,和操作系统中常见的解决死锁问题的方法一样,可以从事后检测(Detection)和事前阻止(Prevention)来考虑

Deadlock Detection

DBMS会维护一张waits-for graph,用来记录多个事务之间的相互等待关系,一旦图中出现了环,那么DBMS就需要考虑如何去打破这个环

Deadlock Handling

当 DBMS 检测到死锁时,它会选择一个 “受害者” (事务),将该事务回滚,打破环形依赖,而这个 “受害者” 将依靠配置或者应用层逻辑重试或中止。这里有两个设计决定:

  1. 检测死锁的频率
  2. 如何选择合适的 “受害者”

检测死锁的频率越高,陷入死锁的事务等待的时间越短,但消耗的 cpu 也就越多。所以这是个典型的 trade-off,通常有一个调优的参数供用户配置

选择 “受害者” 的指标可能有很多:事务持续时间、事务的进度、事务锁住的数据数量、级联事务的数量、事务曾经重启的次数等等。在选择完 “受害者” 后,DBMS 还有一个设计决定需要做:完全回滚还是回滚到足够消除环形依赖即可

Deadlock Prevention

Deadlock prevention 是一种事前行为,采用这种方案的 DBMS 无需维护 waits-for graph,也不需要实现 detection 算法,而是在事务尝试获取其它事务持有的锁时直接决定是否需要将其中一个事务中止。

通常 prevention 会按照事务的年龄来赋予优先级,事务的时间戳越老,优先级越高。有两种 prevention 的策略:

  • Old Waits for Young:如果 requesting txn 优先级比 holding txn 更高则等待后者释放锁;更低则自行中止
  • Young Waits for Old:如果 requesting txn 优先级比 holding txn 更高则后者自行中止释放锁,让前者获取锁,否则 requesting txn 等待 holding txn 释放锁

无论是 Old Waits for Young 还是 Young Waits for Old,只要保证 prevention 的方向是一致的,就能阻止死锁发生,其原理类似哲学家就餐设定顺序的解决方案:先给哲学家排个序,遇到获取刀叉冲突时,顺序高的优先

Lock Granularity

当事务在获取锁的时候,DBMS可以决定分配给它的锁的粒度(到底是给它锁住一整张表?还是一个tuple?还是一个page?),DBMS需要尽可能给事务分配最少的锁,并且在并行性和开销之间进行权衡(更少,但粒度更大的锁 vs. 更多,但粒度更细的锁)

Intention Locks

Definition

意向锁是一种表级别的锁,用于表明事务将要对某个对象(表、页、行)加锁。它被用来表示一个事务在获取该对象的锁之前是否会先获取其他类型的锁

来自百度百科的定义:

如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞。第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁

Why

那么为什么我们需要意向锁呢?举个例子:现在有事务A已经获取了表t的某一行的互斥锁,而此时表B想要获取这张表的表共享锁,由于两把锁互斥,所以B在试图对表t施加锁的时候必须保证:

  • 当前没有其他事务持有 users 表的排他锁
  • 当前没有其他事务持有 users 表中任意一行的排他锁

而为了确保第二个条件成立,B就需要检查表t的每一行进行判断。很明显效率非常低,而有了意向锁之后,情况就不一样了,我们只需要检查这张表的IS或者IX锁是否空闲,那么就能直接判断能够进行后续的加锁操作,类比来说,意向锁就是这张表所有锁的哨兵

Lock Types

  • Intention-Shared Lock

    如果我们只需要读取表R的某些行,我们可以在这些行上加上S锁。对于整个表R, 表的任何行都可以被读取。这种情况下,可以给表R加上IS锁。从而,如果此时有另一个事务需要读取表R的某些行或是整个表R, 就能根据R上的IS锁直接做出判断,无需遍历表的每一行

  • Intention-Exclusive Lock

    如果我们需要写表R的某些行,此时可以给表R加上IX锁以表达这个意思。例如,表R加上IX锁后,就不能申请R上的S锁,因为IX锁表明某个事务正在修改表R的某些行

  • Shared+Intention-Exclusive Lock

    共享意向排它锁,是S锁和IX锁的结合,适用于以下场景:如果需要修改表中的某些行,但需要读取整个表,这时候就可以给整张表加上SIX锁。可以看到它与IX锁的区别:加入SIX锁后,不能修改表的其它行,因为需要读整张表。

    在MySQL中,并没有SIX锁;但在Oracle、SQL Server中有这种锁,此类数据库有更为复杂的树状组织

Compatibility Matrix

这里给出意向锁、读写锁之间的兼容矩阵,竖为事务T1已经持有的锁,横为T2想要申请的锁:

IS IX S SIX X
IS
IX
S
SIX
X

Locking Protocol

对于一个事务,其需要在数据库层次结构的最高一级获取合适的锁:

  • 如果想要获取一个结点的S或者IS锁,事务必须至少获取其双亲结点的IS锁
  • 如果想要获取一个结点的X、IX或者SIX锁,则必须至少获取其双亲结点的IX锁