CORDISDSH 的心脏第 06 章 · 思想三 · 一扇门全书目录术语表读论文原文 ↗
第六章 · 思想三 · 语境范式

所有事,
都走同一扇门

前两章的保证有个前提:运行时得「看得见」每一件事。这一章讲那扇门,以及两个更微妙的问题:怎样才算复原,怎样才算互不干扰。

约 8 分钟 · 上一章 05 思想二 · 插座 · 下一章 07 思想四 · 一生

题图一扇门,一个门卫。

看不见的,就管不了

回执也好,需求清单也好,都得登记在某个地方。论文把这个地方叫作语境,本书叫它总账:系统当前的状态、每张回执、每份清单,全记在这一本上。

第三个思想就一句话:组件和环境的每一次交互,都必须经过语境。改动要经过它,才会有回执;依赖要经过它,才会被盯着。论文把效应的语境和反效应的语境合成了同一种东西,统一由它居中办理,这就是论文标题里那个「范式」。

反过来,绕开语境做的事,运行时一概不知道。不知道,就谈不上撤,也谈不上通知。论文专门举了一个计数器的例子 p.22,就是下面这个演示。

演示 6.1 · 可以点一个计数器,从黑户到转正
    插件
    管理插件未装
    统计本周用了多少次命令
    语境 · 运行时看得见的总账
    (空)
    门外 · 运行时看不见
    (空)
    旁白小周的管理插件有个「本周命令数」统计。按「开始」,看它把这个数字记在了哪里。
    按主按钮一拍一拍往下走

    怎样才算「复原」

    第 04 章说,卸掉一个组件,环境回到它来之前的样子。可「一模一样」其实做不到。

    论文举的例子很实在:释放一块内存,并不会把内存的排布恢复成分配之前的样子 p.23;删掉一个名字再重新创建,新名字也不是原来那个。物理上,时间回不去。

    图 6.1 · 凑近看,退远看

    看不出区别,就算一样

    所以论文换了一个更聪明的标准,叫观察等价:两个状态,只要用任何合法操作去试都看不出区别,就算一样。

    就像印刷品上的灰色,凑近看是一颗颗网点,退远看就是一片灰。只要读者看到的一样,网点怎么排并不重要。

    这个标准还带来一个好处:「看得出什么」由语境里的操作决定。一个格子只开放了哪些操作,别人就只能用这些操作去试。门越窄,越容易做到「看不出区别」。

    怎样才算「互不干扰」

    几十个组件在同一个系统里同时装装卸卸,凭什么互不干扰?论文的检查办法很省力:按语境里的键,一格一格地看。

    不同的格子,天然互不影响:小周的天气插件写「菜单」格,提醒插件写「数据库」格,谁先谁后都一样。真正要小心的只有一种情况:好几个组件在同一个格子里做事。这时候要看这个格子的操作是不是可交换的:谁先谁后,结果看不出区别。

    演示 6.2 · 可以点
    两个发号窗口

    两个窗口手里的空号都是:2 · 5 · 7。小王和小李各来领一个号。

    窗口 A承诺:发最小的空号
    小王 2 · 小李 5小李 2 · 小王 5
    次序有讲究换个顺序,小王拿到的号就变了
    窗口 B承诺:发一个空号,不说是哪个
    两人各拿到一个空号,互不重复
    随便乱序谁先谁后,外面看不出区别
    窗口 A 多承诺了一句「最小」,别人就能拿这句话来检验谁先谁后,于是两次领号不能交换。窗口 B 少承诺一点,反而换来了自由。POSIX 里,open 是窗口 A(必须返回最小的空闲描述符),mmap 是窗口 B(任意空闲地址都行)。

    这个例子是论文自己举的 p.29,结论有点反直觉:接口承诺得越少,能随便乱序的操作就越多。不必要的承诺,比如「一定是最小的号」,会把你锁死在某个顺序上。

    当然也有天生不可交换的格子。论文点名的是有顺序的中间件链:插在前面的中间件,看到的请求和插在后面的不一样,两者既不能换位,也没法单独撤掉其中一个而不影响另一个 p.29。遇到这种格子,就得老老实实规定顺序。

    所以,一扇门换来了什么

    把这一章串起来:所有交互走同一扇门,运行时才看得见;看得见,才能撤、能通知;再用「看不出区别」来定义复原,用「一格一格地查交换性」来保证互不干扰。前两个思想的保证,到这里才真正落地。

    代价也在这里:门外的东西不受任何保证。所以这一章留给你的,其实是一道检查题。

    带走这一句

    所有状态都进语境:门外的东西,没有回执,也没人通知。接口少承诺一点,就多一些可以乱序的操作。

    请检查这个项目的共享状态:
    1. 列出所有模块级变量、单例、全局缓存,标出哪些在模块卸载或重载后会残留;
    2. 把它们改成通过一个统一的注册入口管理:每个状态有名字、有清理函数;
    3. 对多个模块会同时写的同一份状态,检查写入顺序是否会影响结果;如果会,说明原因,并考虑能否放宽接口承诺(比如不承诺返回值的具体大小或顺序)。