第72章 模擬器更新(5.1k大章)
斯坦威警长在fbi工作,从这警衔就能看出对方的地位,工薪在13万到18万美刀之间0
实际上美利坚联邦调查局只是称警长,职衔正常来说,是属於高级探员,至於主管和助理局长就都是文职一类,在美利坚复杂的官僚调度体系里难以琢磨,不过肉眼可见的也是地位很高。
陈哲本来以为对方也是留学生,可是照现在这么看,这傢伙反而是个土生土长的美国人!
“我对此深感惋惜。”
陈哲心中思绪起伏,表情没什么波折。
“没关係,也不是我第一次跟別人提起这件事了。”书虫摆了摆手,声音低沉。
陈哲拍了拍对方的肩膀。
他並不打算利用书虫的身份做些什么,和对方交谈片刻之后,就整理好了自己脑中的所想,回到了聚会之中。
聚会下半场,本端著咖啡回来的时候,气氛已经松下来了。七八个人散坐在草坪上,有的靠著树干,有的盘腿坐在枯黄的草上,有的趴在野餐桌边刷手机。阳光从云层后面露出来,把整片草坪晒出一层薄薄的暖意。
本没坐下。他站在野餐桌旁边,喝了一口咖啡,目光从人群里扫过,最后落在陈哲身上。
“陈,”他忽然开口,“上次在绿点区跟你聊的时候,有个问题你没答完。”
陈哲抬起头。
本把咖啡放下,走到桌边,从背包里翻出一支马克笔,在白板——其实就是一张a3纸上写了一行字。
“python的gil,你怎么看?”
桌上几个人抬起头。提米的手指停在键盘上,莱拉把可乐罐放下,全民超人从树干上直起身子。
这一句话的信息量极大。
“又是出难题的时候了。”莱拉低声说。
“这次是针对陈一个人的?”
“看来他俩线下已经单独约见过了。”
“群主对有潜力的新人真是上心————”
席中短暂骚动只言片语。
陈哲想了想:“gil是cpython的全局解释器锁,同一时刻只有一个线程执行python字节码。”
本点点头,等他继续。
“所以多线程cpu密集型任务在python里是偽並行,”陈哲说,“只能跑在一个核心上。i0密集型可以用多线程,因为有阻塞等待,gil会释放。”
本没评价,又写了一行字。
“怎么绕过gil?”
陈哲答:“多进程。用multiprocessing模块,每个进程有自己的解释器和gil,能跑满多核。或者用c扩展,把计算密集的部分用c写,在c层面释放gil。还有asyncio,协程,適合i0密集型但不是cpu。
本又写了一行。
“那你在实际项目里用过asyncio吗?”
陈哲想了想,摇头:“用过一点。不过有一次写异步爬虫,aiohttp搭配asyncio,跑起来之后发现有些请求没发出去。后来查了半天,发现是事件循环里有个地方忘了await。调了一天才找到。”
提米在旁边笑了一声:“我懂。我第一次用asyncio的时候,在代码里写了个time.sleep,整个事件循环都卡住了。后来才知道要用asyncio.sleep。
“7
几个人笑了。气氛鬆快了一点。
本没笑。他看著陈哲,又写了一行字。
“那gil在什么情况下是真正的瓶颈?”
陈哲的手指微微紧了一下。这个问题的方向和他预想的不太一样——不是基础题,是在问真实场景的边界判断。
他想了想,开口:“如果是纯cpu计算,单线程已经跑满一个核心,gil就是瓶颈。比如科学计算、图像处理、机器学习训练这些场景。但那种情况一般用numpy,它底层是c,不在python层面算。”
本等著他继续。
“如果是混合场景,”陈哲说,“计算加i0,比如web服务,gil的影响要看请求量和每个请求的计算占比。qps不高的时候,gil不是问题。qps上去之后,每个请求的计算时间只要超过几十毫秒,gil就会开始卡。”
他顿了顿。
“具体閾值要看业务。一般估算的话,单核能撑的qps上限除以cpu占比,再除以並发係数。比如单核能撑1000qps纯i0,如果每个请求有10%的cpu计算,那实际能撑的qps大概在100左右。超过这个数就需要多进程或者换语言。”
本听完,没说话。
他低头在白板上又写了一行字。这次写得很慢,像是在斟酌什么。
“你写过多线程的生產级代码吗?”
陈哲看著那行字,沉默了两秒。
然后他摇了摇头。
“没有。我写的都是单机脚本和web后端。多线程只在demo里跑过,没上过生產。”
本的眉头微微动了一下。
他盯著陈哲看了两秒,然后问了一个更细的问题:“如果让你设计一个多线程的爬虫系统,抓取一万个网页,你会怎么处理线程池的大小?”
陈哲想了想,开口:“先看瓶颈在哪。如果是i0阻塞,线程池大小可以设大一点,一般设100到200。但也要看目標网站的承受能力,不能把人家的伺服器打崩了。所以要用信號量限流,或者用队列控制並发数。”
“如果伺服器返回429呢?”
“加退避。指数退避,第一次等1秒,第二次等2秒,第三次等4秒。如果连续失败超过三次,就把这个url丟回队列,等后面再重试。”
本又问:“那如果队列满了呢?”
“满了就阻塞生產者。或者用有界队列,满了之后生產者等待,等消费者空出位置。”
本的眉头没有鬆开。
他又写了一行字:“你怎么保证每个线程拿到的url不会重复?”
陈哲的手指在膝盖上轻轻敲了一下。这个问题比他预想的深一不是问怎么去重,是问分布式的去重。
“用布隆过滤器。”他说,“每个线程拿url之前先过一遍布隆过滤器,已经爬过的就跳过。布隆过滤器可以用redis的bitmap实现,多个线程共享。误差率可以通过哈希函数个数和位数组大小控制,一般能接受千分之一的误判,少爬几个页面问题不大。”
本点了点头。但他没停。
“如果布隆过滤器误判漏了一个重要页面呢?”
陈哲这次真的停住了。
他沉默了两秒,然后开口,声音很平。
“用確定性去重做备份。布隆过滤器只是第一层过滤,漏掉的页面可以靠第二层校验,比如把url的哈希存在redis的set里,精確去重。但set的內存占用太大,所以可以用布隆过滤器做预筛选,set做兜底。”
本盯著他,又问:“那如果set也扛不住呢?”
陈哲的手指停住了。
他看著本的眼睛,沉默了三秒。
然后他嘆了口气,声音里带著一点无奈,又好像只是很坦诚。
“这个我不知道了。”
他顿了顿,又说了一遍,语气更轻,像是自言自语。
“学艺不精。没做过这么大的量,没碰到过这种级別的瓶颈。”
本的目光在他脸上停了两秒,像是想確认什么。
桌上安静了片刻。
提米在旁边嘖了一声,替陈哲解围:“本,你这题出得也太偏了。分布式爬虫的去重方案,那是架构师才需要考虑的问题。他一个刚入行的新人,能想到布隆过滤器加set兜底已经很不错了。”
全民超人点头:“就是。你问他gil、问线程池、问退避策略,这些还算正常。问到分布式去重,那已经超出普通开发的范围了。”
莱拉也帮腔:“我工作三年了,也没设计过这种量级的系统。平时业务里碰到这种需求,直接用现成的消息队列和分布式框架,谁会自己造轮子?”
本没说话,只是看著陈哲。
陈哲靠回椅背上,端起那杯凉透的咖啡,喝了一口。苦的。他的表情很坦然,像是一个真的不会的人。
“確实不会。”他又说了一遍,声音很稳,没有辩解的意思。
codemaster—us从桌子另一头站起来,走到这边,看了一眼白板上那行字。
他的眉毛挑了一下,然后看著陈哲,开口说:“你能想到布隆过滤器加set已经够了。我面试別人的时候,大多数人连布隆过滤器都说不清楚。分布式去重本身就是个系统工程问题,不是一两句话能说清的。”
本听到这句话,眉毛动了一下。
他看了看codemaster—us,又看了看陈哲,嘴角动了动,想说什么,但最后还是咽了回去。
“行。”他说,把白板翻过去,“这题算我出难了。
“7
他端起咖啡喝了一口,目光在陈哲身上停了一秒,然后移开。
陈哲嘴角微微上扬,隱去了最关键的一步。
那一步,是只有在公司上过班的程式设计师才能得知的一些职业写法,既然不应该出现在他这个大学生的身上,那他就不会说出口去。
聚会散场的时候,天已经暗下来了。
陈哲站在公园门口,看著那群人三三两两地消失在街角。麦克拍了拍他的肩膀,说了句“改天约酒”,然后往地铁站的方向走了。提米和莱拉凑在一起商量去哪儿吃饭,边走边爭论哪家店的披萨正宗。全民超人一个人往河边走,耳机塞在耳朵里,步子很慢。书虫不知道什么时候已经走了,陈哲回头找他的时候,只看见一个黑色的背影消失在街角。
本最后走的。他站在野餐桌旁边,把那块白板收起来,塞进背包里。看见陈哲还站在门口,走过来,在他面前站定。
“你今天答得不错。”他说,“尤其是分布式去重那部分。能想到布隆过滤器加set
兜底,说明你確实有系统设计的意识。至於再往上,那是经验问题,急不来。”
陈哲点点头:“我知道。”
本盯著他看了两秒,目光从他脸上扫过,忽然问了一句:“你刚才说的那些线程池、退避策略、布隆过滤器都是自己在网上学的?”
“对。”陈哲说,“看书,看博客,看开源项目。”
本没说话,只是点了点头,转身往街边走。走了几步,又停下来,回头看著他。
“你那个youtube频道,”他说,“我看过了。”
陈哲的手指微微紧了一下。
“第六期那集文件操作讲得不错。”本说,语气很平,“付费视频我也买了。质量对得起价格。”
他顿了顿,目光在陈哲脸上停了一秒。
“继续做。別停。”
说完,他转身走了。
陈哲站在原地,看著他的背影消失在街角,然后转身往公交站走。
回到公寓的时候,杰姆尼正躺在沙发上看手机。电视开著,放的是某个他叫不上名字的cult血浆片,声音开得很小。他看见陈哲进来,把手机放下,从沙发上坐起来。
——
“回来了?今天的事怎么样?”
“还行。”陈哲把背包扔在沙发上,在另一边坐下。
杰姆尼盯著他看了两秒:“你看起来有点累。”
“有点。”
杰姆尼没再问,从茶几下面摸出两个哑铃,开始做弯举。他的动作比前几天標准了一点,但还是很生硬,每次举起来的时候肩膀都会不自觉地往上耸。陈哲在旁边看了一会儿,忽然开口:“肩膀放鬆。不要用惯性,用肌肉控制。”
杰姆尼愣了一下,调整了一下姿势。这次好了一点,但还是不太对。陈哲站起来,走到他旁边,伸手按了按他的肩膀:“沉下去。不要耸肩。肘关节夹在身体两侧,不要往外撇。”
杰姆尼照做了。这次动作明显顺了很多,二头肌鼓起来,手臂上的血管凸出皮肤表面。他做了十五个,把哑铃放下,活动了一下肩膀。
“舒服多了。”他说,“陈,你怎么这么懂?”
“但凡有一点锻炼经验都知道的吧,你不会上网查么?”
杰姆尼嘖了一声,从沙发上站起来,开始做伏地挺身。这次他没等陈哲纠正,自己调整了一下手的位置和背的角度。做了二十个,站起来,拍了拍手上的灰。
“陈,”他说,“忘了说————你那个枪,今天打完之后我擦过了。弹壳也捡了,扔在河边的垃圾桶里。放心。”
陈哲点点头:“谢了。”
杰姆尼该说的话也说完了,也就摇头晃脑地回了房间。
客厅安静下来。陈哲靠在沙发上,盯著天花板。
休息了一会儿,陈哲站起来,走到桌边,打开那台二手thinkpad。屏幕亮起来的时候,他习惯性地先看了一眼右下角,模擬器的图標闪烁。
陈哲盯著软体图標看了几秒,手指在触控板上悬著,没有点下去。窗外传来警笛声,由远及近,又由近及远,消失在不知道哪个街区。
他收回目光,关掉模擬器窗口,打开群聊。
【布鲁克林区程式设计师交流群】
【小王子:@陈,今天聚会没聊够啊,本那个分布式去重的题你后面想明白了吗?】
【全民超人:那题本来就是架构师面试用的,他能想到布隆过滤器加set已经很不错了。】
【爱来自冒险家协会:+1。我工作三年了,也没设计过那种量级的系统。】
【麦克:@陈,別放在心上,本就是那种人,对新人特別严格。】
陈哲靠在椅背上,开始打字。
【陈:没事。本上次单独约我聊过一次,可能对我期望比较高。】
群里安静了一秒。
【小王子:???】
【爱来自冒险家协会:单独约聊?什么时候的事?】
【陈:上周末。在绿点区,他家里。】
【全民超人:————】
【麦克:臥槽,本一般不单独见人。】
【爱来自冒险家协会:他跟你聊了什么?】
陈哲想了想,打字。
【陈:聊了聊技术方向。看了我一些代码。喝了杯咖啡。】
【小王子:就这些?】
【陈:就这些。】
【全民超人:本这个人眼光很高的。他愿意单独约你,说明他觉得你有东西。】
【麦克:+1。我跟他认识三年了,他从来没单独约过我。】
【小王子:+1】
【爱来自冒险家协会:+1】
一如既往的震惊。
陈哲盯著屏幕,嘴角动了一下,大概过一会儿就能看到本在群里的认证了,反正这事在聚会里已经暴露过了,那么证实一下他们的揣测,反而是对自己百利无一害的,是增加声望的行为。
他关掉群聊,靠在椅背上。窗外的天已经完全黑了,只有对面那栋老公寓楼上还亮著几扇窗,橘黄色的光透出来,在夜色里晕成模糊的一团。
杰姆尼房间里传来他锻炼的声音,呼哧呼哧的喘息,夹杂著计数声。
陈哲听著那个声音,忽然站起来,走到客厅中间,趴下去,开始做伏地挺身。
一个,两个,三个。动作很慢,下去的时候胸口几乎贴著地板,起来的时候手臂完全伸直。他做了二十个,呼吸开始变重。做到四十个的时候,汗从额头滴下来,砸在地板上,发出轻微的啪嗒声。做到六十个的时候,手臂开始发抖。
八十个。他咬著牙,脸上的肌肉绷紧,额头上青筋暴起。一百个。他撑在地上,胸口离地板只剩一拳的距离,起不来了。
他趴在地板上,大口喘气。汗从下巴滴在地上,在灰白色的地板上洇出深色的一小片。
一百个伏地挺身,这就不是神经募集能力能涉足的领域了,是纯粹的肌肉耐性问题。
陈哲能感觉到自己一开始做得轻鬆,但是后面倒也没有多少疲惫的时候就开始喘息,可见还是身体的硬体没能跟上多少。
而最为重点的,是陈哲已经许久没有和互助会联络,那个作家弗兰克也没有在私人聊天的界面里和他有多少交谈。
陈哲想了想,滑动触控板,还是打开人生重开模擬器。
只是出乎他意料的是,呈现在眼前的,並不是新增加的模擬次数,而是模擬器更新了版本。
斯坦威警长在fbi工作,从这警衔就能看出对方的地位,工薪在13万到18万美刀之间0
实际上美利坚联邦调查局只是称警长,职衔正常来说,是属於高级探员,至於主管和助理局长就都是文职一类,在美利坚复杂的官僚调度体系里难以琢磨,不过肉眼可见的也是地位很高。
陈哲本来以为对方也是留学生,可是照现在这么看,这傢伙反而是个土生土长的美国人!
“我对此深感惋惜。”
陈哲心中思绪起伏,表情没什么波折。
“没关係,也不是我第一次跟別人提起这件事了。”书虫摆了摆手,声音低沉。
陈哲拍了拍对方的肩膀。
他並不打算利用书虫的身份做些什么,和对方交谈片刻之后,就整理好了自己脑中的所想,回到了聚会之中。
聚会下半场,本端著咖啡回来的时候,气氛已经松下来了。七八个人散坐在草坪上,有的靠著树干,有的盘腿坐在枯黄的草上,有的趴在野餐桌边刷手机。阳光从云层后面露出来,把整片草坪晒出一层薄薄的暖意。
本没坐下。他站在野餐桌旁边,喝了一口咖啡,目光从人群里扫过,最后落在陈哲身上。
“陈,”他忽然开口,“上次在绿点区跟你聊的时候,有个问题你没答完。”
陈哲抬起头。
本把咖啡放下,走到桌边,从背包里翻出一支马克笔,在白板——其实就是一张a3纸上写了一行字。
“python的gil,你怎么看?”
桌上几个人抬起头。提米的手指停在键盘上,莱拉把可乐罐放下,全民超人从树干上直起身子。
这一句话的信息量极大。
“又是出难题的时候了。”莱拉低声说。
“这次是针对陈一个人的?”
“看来他俩线下已经单独约见过了。”
“群主对有潜力的新人真是上心————”
席中短暂骚动只言片语。
陈哲想了想:“gil是cpython的全局解释器锁,同一时刻只有一个线程执行python字节码。”
本点点头,等他继续。
“所以多线程cpu密集型任务在python里是偽並行,”陈哲说,“只能跑在一个核心上。i0密集型可以用多线程,因为有阻塞等待,gil会释放。”
本没评价,又写了一行字。
“怎么绕过gil?”
陈哲答:“多进程。用multiprocessing模块,每个进程有自己的解释器和gil,能跑满多核。或者用c扩展,把计算密集的部分用c写,在c层面释放gil。还有asyncio,协程,適合i0密集型但不是cpu。
本又写了一行。
“那你在实际项目里用过asyncio吗?”
陈哲想了想,摇头:“用过一点。不过有一次写异步爬虫,aiohttp搭配asyncio,跑起来之后发现有些请求没发出去。后来查了半天,发现是事件循环里有个地方忘了await。调了一天才找到。”
提米在旁边笑了一声:“我懂。我第一次用asyncio的时候,在代码里写了个time.sleep,整个事件循环都卡住了。后来才知道要用asyncio.sleep。
“7
几个人笑了。气氛鬆快了一点。
本没笑。他看著陈哲,又写了一行字。
“那gil在什么情况下是真正的瓶颈?”
陈哲的手指微微紧了一下。这个问题的方向和他预想的不太一样——不是基础题,是在问真实场景的边界判断。
他想了想,开口:“如果是纯cpu计算,单线程已经跑满一个核心,gil就是瓶颈。比如科学计算、图像处理、机器学习训练这些场景。但那种情况一般用numpy,它底层是c,不在python层面算。”
本等著他继续。
“如果是混合场景,”陈哲说,“计算加i0,比如web服务,gil的影响要看请求量和每个请求的计算占比。qps不高的时候,gil不是问题。qps上去之后,每个请求的计算时间只要超过几十毫秒,gil就会开始卡。”
他顿了顿。
“具体閾值要看业务。一般估算的话,单核能撑的qps上限除以cpu占比,再除以並发係数。比如单核能撑1000qps纯i0,如果每个请求有10%的cpu计算,那实际能撑的qps大概在100左右。超过这个数就需要多进程或者换语言。”
本听完,没说话。
他低头在白板上又写了一行字。这次写得很慢,像是在斟酌什么。
“你写过多线程的生產级代码吗?”
陈哲看著那行字,沉默了两秒。
然后他摇了摇头。
“没有。我写的都是单机脚本和web后端。多线程只在demo里跑过,没上过生產。”
本的眉头微微动了一下。
他盯著陈哲看了两秒,然后问了一个更细的问题:“如果让你设计一个多线程的爬虫系统,抓取一万个网页,你会怎么处理线程池的大小?”
陈哲想了想,开口:“先看瓶颈在哪。如果是i0阻塞,线程池大小可以设大一点,一般设100到200。但也要看目標网站的承受能力,不能把人家的伺服器打崩了。所以要用信號量限流,或者用队列控制並发数。”
“如果伺服器返回429呢?”
“加退避。指数退避,第一次等1秒,第二次等2秒,第三次等4秒。如果连续失败超过三次,就把这个url丟回队列,等后面再重试。”
本又问:“那如果队列满了呢?”
“满了就阻塞生產者。或者用有界队列,满了之后生產者等待,等消费者空出位置。”
本的眉头没有鬆开。
他又写了一行字:“你怎么保证每个线程拿到的url不会重复?”
陈哲的手指在膝盖上轻轻敲了一下。这个问题比他预想的深一不是问怎么去重,是问分布式的去重。
“用布隆过滤器。”他说,“每个线程拿url之前先过一遍布隆过滤器,已经爬过的就跳过。布隆过滤器可以用redis的bitmap实现,多个线程共享。误差率可以通过哈希函数个数和位数组大小控制,一般能接受千分之一的误判,少爬几个页面问题不大。”
本点了点头。但他没停。
“如果布隆过滤器误判漏了一个重要页面呢?”
陈哲这次真的停住了。
他沉默了两秒,然后开口,声音很平。
“用確定性去重做备份。布隆过滤器只是第一层过滤,漏掉的页面可以靠第二层校验,比如把url的哈希存在redis的set里,精確去重。但set的內存占用太大,所以可以用布隆过滤器做预筛选,set做兜底。”
本盯著他,又问:“那如果set也扛不住呢?”
陈哲的手指停住了。
他看著本的眼睛,沉默了三秒。
然后他嘆了口气,声音里带著一点无奈,又好像只是很坦诚。
“这个我不知道了。”
他顿了顿,又说了一遍,语气更轻,像是自言自语。
“学艺不精。没做过这么大的量,没碰到过这种级別的瓶颈。”
本的目光在他脸上停了两秒,像是想確认什么。
桌上安静了片刻。
提米在旁边嘖了一声,替陈哲解围:“本,你这题出得也太偏了。分布式爬虫的去重方案,那是架构师才需要考虑的问题。他一个刚入行的新人,能想到布隆过滤器加set兜底已经很不错了。”
全民超人点头:“就是。你问他gil、问线程池、问退避策略,这些还算正常。问到分布式去重,那已经超出普通开发的范围了。”
莱拉也帮腔:“我工作三年了,也没设计过这种量级的系统。平时业务里碰到这种需求,直接用现成的消息队列和分布式框架,谁会自己造轮子?”
本没说话,只是看著陈哲。
陈哲靠回椅背上,端起那杯凉透的咖啡,喝了一口。苦的。他的表情很坦然,像是一个真的不会的人。
“確实不会。”他又说了一遍,声音很稳,没有辩解的意思。
codemaster—us从桌子另一头站起来,走到这边,看了一眼白板上那行字。
他的眉毛挑了一下,然后看著陈哲,开口说:“你能想到布隆过滤器加set已经够了。我面试別人的时候,大多数人连布隆过滤器都说不清楚。分布式去重本身就是个系统工程问题,不是一两句话能说清的。”
本听到这句话,眉毛动了一下。
他看了看codemaster—us,又看了看陈哲,嘴角动了动,想说什么,但最后还是咽了回去。
“行。”他说,把白板翻过去,“这题算我出难了。
“7
他端起咖啡喝了一口,目光在陈哲身上停了一秒,然后移开。
陈哲嘴角微微上扬,隱去了最关键的一步。
那一步,是只有在公司上过班的程式设计师才能得知的一些职业写法,既然不应该出现在他这个大学生的身上,那他就不会说出口去。
聚会散场的时候,天已经暗下来了。
陈哲站在公园门口,看著那群人三三两两地消失在街角。麦克拍了拍他的肩膀,说了句“改天约酒”,然后往地铁站的方向走了。提米和莱拉凑在一起商量去哪儿吃饭,边走边爭论哪家店的披萨正宗。全民超人一个人往河边走,耳机塞在耳朵里,步子很慢。书虫不知道什么时候已经走了,陈哲回头找他的时候,只看见一个黑色的背影消失在街角。
本最后走的。他站在野餐桌旁边,把那块白板收起来,塞进背包里。看见陈哲还站在门口,走过来,在他面前站定。
“你今天答得不错。”他说,“尤其是分布式去重那部分。能想到布隆过滤器加set
兜底,说明你確实有系统设计的意识。至於再往上,那是经验问题,急不来。”
陈哲点点头:“我知道。”
本盯著他看了两秒,目光从他脸上扫过,忽然问了一句:“你刚才说的那些线程池、退避策略、布隆过滤器都是自己在网上学的?”
“对。”陈哲说,“看书,看博客,看开源项目。”
本没说话,只是点了点头,转身往街边走。走了几步,又停下来,回头看著他。
“你那个youtube频道,”他说,“我看过了。”
陈哲的手指微微紧了一下。
“第六期那集文件操作讲得不错。”本说,语气很平,“付费视频我也买了。质量对得起价格。”
他顿了顿,目光在陈哲脸上停了一秒。
“继续做。別停。”
说完,他转身走了。
陈哲站在原地,看著他的背影消失在街角,然后转身往公交站走。
回到公寓的时候,杰姆尼正躺在沙发上看手机。电视开著,放的是某个他叫不上名字的cult血浆片,声音开得很小。他看见陈哲进来,把手机放下,从沙发上坐起来。
——
“回来了?今天的事怎么样?”
“还行。”陈哲把背包扔在沙发上,在另一边坐下。
杰姆尼盯著他看了两秒:“你看起来有点累。”
“有点。”
杰姆尼没再问,从茶几下面摸出两个哑铃,开始做弯举。他的动作比前几天標准了一点,但还是很生硬,每次举起来的时候肩膀都会不自觉地往上耸。陈哲在旁边看了一会儿,忽然开口:“肩膀放鬆。不要用惯性,用肌肉控制。”
杰姆尼愣了一下,调整了一下姿势。这次好了一点,但还是不太对。陈哲站起来,走到他旁边,伸手按了按他的肩膀:“沉下去。不要耸肩。肘关节夹在身体两侧,不要往外撇。”
杰姆尼照做了。这次动作明显顺了很多,二头肌鼓起来,手臂上的血管凸出皮肤表面。他做了十五个,把哑铃放下,活动了一下肩膀。
“舒服多了。”他说,“陈,你怎么这么懂?”
“但凡有一点锻炼经验都知道的吧,你不会上网查么?”
杰姆尼嘖了一声,从沙发上站起来,开始做伏地挺身。这次他没等陈哲纠正,自己调整了一下手的位置和背的角度。做了二十个,站起来,拍了拍手上的灰。
“陈,”他说,“忘了说————你那个枪,今天打完之后我擦过了。弹壳也捡了,扔在河边的垃圾桶里。放心。”
陈哲点点头:“谢了。”
杰姆尼该说的话也说完了,也就摇头晃脑地回了房间。
客厅安静下来。陈哲靠在沙发上,盯著天花板。
休息了一会儿,陈哲站起来,走到桌边,打开那台二手thinkpad。屏幕亮起来的时候,他习惯性地先看了一眼右下角,模擬器的图標闪烁。
陈哲盯著软体图標看了几秒,手指在触控板上悬著,没有点下去。窗外传来警笛声,由远及近,又由近及远,消失在不知道哪个街区。
他收回目光,关掉模擬器窗口,打开群聊。
【布鲁克林区程式设计师交流群】
【小王子:@陈,今天聚会没聊够啊,本那个分布式去重的题你后面想明白了吗?】
【全民超人:那题本来就是架构师面试用的,他能想到布隆过滤器加set已经很不错了。】
【爱来自冒险家协会:+1。我工作三年了,也没设计过那种量级的系统。】
【麦克:@陈,別放在心上,本就是那种人,对新人特別严格。】
陈哲靠在椅背上,开始打字。
【陈:没事。本上次单独约我聊过一次,可能对我期望比较高。】
群里安静了一秒。
【小王子:???】
【爱来自冒险家协会:单独约聊?什么时候的事?】
【陈:上周末。在绿点区,他家里。】
【全民超人:————】
【麦克:臥槽,本一般不单独见人。】
【爱来自冒险家协会:他跟你聊了什么?】
陈哲想了想,打字。
【陈:聊了聊技术方向。看了我一些代码。喝了杯咖啡。】
【小王子:就这些?】
【陈:就这些。】
【全民超人:本这个人眼光很高的。他愿意单独约你,说明他觉得你有东西。】
【麦克:+1。我跟他认识三年了,他从来没单独约过我。】
【小王子:+1】
【爱来自冒险家协会:+1】
一如既往的震惊。
陈哲盯著屏幕,嘴角动了一下,大概过一会儿就能看到本在群里的认证了,反正这事在聚会里已经暴露过了,那么证实一下他们的揣测,反而是对自己百利无一害的,是增加声望的行为。
他关掉群聊,靠在椅背上。窗外的天已经完全黑了,只有对面那栋老公寓楼上还亮著几扇窗,橘黄色的光透出来,在夜色里晕成模糊的一团。
杰姆尼房间里传来他锻炼的声音,呼哧呼哧的喘息,夹杂著计数声。
陈哲听著那个声音,忽然站起来,走到客厅中间,趴下去,开始做伏地挺身。
一个,两个,三个。动作很慢,下去的时候胸口几乎贴著地板,起来的时候手臂完全伸直。他做了二十个,呼吸开始变重。做到四十个的时候,汗从额头滴下来,砸在地板上,发出轻微的啪嗒声。做到六十个的时候,手臂开始发抖。
八十个。他咬著牙,脸上的肌肉绷紧,额头上青筋暴起。一百个。他撑在地上,胸口离地板只剩一拳的距离,起不来了。
他趴在地板上,大口喘气。汗从下巴滴在地上,在灰白色的地板上洇出深色的一小片。
一百个伏地挺身,这就不是神经募集能力能涉足的领域了,是纯粹的肌肉耐性问题。
陈哲能感觉到自己一开始做得轻鬆,但是后面倒也没有多少疲惫的时候就开始喘息,可见还是身体的硬体没能跟上多少。
而最为重点的,是陈哲已经许久没有和互助会联络,那个作家弗兰克也没有在私人聊天的界面里和他有多少交谈。
陈哲想了想,滑动触控板,还是打开人生重开模擬器。
只是出乎他意料的是,呈现在眼前的,並不是新增加的模擬次数,而是模擬器更新了版本。
